Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Automates unit test creation for Go projects using the standard testing package with consistent software testing patterns including In-Got-Want, Table-Driven Testing, and AAA patterns. Use when creating, modifying, or reviewing unit tests, or when the user mentions unit tests, test coverage, or Go testing.
.claude/skills/sentenz-go-unit-testing/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 80% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 120% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 102% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 299% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 154% | 0% |
Instructions for AI coding agents on automating unit test creation using consistent software testing patterns in this Go project.
> Ensures high code quality and reliability. Tests are self-documenting, reducing cognitive load for reviewers and maintainers.
> Uniform structure across tests ensures predictable, familiar code that team members can navigate efficiently.
> Table-driven and data-driven approaches minimize boilerplate code when adding new test cases, making it simple to expand coverage.
> Scoped traces and detailed assertion messages pinpoint failures quickly during continuous integration and local testing.
The FIRST principles for unit testing focus on creating effective and maintainable tests.
> Unit tests should execute quickly to provide rapid feedback during development and continuous integration.
> Each unit test should be self-contained and not rely on the state or behavior of other tests.
> Unit tests should produce deterministic results every time they are run, regardless of the environment or order of execution.
> Unit tests should have clear pass/fail outcomes without requiring manual inspection.
> Unit tests should be written and executed early in the development process to catch issues as soon as possible.
The In-Got-Want pattern structures each test case into three clear sections.
> Defines the input parameters or conditions for the test.
> Captures the actual output or result produced by the code under test.
> Specifies the expected output or result that the test is verifying against.
Table-driven testing organizes test cases in a tabular format, allowing multiple scenarios to be defined concisely.
> Each row in the table represents a distinct test case with its own set of inputs and expected outputs.
> The test framework iterates over each row, executing the same test logic with different data.
Data-driven testing separates test data from test logic, enabling the same test logic to be executed with multiple sets of input data.
> Test data can be stored in external files (e.g., JSON, CSV) and loaded at runtime.
> The same test logic can be reused with different datasets, enhancing maintainability and coverage.
The AAA pattern structures each test case into three clear phases.
> Set up the necessary preconditions and inputs for the test.
> Execute the function or method being tested.
> Verify that the actual output matches the expected output.
Test fixtures provide a consistent and reusable setup and teardown mechanism for test cases.
> Initialize common objects or state needed for multiple tests.
> Clean up resources or reset state after each test.
Identify new functions in pkg/ or internal/ (e.g., pkg/<package>/<file>.go).
Create new tests in the same package (e.g., pkg/<package>/<file>_test.go).
Include comprehensive edge cases:
Structure all tests using the template pattern.
| Command | Description | | ----------------------- | -------------------------------------------------- | | make go-test-unit | Execute tests with race detection and JUnit report | | make go-test-coverage | Generate coverage reports (HTML and XML) |
> Use the standard Go testing package.
> Include testing and github.com/google/go-cmp/cmp for comparisons.
> Use t.Parallel() to run tests in parallel.
> Consolidate test cases for a single function into one TestXxx(t *testing.T) function using table-driven testing.
This approach:
> Use cmp.Equal for value comparisons and errors.Is for error checking.
Use these templates for new unit tests. Replace placeholders with actual values.
go// SPDX-License-Identifier: Apache-2.0 package <package> import ( "errors" "testing" "github.com/google/go-cmp/cmp" )
gofunc Test<FunctionName>(t *testing.T) { t.Parallel() // In-Got-Want type in struct { /* input fields */ } type want struct { /* expected output fields */ err error } // Table-Driven Testing tests := []struct { name string in in want want }{ { name: "case-description-1", in: in{ /* input values */ }, want: want{ /* expected output */ err: nil, }, }, { name: "case-description-2", in: in{ /* input values */ }, want: want{ /* expected output */ err: nil, // or specific error }, }, // add more cases as needed } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Arrange // additional setup as needed // Act got, err := <Function>(tt.in.<input>) // Assert if !errors.Is(err, tt.want.err) { t.Errorf("<Function>() error = %v, want err %v", err, tt.want.err) } if !cmp.Equal(got, tt.want.<value>) { t.Errorf("<Function>(%+v) = %v, want %v", tt.in, got, tt.want.<value>) } }) } }
go// testFixture holds common test state and provides setup/teardown. type testFixture struct { t *testing.T // Add common fields for test state object *<Type> } // newTestFixture creates and initializes a test fixture. func newTestFixture(t *testing.T) *testFixture { t.Helper() // Setup return &testFixture{ t: t, object: New<Type>(), } } // teardown cleans up resources after test completion. func (f *testFixture) teardown() { f.t.Helper() // Teardown if f.object != nil { f.object.Close() } } func Test<FunctionName>WithFixture(t *testing.T) { t.Parallel() // Arrange f := newTestFixture(t) defer f.teardown() input := <input_value> // Act got, err := f.object.<Function>(input) // Assert if err != nil { t.Errorf("<Function>() unexpected error: %v", err) } if !cmp.Equal(got, <expected>) { t.Errorf("<Function>() = %v, want %v", got, <expected>) } }
gofunc Test<FunctionName>Error(t *testing.T) { t.Parallel() // In-Got-Want type in struct { /* invalid input fields */ } type want struct { err error } // Table-Driven Testing tests := []struct { name string in in want want }{ { name: "nil-input-returns-error", in: in{ /* nil or invalid input */ }, want: want{ err: resource.Err<ErrorName>, }, }, { name: "invalid-value-returns-error", in: in{ /* invalid value */ }, want: want{ err: resource.Err<ErrorName>, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Arrange // setup if needed // Act _, err := <Function>(tt.in.<input>) // Assert if !errors.Is(err, tt.want.err) { t.Errorf("<Function>() error = %v, want err %v", err, tt.want.err) } }) } }
gofunc Test<FunctionName>BoundaryValues(t *testing.T) { t.Parallel() // In-Got-Want type in struct { input <input_type> } type want struct { value <output_type> err error } // Table-Driven Testing tests := []struct { name string in in want want }{ { name: "minimum-value", in: in{input: <MIN_VALUE>}, want: want{value: /* expected */, err: nil}, }, { name: "maximum-value", in: in{input: <MAX_VALUE>}, want: want{value: /* expected */, err: nil}, }, { name: "zero-value", in: in{input: 0}, want: want{value: /* expected */, err: nil}, }, { name: "negative-value", in: in{input: -1}, want: want{value: /* expected */, err: nil}, }, { name: "overflow-value", in: in{input: math.MaxFloat64}, want: want{value: 0, err: resource.ErrOverflow}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Arrange // setup if needed // Act got, err := <Function>(tt.in.input) // Assert if !errors.Is(err, tt.want.err) { t.Errorf("<Function>() error = %v, want err %v", err, tt.want.err) } if !cmp.Equal(got, tt.want.value) { t.Errorf("<Function>(%v) = %v, want %v", tt.in.input, got, tt.want.value) } }) } }
goimport ( "encoding/json" "os" "path/filepath" "testing" "github.com/google/go-cmp/cmp" ) // testCase represents a single test case loaded from JSON. type testCase struct { Name string `json:"name"` In struct { Input <input_type> `json:"input"` } `json:"in"` Want struct { Expected <output_type> `json:"expected"` } `json:"want"` } // testData represents the JSON test data structure. type testData struct { Tests []testCase `json:"tests"` } func Test<FunctionName>DataDriven(t *testing.T) { t.Parallel() // Load test data from JSON file testdataPath := filepath.Join("testdata", "<function>_test.json") data, err := os.ReadFile(testdataPath) if err != nil { t.Fatalf("failed to read test data: %v", err) } var td testData if err := json.Unmarshal(data, &td); err != nil { t.Fatalf("failed to parse test data: %v", err) } for _, tc := range td.Tests { t.Run(tc.Name, func(t *testing.T) { // Arrange input := tc.In.Input expected := tc.Want.Expected // Act got, err := <Function>(input) // Assert if err != nil { t.Errorf("<Function>() unexpected error: %v", err) } if !cmp.Equal(got, expected) { t.Errorf("<Function>(%v) = %v, want %v", input, got, expected) } }) } }
tests/data/<function>_test.json> JSON file containing test cases.
json { "tests": [ { "name": "case-description-1", "in": { "input": <value> }, "want": { "expected": <value> } }, { "name": "case-description-2", "in": { "input": <value> }, "want": { "expected": <value> } } ] }
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 28,254 | 10,640 | -62% | 1 | 1 | 0% | 3,506 | 6,306 | +80% | 0 | 0 | — |
case-02 | fail→pass | 15,099 | 11,926 | -21% | 1 | 1 | 0% | 3,030 | 6,679 | +120% | 0 | 0 | — |
case-03 | fail→pass | 13,700 | 8,116 | -41% | 1 | 1 | 0% | 2,756 | 5,569 | +102% | 0 | 0 | — |
case-04 | fail→pass | 6,774 | 7,605 | +12% | 1 | 1 | 0% | 1,362 | 5,434 | +299% | 0 | 0 | — |
case-05 | pass→pass | 11,386 | 5,397 | -53% | 1 | 1 | 0% | 1,867 | 4,938 | +164% | 0 | 0 | — |
case-06 | fail→pass | 13,249 | 8,803 | -34% | 1 | 1 | 0% | 2,203 | 5,604 | +154% | 0 | 0 | — |
case-07 | pass→pass | 13,281 | 9,630 | -27% | 1 | 1 | 0% | 2,788 | 5,840 | +109% | 0 | 0 | — |
case-08 | fail→pass | 5,923 | 2,381 | -60% | 1 | 1 | 0% | 1,117 | 4,206 | +277% | 0 | 0 | — |
case-09 | fail→pass | 14,599 | 10,833 | -26% | 1 | 1 | 0% | 2,960 | 6,103 | +106% | 0 | 0 | — |
case-10 | pass→pass | 18,065 | 9,047 | -50% | 1 | 1 | 0% | 3,038 | 6,102 | +101% | 0 | 0 | — |
case-11 | fail→pass | 16,119 | 11,498 | -29% | 1 | 1 | 0% | 3,200 | 6,191 | +93% | 0 | 0 | — |
case-12 | pass→pass | 10,576 | 7,133 | -33% | 1 | 1 | 0% | 2,219 | 5,258 | +137% | 0 | 0 | — |
case-13 | fail→pass | 11,820 | 1,948 | -84% | 1 | 1 | 0% | 1,646 | 4,068 | +147% | 0 | 0 | — |
case-14 | pass→pass | 12,668 | 10,790 | -15% | 1 | 1 | 0% | 2,460 | 5,968 | +143% | 0 | 0 | — |
case-15 | pass→pass | 6,851 | 2,778 | -59% | 1 | 1 | 0% | 1,208 | 4,235 | +251% | 0 | 0 | — |
case-16 | pass→pass | 10,647 | 5,267 | -51% | 1 | 1 | 0% | 2,067 | 4,810 | +133% | 0 | 0 | — |
case-17 | fail→pass | 8,433 | 6,156 | -27% | 1 | 1 | 0% | 1,745 | 4,952 | +184% | 0 | 0 | — |
case-18 | fail→pass | 9,414 | 5,807 | -38% | 1 | 1 | 0% | 1,709 | 4,781 | +180% | 0 | 0 | — |
case-19 | fail→fail | 19,103 | 16,357 | -14% | 1 | 1 | 0% | 3,986 | 7,452 | +87% | 0 | 0 | — |
case-20 | pass→pass | 13,077 | 10,548 | -19% | 1 | 1 | 0% | 2,745 | 5,935 | +116% | 0 | 0 | — |
case-21 | fail→pass | 11,575 | 11,933 | +3% | 1 | 1 | 0% | 2,310 | 6,341 | +175% | 0 | 0 | — |
case-22 | pass→pass | 17,362 | 14,423 | -17% | 1 | 1 | 0% | 3,595 | 6,796 | +89% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +55 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.