{"id":19168421,"url":"https://github.com/bloomberg/go-testgroup","last_synced_at":"2025-07-29T19:35:30.104Z","repository":{"id":57534232,"uuid":"281220082","full_name":"bloomberg/go-testgroup","owner":"bloomberg","description":"Helps you organize tests in Go programs into groups.","archived":false,"fork":false,"pushed_at":"2023-12-12T21:41:26.000Z","size":77,"stargazers_count":22,"open_issues_count":1,"forks_count":3,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-19T23:31:59.125Z","etag":null,"topics":["go","golang","testing","testing-tools"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bloomberg.png","metadata":{"files":{"readme":"README.markdown","changelog":"CHANGELOG.markdown","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-07-20T20:33:49.000Z","updated_at":"2025-03-24T16:09:01.000Z","dependencies_parsed_at":"2023-12-12T22:47:55.336Z","dependency_job_id":null,"html_url":"https://github.com/bloomberg/go-testgroup","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bloomberg%2Fgo-testgroup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bloomberg%2Fgo-testgroup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bloomberg%2Fgo-testgroup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bloomberg%2Fgo-testgroup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bloomberg","download_url":"https://codeload.github.com/bloomberg/go-testgroup/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252897385,"owners_count":21821431,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","testing","testing-tools"],"created_at":"2024-11-09T09:42:37.313Z","updated_at":"2025-05-07T14:41:35.606Z","avatar_url":"https://github.com/bloomberg.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# testgroup\n\n[![PkgGoDev][pkg-go-dev-badge]][pkg-go-dev-page]\n[![Workflow:Main][workflow-main-badge]][workflow-main-page]\n[![Coverage][coveralls-main-badge]][coveralls-main-page]\n[![Go Report Card][go-report-card-badge]][go-report-card-page]\n\n[coveralls-main-badge]:\n  https://coveralls.io/repos/github/bloomberg/go-testgroup/badge.svg?branch=main\n[coveralls-main-page]:\n  https://coveralls.io/github/bloomberg/go-testgroup?branch=main\n  \"Coverage for main branch on Coveralls\"\n[go-report-card-badge]:\n  https://goreportcard.com/badge/github.com/bloomberg/go-testgroup\n[go-report-card-page]:\n  https://goreportcard.com/report/github.com/bloomberg/go-testgroup\n  \"Go Report Card\"\n[pkg-go-dev-badge]: https://pkg.go.dev/badge/github.com/bloomberg/go-testgroup\n[pkg-go-dev-page]:\n  https://pkg.go.dev/github.com/bloomberg/go-testgroup\n  \"Reference Documentation on pkg.go.dev\"\n[workflow-main-badge]:\n  https://github.com/bloomberg/go-testgroup/workflows/Main/badge.svg\n[workflow-main-page]:\n  https://github.com/bloomberg/go-testgroup/actions?query=workflow%3AMain\n  \"Main Github Workflow\"\n\n`testgroup` helps you organize tests into groups. A test group is a `struct` (or\nother type) whose exported methods are its subtests. The subtests can share\ndata, helper functions, and pre/post-group and pre/post-test hooks.\n\n`testgroup` was inspired by\n[`github.com/stretchr/testify/suite`](https://pkg.go.dev/github.com/stretchr/testify/suite).\n\n## Contents\n\n- [Features](#features)\n- [Example](#example)\n- [Documentation](#documentation)\n  - [Motivation (\"Why not `testify/suite`?\")](#motivation-why-not-testifysuite)\n  - [Writing test groups](#writing-test-groups)\n    - [Pre/post-group and pre/post-test hooks (optional)](#prepost-group-and-prepost-test-hooks-optional)\n  - [Running test groups](#running-test-groups)\n    - [Serially](#serially)\n    - [In parallel](#in-parallel)\n  - [Using `testgroup.T`](#using-testgroupt)\n    - [Running subtests](#running-subtests)\n    - [Running subgroups](#running-subgroups)\n    - [Using `testing.T`](#using-testingt)\n    - [Asserting with `testify/assert` and `testify/require`](#asserting-with-testifyassert-and-testifyrequire)\n- [Code of Conduct](#code-of-conduct)\n- [Contributing](#contributing)\n- [License](#license)\n- [Security Policy](#security-policy)\n\n## Features\n\n- Support for parallel execution of tests, including waiting for parallel tests\n  to finish\n- Easy access to assertion helpers\n- Pre/post-group and pre/post-test hooks\n\n## Example\n\nHere's a simple test group from\n[`example_absint_test.go`](example_absint_test.go):\n\n```go\npackage testgroup_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/bloomberg/go-testgroup\"\n)\n\nfunc AbsInt(n int) int {\n\tif n \u003c 0 {\n\t\treturn -n\n\t}\n\treturn n\n}\n\n// This function is the entry point to the test group.\n// Because it starts with \"Test\" and accepts a *testing.T argument,\n// it is detected and called by the Go testing package when you run \"go test\".\nfunc TestAbsInt(t *testing.T) {\n\ttestgroup.RunSerially(t, \u0026AbsIntTests{})\n}\n\ntype AbsIntTests struct {\n\t// Group-wide data/state can be stored here.\n}\n\nfunc (grp *AbsIntTests) DoesNotChangeNonNegativeNumbers(t *testgroup.T) {\n\tt.Equal(0, AbsInt(0))\n\tt.Equal(1, AbsInt(1))\n\tt.Equal(123456789, AbsInt(123456789))\n}\n\nfunc (grp *AbsIntTests) MakesNegativeNumbersPositive(t *testgroup.T) {\n\tt.Equal(1, AbsInt(-1))\n\tt.Equal(123456789, AbsInt(-123456789))\n}\n```\n\nWhen you run `go test`, you'll see something like this:\n\n```console\n$ go test -v example_absint_test.go\n=== RUN   TestAbsInt\n=== RUN   TestAbsInt/DoesNotChangeNonNegativeNumbers\n=== RUN   TestAbsInt/MakesNegativeNumbersPositive\n--- PASS: TestAbsInt (0.00s)\n    --- PASS: TestAbsInt/DoesNotChangeNonNegativeNumbers (0.00s)\n    --- PASS: TestAbsInt/MakesNegativeNumbersPositive (0.00s)\nPASS\nok  \tcommand-line-arguments\t0.014s\n```\n\n## Documentation\n\n### Motivation (\"Why not `testify/suite`?\")\n\n`testgroup` was inspired by [testify][]'s [suite][testify-suite-docs] package.\nWe really like `testify/suite`, but we had trouble getting subtests to run in\nparallel. Testify stores each test's `testing.T` inside the `Suite` struct,\nwhich means that only one `testing.T` is available at a given time. If you run\ntests in parallel, failures can be reported as another test failing, and if a\ntest failure is reported twice, `testing` panics.\n\nAfter separating the group-wide state from the test-specific state, we also made\na few usability improvements to the `testing.T`-like object.\n\n[testify]: https://github.com/stretchr/testify\n[testify-suite-docs]: https://pkg.go.dev/github.com/stretchr/testify/suite\n\n### Writing test groups\n\nA test group is a `struct` (or other type) whose exported methods are its\nsubtests.\n\n```go\ntype MyGroup struct{}\n\nfunc (*MyGroup) Subtest(t *testgroup.T) {\n\t// ...\n}\n```\n\n`testgroup` considers _all_ of the type's exported methods to be subtests.\n(Unlike `testing`-style tests or `testify/suite` subtests, you don't have to\nstart `testgroup` subtests with the prefix `Test`.)\n\nA valid subtest accepts a `*testgroup.T` as its only argument and does not\nreturn anything. If a subtest (exported method) has a different signature,\n`testgroup` will fail the parent test to avoid accidentally skipping malformed\ntests.\n\n#### Pre/post-group and pre/post-test hooks (optional)\n\n`testgroup` considers a few names to be hook methods that have special behavior.\nYou may find them useful to help clarify your code and avoid some repetition.\n\n`testgroup` supports the following hooks:\n\n```go\nfunc (*MyGroup) PreGroup(t *testgroup.T)  {\n\t// code that will run before any of MyGroup's subtests have started\n}\n\nfunc (*MyGroup) PostGroup(t *testgroup.T) {\n\t// code that will run after all of MyGroup's subtests have finished\n}\n\nfunc (*MyGroup) PreTest(t *testgroup.T)  {\n\t// code that will run at the beginning of each subtest in MyGroup\n}\n\nfunc (*MyGroup) PostTest(t *testgroup.T) {\n\t// code that will run at the end of each subtest in MyGroup\n}\n```\n\nLike subtests, these methods accept a single `*testgroup.T` argument.\n\nIf you skip a test by calling `t.Skip()`, the `PreTest` and `PostTest` hook\nfunctions will still run before and after that test.\n\n### Running test groups\n\nHere's an example of a top-level `testing`-style test running the subtests in a\ntest group:\n\n```go\nfunc TestMyGroup(t *testing.T) {\n\ttestgroup.RunSerially(t, \u0026MyGroup{})\n}\n```\n\n`testgroup` has two ways to run subtests: `RunSerially` and `RunInParallel`.\n\n#### Serially\n\n`RunSerially` runs a group's subtests in lexicographical order, one after\nanother.\n\nHere's a contrived example:\n\n```go\nfunc TestSerial(t *testing.T) {\n\ttestgroup.RunSerially(t, \u0026MyGroup{})\n}\n\ntype MyGroup struct{}\n\nfunc (*MyGroup) C(t *testgroup.T) {}\nfunc (*MyGroup) A(t *testgroup.T) {}\nfunc (*MyGroup) B(t *testgroup.T) {}\n```\n\nRunning the test above gives this output:\n\n```console\n$ go test -v serial_test.go\n=== RUN   TestSerial\n=== RUN   TestSerial/A\n=== RUN   TestSerial/B\n=== RUN   TestSerial/C\n--- PASS: TestSerial (0.00s)\n    --- PASS: TestSerial/A (0.00s)\n    --- PASS: TestSerial/B (0.00s)\n    --- PASS: TestSerial/C (0.00s)\nPASS\nok  \tcommand-line-arguments\t0.014s\n```\n\n#### In parallel\n\n`RunInParallel` runs a group's subtests in parallel.\n\nWhen using this mode, **you must not call `t.Parallel()` inside your tests**\n\u0026ndash; `testgroup` does this for you.\n\nIn order to make sure [hooks](#prepost-group-and-prepost-test-hooks) run at the\ncorrect time, `RunInParallel` wraps a parent test around your subtests. By\ndefault, the parent test is named `_`, but you can override this by setting\n`RunInParallelParentTestName`.\n\nThe execution order of subtests and hooks looks like this:\n\n1. Run `PreGroup`.\n2. In parallel, run the following sequence of steps for each subtest:\n   1. Run `PreTest`.\n   2. Run the subtest method.\n   3. Run `PostTest`.\n3. After all subtests finish, run `PostGroup`.\n\nHere's another contrived example:\n\n```go\nfunc TestParallel(t *testing.T) {\n\ttestgroup.RunInParallel(t, \u0026MyGroup{})\n}\n\ntype MyGroup struct{}\n\nfunc (*MyGroup) C(t *testgroup.T) {}\nfunc (*MyGroup) A(t *testgroup.T) {}\nfunc (*MyGroup) B(t *testgroup.T) {}\n```\n\nRunning the test above gives this output:\n\n```console\n$ go test -v parallel_test.go\n=== RUN   TestParallel\n=== RUN   TestParallel/_\n=== RUN   TestParallel/_/A\n=== PAUSE TestParallel/_/A\n=== RUN   TestParallel/_/B\n=== PAUSE TestParallel/_/B\n=== RUN   TestParallel/_/C\n=== PAUSE TestParallel/_/C\n=== CONT  TestParallel/_/A\n=== CONT  TestParallel/_/C\n=== CONT  TestParallel/_/B\n--- PASS: TestParallel (0.00s)\n    --- PASS: TestParallel/_ (0.00s)\n        --- PASS: TestParallel/_/B (0.00s)\n        --- PASS: TestParallel/_/C (0.00s)\n        --- PASS: TestParallel/_/A (0.00s)\nPASS\nok  \tcommand-line-arguments\t0.014s\n```\n\n### Using `testgroup.T`\n\n`testgroup.T` is a type passed to each test function. It is mainly concerned\nwith test state, and it embeds and contains other types for convenience.\n\n#### Running subtests\n\n`testgroup.T.Run` is just like `testing.T.Run`, but its test function has a\n`*testgroup.T` argument for convenience.\n\n```go\nfunc (*MyGroup) MySubtest(t *testgroup.T) {\n\t// set up a table of testcases\n\ttype testcase struct{\n\t\tinput, output int\n\t}\n\ttable := []testcase{\n\t\t// ...\n\t}\n\n\tfor _, tc := range table {\n\t\ttc := tc // local copy to pin range variable\n\t\tt.Run(fmt.Sprintf(\"%d\", tc.input), func (t *testgroup.T) {\n\t\t\tt.Equal(tc.output, someCalculation(tc.input))\n\t\t})\n\t}\n}\n```\n\n#### Running subgroups\n\n`testgroup.T` has a few convenience methods that simply wrap the package-level\nfunctions.\n\n- `testgroup.T.RunSerially` calls `testgroup.RunSerially`.\n- `testgroup.T.RunInParallel` calls `testgroup.RunInParallel`.\n\n#### Using `testing.T`\n\n`testgroup.T` embeds a `*testing.T`, which lets you write\n\n```go\nfunc (*MyGroup) MySubtest(t *testgroup.T) {\n\tif testing.Short() {\n\t\tt.Skip(\"skipping due to short mode\")\n\t}\n\n\tt.Logf(\"Have we failed yet? %v\", t.Failed())\n}\n```\n\n#### Asserting with `testify/assert` and `testify/require`\n\nSimilar to `testify/suite`, `testgroup.T` embeds a\n[`testify/assert`][testify-assert-docs] `*Assertions`, so you can call its\nmember functions directly from a `testgroup.T`:\n\n```go\nfunc (*MyGroup) MySubtest(t *testgroup.T) {\n\tconst expectedValue = 42\n\tresult := callSomeFunction(input)\n\tt.Equal(expectedValue, result)\n}\n```\n\n`testgroup.T` also contains a [`testify/require`][testify-require-docs]\n`*Assertions` named `Require`. You can use it to fail your test immediately\ninstead of continuing.\n\n```go\nfunc (*MyGroup) MySubtest(t *testgroup.T) {\n\tconst expectedValue = 42\n\tresult, err := somethingThatMightError(input)\n\tt.NoError(err)                  // testify/assert assertion -- continues test execution if it fails\n\tt.Require.NotNil(result)        // testify/require assertion -- stops test execution if it fails\n\tt.Equal(expectedValue, result)\n}\n```\n\n[testify-assert-docs]: https://pkg.go.dev/github.com/stretchr/testify/assert\n[testify-require-docs]: https://pkg.go.dev/github.com/stretchr/testify/require\n\n## Code of Conduct\n\n`testgroup` has adopted a\n[Code of Conduct](https://github.com/bloomberg/.github/blob/master/CODE_OF_CONDUCT.md).\nIf you have any concerns about the Code or behavior which you have experienced\nin the project, please contact us at opensource@bloomberg.net.\n\n## Contributing\n\nWe'd love to hear from you, whether you've found a bug or want to suggest how\n`testgroup` could be better. Please\n[open an issue](https://github.com/bloomberg/go-testgroup/issues/new/choose) and\nlet us know what you think!\n\nIf you want to contribute code to `testgroup`, please be sure to read our\n[contribution guidelines](https://github.com/bloomberg/.github/blob/master/CONTRIBUTING.md).\n**We highly recommend opening an issue before you start working on your pull\nrequest.** We'd like to talk with you about the change you want to make _before_\nyou start making it. :smile:\n\n## License\n\n`testgroup` is licensed under the [Apache License, Version 2.0](LICENSE).\n\n## Security Policy\n\nIf you believe you have identified a security vulnerability in this project,\nplease send an email to the project team at opensource@bloomberg.net detailing\nthe suspected issue and any methods you've found to reproduce it.\n\nPlease do _not_ open an issue in the GitHub repository, as we'd prefer to keep\nvulnerability reports private until we've had an opportunity to review and\naddress them. Thank you.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbloomberg%2Fgo-testgroup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbloomberg%2Fgo-testgroup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbloomberg%2Fgo-testgroup/lists"}