ワンクリックで
tests
Write and/or run unit and integration tests, ensuring high code coverage and reliability.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Write and/or run unit and integration tests, ensuring high code coverage and reliability.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Conventions for writing NEW Go code in the Fabric-X Committer repo: code organization/ordering, concurrency (errgroup + context-aware channels), Go 1.26 idioms, error handling, logging, and service/config/metrics structure. Use this skill BEFORE writing or modifying any Go source in this repository — adding a service, component, function, gRPC handler, or config field, or refactoring existing code — so the new code matches established patterns and passes `make lint`. For test-only work use the `tests` skill; for reviewing a PR use `pr-review`.
Audit the repository's documentation, agent instructions, other skills, config samples, and config templates for staleness introduced by a code change — every *.md, *.yaml/*.yml, and *.tmpl file. Use AFTER finishing any development task that touched Go code, config fields, CLI flags, metrics, make targets, scripts, or file/package layout, and whenever the user asks to "check the docs", "audit the docs", or "did I make anything obsolete?". It auto-fixes the repo's generated docs (metrics reference, CLI help, loadgen sample tree) and reports likely-stale prose, agent instructions, skills, and config templates with file:line and a suggested edit. Runs well on a dispatched subagent to keep the parent context clean. It only audits EXISTING files for staleness a code change introduced — do not use it to author or edit documentation (writing a new doc, adding a section, rewriting prose). For writing new Go code use the `development` skill; for tests use `tests`; for PR review use `pr-review`.
Review a GitHub pull request for the Fabric-X Committer project and post comment-only findings. Invoke with the PR URL.
Write a commit message (which becomes the GitHub PR description verbatim) or open a GitHub issue for the Fabric-X Committer repo, following the repo's PR template and conventions. Use whenever the user wants to commit staged changes, write or fix a commit/PR message, prepare a PR description, or open/file/create a GitHub issue for this project — even for terse asks like "commit this", "write the PR description", or "file an issue for this bug".
| name | tests |
| description | Write and/or run unit and integration tests, ensuring high code coverage and reliability. |
This document provides specific guidelines for writing and running tests in the fabric-x-committer project.
test-core-db: Components that directly interact with the databasetest-requires-db: Components that depend on the database layertest-no-db: Pure logic tests with no database dependencymake kill-test-docker to clean up containers after testing.export DB_DEPLOYMENT=local.docker ps and check sc_test_postgres_unit_tests.scripts/get-and-start-postgres.shtc as the test-case variable namefor _, tc := range []struct {...}t.Parallel() in all tests and subtestst.Helper() in helper functionrequire.NoError(t, err) to handle errors.require.ErrorContains() instead of require.Error() and then require.Contains()make lintDon't hand-roll crypto, TLS, config-block, or service/DB setup — reuse existing helpers:
test.RunServiceForTest / test.ServeForTest (utils/test/serve.go), testdb.PrepareTestEnv + testdb.RunTestMain (utils/testdb), and the hand-written mocks in mock/.test.RequireProtoEqual / test.RequireProtoElementsMatch (utils/test/require_proto.go) — never compare protos with require.Equal. Both accept require.TestingT, so they also work inside require.EventuallyWithT.github.com/hyperledger/fabric-x-common/utils/testcrypto — CreateOrExtendConfigBlockWithCrypto, ConfigBlock, PrepareBlockHeaderAndMetadata, GetPeersIdentities / GetConsenterIdentities / GetSigningIdentities / GetPeersMspDirs / GetConsenterMspDirs.github.com/hyperledger/fabric-x-common/common/crypto/tlsgen — tlsgen.NewCA(), CA, CertKeyPair.github.com/hyperledger/fabric-x-common/tools/cryptogen.For authoring the production code these tests cover, use the development skill.
❌ INCORRECT - Do not nest success/failure cases:
func TestMyFunction(t *testing.T) {
t.Parallel()
t.Run("success cases", func(t *testing.T) { // ❌ Unnecessary nesting
t.Parallel()
for _, tc := range []struct{...}{...} {
t.Run(tc.name, func(t *testing.T) {
// test logic
})
}
})
t.Run("failure cases", func(t *testing.T) { // ❌ Unnecessary nesting
t.Parallel()
for _, tc := range []struct{...}{...} {
t.Run(tc.name, func(t *testing.T) {
// test logic
})
}
})
}
✅ CORRECT - Use flat structure with descriptive test names:
func TestMyFunction(t *testing.T) {
t.Parallel()
// Success cases
for _, tc := range []struct {
name string
input string
expected string
}{
{
name: "valid input returns expected output",
input: "test",
expected: "TEST",
},
{
name: "empty input returns empty string",
input: "",
expected: "",
},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
result := MyFunction(tc.input)
require.Equal(t, tc.expected, result)
})
}
// Failure cases
for _, tc := range []struct {
name string
input string
}{
{
name: "nil input panics",
input: nil,
},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
require.Panics(t, func() {
MyFunction(tc.input)
})
})
}
}
✅ CORRECT - Define test cases inline:
for _, tc := range []struct {
name string
input int
expected int
}{
{name: "positive number", input: 5, expected: 25},
{name: "zero", input: 0, expected: 0},
{name: "negative number", input: -3, expected: 9},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
result := Square(tc.input)
require.Equal(t, tc.expected, result)
})
}
tc as the test case variable name in table-driven testst.Parallel() in the main test functiont.Parallel() in each subtestt.Helper() in helper functions to improve error reportingpanic() in testsrequire.NoError(t, err) to handle errorsrequire.ErrorContains(t, err, "expected message") instead of require.Error() followed by require.Contains()When testing functions that panic:
✅ CORRECT - Use require.Panics() for general panic testing:
require.Panics(t, func() {
MyFunction(invalidInput)
})
❌ AVOID - Don't use require.PanicsWithValue() or require.PanicsWithError() when error wrapping is involved:
// This may fail due to error wrapping (e.g., cockroachdb/errors)
require.PanicsWithValue(t, "exact error message", func() {
MyFunction(invalidInput)
})
Rationale: Error wrapping libraries (like cockroachdb/errors) add stack traces and metadata, making exact value matching unreliable. Use require.Panics() unless you have a specific need to verify the exact panic value.
func TestBucketConfig_Buckets(t *testing.T) {
t.Parallel()
// Success cases
for _, tc := range []struct {
name string
config BucketConfig
expected []float64
}{
{
name: "uniform distribution with 5 buckets",
config: BucketConfig{
Distribution: BucketUniform,
MaxLatency: 10 * time.Second,
BucketCount: 5,
},
expected: []float64{0, 2.5, 5, 7.5, 10},
},
{
name: "empty distribution",
config: BucketConfig{
Distribution: BucketEmpty,
},
expected: []float64{},
},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
result := tc.config.Buckets()
require.Equal(t, tc.expected, result)
})
}
// Failure cases
for _, tc := range []struct {
name string
config BucketConfig
}{
{
name: "invalid bucket count",
config: BucketConfig{
Distribution: BucketUniform,
BucketCount: 0,
},
},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
require.Panics(t, func() {
tc.config.Buckets()
})
})
}
}
The project provides specialized helper functions in utils/test/metrics.go for testing Prometheus metrics. Use these helpers consistently across all tests.
✅ CORRECT - Use test.RequireIntMetricValue() for immediate assertions:
// Assert metric equals expected value immediately
test.RequireIntMetricValue(t, 5, myMetric)
test.RequireIntMetricValue(t, 0, myMetric.WithLabelValues("label"))
// Verify initial state
test.RequireIntMetricValue(t, 0, metrics.pendingRequests)
✅ CORRECT - Use test.EventuallyIntMetric() when metrics update asynchronously:
// Wait for metric to reach expected value
test.EventuallyIntMetric(
t,
expectedValue,
myMetric,
5*time.Second, // waitFor duration
100*time.Millisecond, // tick interval
)
// With optional message
test.EventuallyIntMetric(
t,
10,
metrics.transactionsSentTotal,
5*time.Second,
10*time.Millisecond,
"transactions should be sent",
)
Use test.GetIntMetricValue() or test.GetMetricValue() when you need the value for custom assertions:
// Get integer metric value (rounded)
preValue := test.GetIntMetricValue(t, metrics.transactionReceivedTotal)
// Perform operations...
// Assert using the captured value
test.EventuallyIntMetric(t, preValue+10, metrics.transactionReceivedTotal,
5*time.Second, 100*time.Millisecond)
// Get float metric value (for histograms, summaries)
latency := test.GetMetricValue(t, metrics.blockMappingInRelaySeconds)
require.Greater(t, latency, float64(0))
// Use with require.EventuallyWithT for complex conditions
require.EventuallyWithT(t, func(ct *assert.CollectT) {
require.Equal(ct, 0, test.GetIntMetricValue(t, metrics.queueSize))
require.Equal(ct, 5, test.GetIntMetricValue(t, metrics.activeWorkers))
}, 3*time.Second, 500*time.Millisecond)
For integration tests that check metrics via HTTP:
// Check that specific metrics exist in the output
test.CheckMetrics(t, metricsURL, tlsConfig,
"loadgen_block_sent_total",
"loadgen_transaction_committed_total",
)
// Get specific metric value from HTTP endpoint
count := test.GetMetricValueFromURL(t, metricsURL, "metric_name", tlsConfig)
require.Greater(t, count, 500)
Use Appropriate Helper: Choose the right helper based on timing requirements:
test.RequireIntMetricValue() for synchronous operations where the metric should already have the expected valuetest.EventuallyIntMetric() for asynchronous operations where the metric will reach the expected value after some timetest.GetIntMetricValue() for capturing baseline values or when using with require.Eventually() for complex conditionstest.GetMetricValue() for float metrics (histograms, summaries) or when you need the raw float valueCapture Baseline Values: When testing incremental changes, capture the metric value before the operation:
preValue := test.GetIntMetricValue(t, metrics.transactionReceivedTotal)
// perform operation that adds 10 transactions
sendTransactions(10)
// Verify the increment
test.EventuallyIntMetric(t, preValue+10, metrics.transactionReceivedTotal,
5*time.Second, 100*time.Millisecond)
Test Labeled Metrics: Use WithLabelValues() to test specific label combinations:
test.RequireIntMetricValue(t, 10,
metrics.transactionCommittedTotal.WithLabelValues("COMMITTED"))
test.RequireIntMetricValue(t, 2,
metrics.transactionCommittedTotal.WithLabelValues("MALFORMED"))
Use Appropriate Wait Times: For test.EventuallyIntMetric():
5*time.Second, 10*time.Millisecond5*time.Second, 100*time.Millisecond2*time.Second, 200*time.MillisecondTest Initial State: Always verify metrics start at expected initial values:
test.RequireIntMetricValue(t, 0, metrics.pendingRequests)
test.RequireIntMetricValue(t, 0, metrics.activeStreams)
Verify Metric Decrements: When testing gauges that can decrease:
test.RequireIntMetricValue(t, 5, metrics.queueSize)
// process items
test.EventuallyIntMetric(t, 0, metrics.queueSize, 2*time.Second, 100*time.Millisecond)
Use require.Never() for Bounds Testing: When verifying metrics don't exceed limits:
require.Never(t, func() bool {
return test.GetIntMetricValue(t, metrics.counter) > maxExpected
}, 3*time.Second, 1*time.Second)
Use require.EventuallyWithT() for Multi-Metric Conditions: When checking multiple metrics, always use require.EventuallyWithT() instead of require.Eventually() to ensure clear visibility of which metric failed:
// ✅ CORRECT - Shows which metric failed
require.EventuallyWithT(t, func(ct *assert.CollectT) {
require.Equal(ct, 1, test.GetIntMetricValue(t, metrics.inputQueue))
require.Equal(ct, 1, test.GetIntMetricValue(t, metrics.outputQueue))
require.Equal(ct, 0, test.GetIntMetricValue(t, metrics.processingQueue))
}, 3*time.Second, 500*time.Millisecond)
// ❌ INCORRECT - Doesn't show which condition failed
require.Eventually(t, func() bool {
return test.GetIntMetricValue(t, metrics.inputQueue) == 1 &&
test.GetIntMetricValue(t, metrics.outputQueue) == 1 &&
test.GetIntMetricValue(t, metrics.processingQueue) == 0
}, 3*time.Second, 500*time.Millisecond)
Use require.EventuallyWithT() for Single Metrics with Context: When you need detailed assertion messages for a single metric:
require.EventuallyWithT(t, func(ct *assert.CollectT) {
actual := test.GetIntMetricValue(t, metrics.counter)
require.Equal(ct, expected, actual, "counter should reach expected value")
}, 5*time.Second, 100*time.Millisecond)
Test Timing Metrics: For histogram and summary metrics, verify they are positive:
latency := test.GetMetricValue(t, metrics.requestDurationSeconds)
require.Greater(t, latency, float64(0))
// Capture baseline
preValue := test.GetIntMetricValue(t, metrics.transactionReceivedTotal)
// Perform operation
sendTransactions(5)
// Verify increment
test.EventuallyIntMetric(t, preValue+5, metrics.transactionReceivedTotal,
5*time.Second, 100*time.Millisecond)
test.RequireIntMetricValue(t, 30, metrics.notifierPendingTxIDs)
test.RequireIntMetricValue(t, 6, metrics.notifierUniquePendingTxIDs)
test.RequireIntMetricValue(t, 10, metrics.notifierTxIDsStatusDeliveries)
test.RequireIntMetricValue(t, 0, metrics.notifierTxIDsTimeoutDeliveries)
// Verify queue fills up
test.EventuallyIntMetric(t, 3, metrics.waitingTransactionsQueueSize,
5*time.Second, 10*time.Millisecond)
// Process items
processQueue()
// Verify queue empties
test.EventuallyIntMetric(t, 0, metrics.waitingTransactionsQueueSize,
5*time.Second, 10*time.Millisecond)
// Use EventuallyWithT for clear failure messages
require.EventuallyWithT(t, func(ct *assert.CollectT) {
require.Equal(ct, 10, test.GetIntMetricValue(t, metrics.gdgTxProcessedTotal))
require.Equal(ct, 8, test.GetIntMetricValue(t, metrics.gdgValidatedTxProcessedTotal))
}, 2*time.Second, 200*time.Millisecond)
require.EventuallyWithT(t, func(ct *assert.CollectT) {
count := test.GetMetricValueFromURL(
t, metricsURL, "loadgen_transaction_committed_total", tlsConfig,
)
require.Greater(ct, count, 500)
}, 300*time.Second, 1*time.Second)
func TestRelayMetrics(t *testing.T) {
t.Parallel()
env := setupTestEnvironment(t)
m := env.metrics
// Verify initial state
test.RequireIntMetricValue(t, 0, m.transactionsSentTotal)
test.RequireIntMetricValue(t, 0, m.waitingTransactionsQueueSize)
// Submit transactions
txCount := 3
submitTransactions(txCount)
// Verify metrics update asynchronously
test.EventuallyIntMetric(t, txCount, m.transactionsSentTotal,
5*time.Second, 10*time.Millisecond)
test.EventuallyIntMetric(t, txCount, m.waitingTransactionsQueueSize,
5*time.Second, 10*time.Millisecond)
// Process transactions
processTransactions()
// Verify labeled metrics
test.RequireIntMetricValue(t, txCount,
m.transactionsStatusReceivedTotal.WithLabelValues("COMMITTED"))
// Verify queue empties
test.EventuallyIntMetric(t, 0, m.waitingTransactionsQueueSize,
5*time.Second, 10*time.Millisecond)
// Verify timing metrics are positive
require.Greater(t, test.GetMetricValue(t, m.processingSeconds), float64(0))
}
tc for test case variablet.Parallel()require.Panics() for panic testingt.Helper()panic() in teststest.RequireIntMetricValue(), test.EventuallyIntMetric(), test.GetIntMetricValue()