mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
#34384: Add TestComments page
parent
6678e1f3ff
commit
dc963e7b78
267
TestComments.md
Normal file
267
TestComments.md
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
## Assert Libraries
|
||||||
|
|
||||||
|
Avoid the use of 'assert' libraries to help your tests. Go developers arriving
|
||||||
|
from xUnit frameworks often want to write code like:
|
||||||
|
|
||||||
|
```go
|
||||||
|
assert.isNotNil(t, "obj", obj)
|
||||||
|
assert.stringEq(t, "obj.Type", obj.Type, "blogPost")
|
||||||
|
assert.intEq(t, "obj.Comments", obj.Comments, 2)
|
||||||
|
assert.stringNotEq(t, "obj.Body", obj.Body, "")
|
||||||
|
```
|
||||||
|
|
||||||
|
but this either stops the test early (if assert calls `t.Fatalf` or `panic`)
|
||||||
|
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:
|
||||||
|
|
||||||
|
```go
|
||||||
|
if obj == nil || obj.Type != "blogPost" || obj.Comments != 2 || obj.Body == "" {
|
||||||
|
t.Errorf("AddPost() = %+v", obj)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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](#identify-the-input), 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](#print-diffs) or [deep comparisons](#equality-comparison-and-diffs).
|
||||||
|
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 a
|
||||||
|
[`cmp.Diff`](https://godoc.org/github.com/google/go-cmp/cmp#Diff) or
|
||||||
|
[`cmp.Equal`](https://godoc.org/github.com/google/go-cmp/cmp#Equal) comparison
|
||||||
|
with [cmpopts](https://godoc.org/github.com/google/go-cmp/cmp/cmpopts) options
|
||||||
|
such as [`cmpopts.IgnoreInterfaces`](https://godoc.org/github.com/google/go-cmp/cmp/cmpopts#IgnoreInterfaces)
|
||||||
|
may meet your needs ([example](https://play.golang.org/p/vrCUNVfxsvF));
|
||||||
|
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`](https://golang.org/pkg/encoding/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 the `json` 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 the
|
||||||
|
[language-defined comparisons](http://golang.org/ref/spec#Comparison_operators).
|
||||||
|
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](https://godoc.org/github.com/google/go-cmp/cmp) package. Use
|
||||||
|
[`cmp.Equal`](https://godoc.org/github.com/google/go-cmp/cmp#Equal) for equality
|
||||||
|
comparison and [`cmp.Diff`](https://godoc.org/github.com/google/go-cmp/cmp#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. Prefer `cmp` for new code, and consider updating
|
||||||
|
older code to use `cmp` 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 the `cmp.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](#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:
|
||||||
|
|
||||||
|
```go
|
||||||
|
t.Errorf("YourFunc(%v) = %v, want %v", in, got, want)
|
||||||
|
```
|
||||||
|
|
||||||
|
and not:
|
||||||
|
|
||||||
|
```go
|
||||||
|
t.Errorf("got %v, want %v", got, want)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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` over `t.Fatal`. When comparing
|
||||||
|
several different properties of a function's output, use `t.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:
|
||||||
|
|
||||||
|
* If you're not using `t.Run` subtests, you should use `t.Error` followed by a
|
||||||
|
`continue` statement to move on to the next table entry.
|
||||||
|
* If you're using subtests (and you're inside a call to `t.Run`), then
|
||||||
|
`t.Fatal` ends the current subtest and allows your test case to progress to
|
||||||
|
the next subtest, so use `t.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`, call
|
||||||
|
[`t.Helper`](https://godoc.org/testing#T.Helper) to attribute failures in the
|
||||||
|
test helper to the line where the helper is called.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func TestSomeFunction(t *testing.T) {
|
||||||
|
golden := readFile(t, "testdata/golden.txt")
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFile(t *testing.T, filename string) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
contents, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(contents)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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 the `cmp` 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](https://github.com/golang/go/wiki/TableDrivenTests) 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]](https://github.com/golang/go/wiki/TableDrivenTests#example-of-a-table-driven-test),
|
||||||
|
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](https://testing.googleblog.com/2015/01/testing-on-toilet-change-detector-tests.html),
|
||||||
|
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.
|
Loading…
x
Reference in New Issue
Block a user