-
Notifications
You must be signed in to change notification settings - Fork 18k
wiki: TestComments #34384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Labels
Documentation
Issues describing a change to documentation.
FrozenDueToAge
help wanted
NeedsFix
The path to resolution is known, but the work has not been done.
Milestone
Comments
CC @golang/osp-team |
SGTM. Go for it. |
This can be closed? |
Yep, sorry. |
@slewiskelly The CodeReviewComments page has a header at the top which includes, in part:
Should the TestComments page have a similar warning? |
Yes. |
Thanks @cespare. Done. |
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Labels
Documentation
Issues describing a change to documentation.
FrozenDueToAge
help wanted
NeedsFix
The path to resolution is known, but the work has not been done.
Propose adding a page similar to CodeReviewComments, but it for testing only. The sections that follow provide advice given during Go readability reviews at Google.
Assert Libraries
Avoid the use of 'assert' libraries to help your tests. Go developers arriving from xUnit frameworks often want to write code like:
but this either stops the test early (if assert calls
t.Fatalf
orpanic
) or omits interesting information about what the test got right. It also forces the assert package to create a whole new sub-language instead of reusing the existing programming language (Go itself). Go has good support for printing structures, so a better way to write this code is:Assert libraries make it too easy to write imprecise tests and inevitably end up duplicating features already in the language, like expression evaluation, comparisons, sometimes even more. Strive to write tests that are precise both about what went wrong and what went right, and make use of Go itself instead of creating a mini-language inside Go.
Choose Human-Readable Subtest Names
When you use
t.Run
to create a subtest, the first argument is used as a descriptive name for the test. To ensure that test results are legible to humans reading the logs, choose subtest names that will remain useful and readable after escaping. (The test runnner replaces spaces with underscores, and it escapes non-printing characters).To identify the inputs, use
t.Log
in the body of the subtest or include them in the test's failure messages, where they won't be escaped by the test runner.Compare Full Structures
If your function returns a struct, don't write test code that performs an individual comparison for each field of the struct. Instead, construct the struct that you're expecting your function to return, and compare in one shot using diffs or deep comparisons. The same rule applies to arrays and maps.
If your struct needs to be compared for approximate equality or some other kind of semantic equality, or it contains fields that cannot be compared for equality (e.g. if one of the fields is an
io.Reader
), tweaking acmp.Diff
orcmp.Equal
comparison with cmpopts options such ascmpopts.IgnoreInterfaces
may meet your needs (example); otherwise, this technique just won't work, so do whatever works.If your function returns multiple return values, you don't need to wrap those in a struct before comparing them. Just compare the return values individually and print them.
Compare Stable Results
Avoid comparing results that may inherently depend on output stability of some external package that you do not control. Instead, the test should compare on semantically relevant information that is stable and resistent to changes in your dependencies. For functionality that returns a formatted string or serialized bytes, it is generally not safe to assume that the output is stable.
For example,
json.Marshal
makes no guarantee about the exact bytes that it may emit. It has the freedom to change (and has changed in the past) the output. Tests that perform string equality on the exact JSON string may break if thejson
package changes how it serializes the bytes. Instead, a more robust test would parse the contents of the JSON string and ensure that it is semantically equivalent to some expected data structure.Equality Comparison and Diffs
The
==
operator evaluates equality using thelanguage-defined comparisons. Values it can compare include numeric, string, and pointer values and structs with fields of those values. In particular, it determines two pointers to be equal only if they point to the same variable.
Use the cmp package. Use
cmp.Equal
for equality comparison andcmp.Diff
to obtain a human-readable diff between objects.Although the
cmp
package is not part of the Go standard library, it is maintained by the Go team and should produce stable results across Go version updates. It is user-configurable and should serve most comparison needs.You will find older code using the standard
reflect.DeepEqual
function to compare complex structures. Prefercmp
for new code, and consider updating older code to usecmp
where practical.reflect.DeepEqual
is sensitive to changes in unexported fields and other implementation details.NOTE: The
cmp
package can also be used with protocol buffer messages, by including thecmp.Comparer(proto.Equal)
option when comparing protocol buffer messages.Got before Want
Test outputs should output the actual value that the function returned before printing the value that was expected. A usual format for printing test outputs is "
YourFunc(%v) = %v, want %v
".For diffs, directionality is less apparent, and such it is important to include a key to aid in interpreting the failure. See Print Diffs.
Whichever order you use in your failure messages, you should explicitly indicate the ordering as a part of the failure message, because existing code is inconsistent about the ordering.
Identify the Function
In most tests, failure messages should include the name of the function that failed, even though it seems obvious from the name of the test function.
Prefer:
and not:
Identify the Input
In most tests, your test failure messages should include the function inputs if they are short. If the relevant properties of the inputs are not obvious (for example, because the inputs are large or opaque), you should name your test cases with a description of what's being tested, and print the description as part of your error message.
Do not use the index of the test in the test table as a substitute for naming your tests or printing the inputs. Nobody wants to go through your test table and count the entries in order to figure out which test case is failing.
Keep Going
Even after your test cases encounter a failure, they should keep going for as long as possible in order to print out all of the failed checks in a single run. This way, someone who's fixing the failing test doesn't have to play whac-a-mole, fixing one bug and then re-running the test to find the next bug.
On a practical level, prefer calling
t.Error
overt.Fatal
. When comparing several different properties of a function's output, uset.Error
for each of those comparisions.t.Fatal
is usually only appropriate when some piece of test setup fails, without which you cannot run the test at all. In a table-driven test,t.Fatal
is appropriate for failures that set up the whole test function before the test loop. Failures that affect a single entry in the test table, which make it impossible to continue with that entry, should be reported as follows:t.Run
subtests, you should uset.Error
followed by acontinue
statement to move on to the next table entry.t.Run
), thent.Fatal
ends the current subtest and allows your test case to progress to the next subtest, so uset.Fatal
.Mark Test Helpers
A test helper is a function that performs a setup or teardown task, such as constructing an input message, that does not depend on the code under test.
If you pass a
*testing.T
, callt.Helper
to attribute failures in the test helper to the line where the helper is called.Do not use this pattern when it obscures the connection between a test failure and the conditions that led to it. Specifically,
t.Helper
should not be used to implement assert libraries.Print Diffs
If your function returns large output then it can be hard for someone reading the failure message to find the differences when your test fails. Instead of printing both the returned value and the wanted value, make a diff.
Add some text to your failure message explaining the direction of the diff.
Something like "
diff -want +got
" is good when you're using thecmp
package (if you pass(want, got)
to the function), because the-
and+
that you add to your format string will match the+
and-
that actually appear at the beginning of the diff lines.The diff will span multiple lines, so you should print a newline before you print the diff.
Table-Driven Tests vs Multiple Test Functions
Table-driven tests should be used whenever many different test cases can be tested using similar testing logic, for example when testing whether the actual output of a function is equal to the expected output [example], or when testing whether the outputs of a function always conform to the same set of invariants.
When some test cases need to be checked using different logic from other test cases, it is more appropriate to write multiple test functions. The logic of your test code can get difficult to understand when every entry in a table has to be subjected to multiple kinds of conditional logic to do the right kind of output check for the right kind of input. If they have different logic but identical setup, a sequence of subtests within a single test function might also make sense.
You can combine table-driven tests with multiple test functions. For example, if you're testing that a function's non-error output exactly matches the expected output, and you're also testing that the function returns some non-nil error when it gets invalid input, then the clearest unit tests can be achieved by writing two separate table-driven test functions — one for normal non-error outputs, and one for error outputs.
Test Error Semantics
When a unit test performs string comparisons or uses
reflect.DeepEqual
to check that particular kinds of errors are returned for particular inputs, you may find that your tests are fragile if you have to reword any of those error messages in the future. Since this has the potential to turn your unit test into a change detector, don't use string comparison to check what type of error your function returns.It's OK to use string comparisons to check that error messages coming from the package under test satisfy some property, for example, that it includes the parameter name.
If you care about testing the exact type of error that your functions return, you should separate the error string intended for human eyes from the structure that is exposed for programmatic use. In this case, you should avoid using
fmt.Errorf
, which tends to destroy semantic error information.Many people who write APIs don't care exactly what kinds of errors their API returns for different inputs. If your API is like this, then it is sufficient to create error messages using
fmt.Errorf
, and then in the unit test, test only whether the error was non-nil when you expected an error.The text was updated successfully, but these errors were encountered: