Merge lp:~thumper/juju-core/testing-docs into lp:~go-bot/juju-core/trunk

Proposed by Tim Penhey
Status: Merged
Approved by: Tim Penhey
Approved revision: no longer in the source branch.
Merged at revision: 2711
Proposed branch: lp:~thumper/juju-core/testing-docs
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 308 lines (+304/-0)
1 file modified
doc/how-to-write-tests.txt (+304/-0)
To merge this branch: bzr merge lp:~thumper/juju-core/testing-docs
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+218732@code.launchpad.net

Commit message

Add some developer docs for writing tests.

Obviously there is a lot that could be written, but this
is a start.

https://codereview.appspot.com/96110043/

Description of the change

Add some developer docs for writing tests.

Obviously there is a lot that could be written, but this
is a start.

https://codereview.appspot.com/96110043/

To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote :

Reviewers: mp+218732_code.launchpad.net,

Message:
Please take a look.

Description:
Add some developer docs for writing tests.

Obviously there is a lot that could be written, but this
is a start.

https://code.launchpad.net/~thumper/juju-core/testing-docs/+merge/218732

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/96110043/

Affected files (+234, -0 lines):
   A [revision details]
   A doc/how-to-write-tests.txt

Revision history for this message
Ian Booth (wallyworld) wrote :

A great start.
I'd also mention github.com/juju/testing/checkers
LGTM. We should get this in and iterate on it.

https://codereview.appspot.com/96110043/

Revision history for this message
Menno Finlay-Smits (menno.smits) wrote :

Minor fixes

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt
File doc/how-to-write-tests.txt (right):

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt#newcode165
doc/how-to-write-tests.txt:165: To create your new test suite composing
one of the following, you can
Clearer as:

To create a new suite composed of one or more of the suites above, you
can do something like:

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt#newcode207
doc/how-to-write-tests.txt:207: in the compisition tree, there are a few
very helpful functions.
composition

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt#newcode223
doc/how-to-write-tests.txt:223: PatchValue works with any matching type.
This includes function variables.
Is it possible to patch methods? I'm guessing it isn't, but if it is
could you add an example here?

https://codereview.appspot.com/96110043/

Revision history for this message
Tim Penhey (thumper) wrote :

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt
File doc/how-to-write-tests.txt (right):

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt#newcode165
doc/how-to-write-tests.txt:165: To create your new test suite composing
one of the following, you can
On 2014/05/08 04:38:58, menn0 wrote:
> Clearer as:

> To create a new suite composed of one or more of the suites above, you
can do
> something like:

Done.

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt#newcode207
doc/how-to-write-tests.txt:207: in the compisition tree, there are a few
very helpful functions.
On 2014/05/08 04:38:58, menn0 wrote:
> composition

Done.

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt#newcode223
doc/how-to-write-tests.txt:223: PatchValue works with any matching type.
This includes function variables.
On 2014/05/08 04:38:58, menn0 wrote:
> Is it possible to patch methods? I'm guessing it isn't, but if it is
could you
> add an example here?

No, you can't patch methods. You do that by using interfaces, and
mocking them out.

https://codereview.appspot.com/96110043/

Revision history for this message
William Reade (fwereade) wrote :

This is awesome. Not all my comments are necessarily directly
actionable, in that they're complaints about our current testing, but
perhaps you can find some way to work their intent in usefully.

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt
File doc/how-to-write-tests.txt (right):

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt#newcode119
doc/how-to-write-tests.txt:119: // a pointer to that variable so we can
patch it in the tests.
formatting

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt#newcode151
doc/how-to-write-tests.txt:151: github.com/juju/testing, which brings in
the CleanupSute from the same.
s/Sute/Suite/

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt#newcode159
doc/how-to-write-tests.txt:159: JUJU_ENV, and JUJU_LOGGING_CONFIG
environment variables.
The FakeHomeSuite should generally not be used, because it assumes a
context of a running (cmd/juju) client; almost all the code should avoid
making this assumption, because it's actually generally going to run on
an API server.

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt#newcode162
doc/how-to-write-tests.txt:162: api server.
The JujuConnSuite is all fucked up, and should be used when you just
don't care about having coherent context available, you just kinda want
everything and don't want to think too hard. State server? Client? Both?
Whooo! Who cares!

Yeah, we should fix this; but in the meantime I want people to know it's
at least somewhat bad/wrong, because then *maybe* someone will take it
upon themselves to extract a saner subset of the functionality and start
using that; as it is it's all too easy to just allow the rot to gently
spread without even knowing that's what you're doing.

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt#newcode215
doc/how-to-write-tests.txt:215: // of the test.
Really, FWIW, I think we should always be clearing the env at the very
start of a test.

https://codereview.appspot.com/96110043/

Revision history for this message
Tim Penhey (thumper) wrote :

Please take a look.

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt
File doc/how-to-write-tests.txt (right):

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt#newcode119
doc/how-to-write-tests.txt:119: // a pointer to that variable so we can
patch it in the tests.
On 2014/05/08 05:34:49, fwereade wrote:
> formatting

Done.

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt#newcode151
doc/how-to-write-tests.txt:151: github.com/juju/testing, which brings in
the CleanupSute from the same.
On 2014/05/08 05:34:49, fwereade wrote:
> s/Sute/Suite/

Done.

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt#newcode159
doc/how-to-write-tests.txt:159: JUJU_ENV, and JUJU_LOGGING_CONFIG
environment variables.
On 2014/05/08 05:34:49, fwereade wrote:
> The FakeHomeSuite should generally not be used, because it assumes a
context of
> a running (cmd/juju) client; almost all the code should avoid making
this
> assumption, because it's actually generally going to run on an API
server.

Perhaps that was the original intent, but it has been extended somewhat
to allow writing of custom files into the HOME directory, and the
isolation of the environment variables that juju cares about.

Perhaps we should split and isolate more.

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt#newcode162
doc/how-to-write-tests.txt:162: api server.
On 2014/05/08 05:34:49, fwereade wrote:
> The JujuConnSuite is all fucked up, and should be used when you just
don't care
> about having coherent context available, you just kinda want
everything and
> don't want to think too hard. State server? Client? Both? Whooo! Who
cares!

> Yeah, we should fix this; but in the meantime I want people to know
it's at
> least somewhat bad/wrong, because then *maybe* someone will take it
upon
> themselves to extract a saner subset of the functionality and start
using that;
> as it is it's all too easy to just allow the rot to gently spread
without even
> knowing that's what you're doing.

Added a note to indicate that we aren't happy with it as it stands :-)

https://codereview.appspot.com/96110043/diff/1/doc/how-to-write-tests.txt#newcode215
doc/how-to-write-tests.txt:215: // of the test.
On 2014/05/08 05:34:49, fwereade wrote:
> Really, FWIW, I think we should always be clearing the env at the very
start of
> a test.

Is there not some part of the environment that we actually care about?

Pretty sure some expect bash.

https://codereview.appspot.com/96110043/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'doc/how-to-write-tests.txt'
--- doc/how-to-write-tests.txt 1970-01-01 00:00:00 +0000
+++ doc/how-to-write-tests.txt 2014-05-08 22:33:29 +0000
@@ -0,0 +1,304 @@
1How to write tests
2==================
3
4On the whole, new or updated code will not pass review unless there are tests
5associated with the code. For code additions, the tests should cover as much
6of the new code as practical, and for code changes, either the tests should be
7updated, or at least the tests that already exist that cover the refactored
8code should be identified when requesting a review to show that there is already
9test coverage, and that the refactoring didn't break anything.
10
11
12go test and gocheck
13-------------------
14
15The `go test` command is used to run the tests. Juju uses the `gocheck` package
16("launchpad.net/gocheck") to provide a checkers and assert methods for the test
17writers. The use of gocheck replaces the standard `testing` library.
18
19Across all of the tests in juju-core, the gocheck package is imported
20with a shorter alias, because it is used a lot.
21
22```go
23import (
24 // system packages
25
26 gc "launchpad.net/gocheck"
27
28 // juju packages
29)
30```
31
32
33setting up tests for new packages
34---------------------------------
35
36Lets say we are creating a new provider for "magic" cloud, and we have a package
37called "magic" that lives at "launchpad.net/juju-core/provider/magic". The
38general approach for testing in juju is to have the tests in a separate package.
39Continuing with this example the tests would be in a package called "magic_test".
40
41A common idiom that has occurred in juju is to setup to gocheck hooks in a special
42file called `package_test.go` that would look like this:
43
44
45```go
46// Copyright 2014 Canonical Ltd.
47// Licensed under the AGPLv3, see LICENCE file for details.
48
49package magic_test
50
51import (
52 "testing"
53
54 gc "launchpad.net/gocheck"
55)
56
57func Test(t *testing.T) {
58 gc.TestingT(t)
59}
60```
61
62or
63
64```go
65// Copyright 2014 Canonical Ltd.
66// Licensed under the AGPLv3, see LICENCE file for details.
67
68package magic_test
69
70import (
71 stdtesting "testing"
72
73 "launchpad.net/juju-core/testing"
74)
75
76func Test(t *stdtesting.T) {
77 testing.MgoTestPackage(t)
78}
79```
80
81The key difference here is that the first one just hooks up `gocheck`
82so it looks for the `gocheck` suites in the package. The second makes
83sure that there is a mongo available for the duration of the package tests.
84
85A general rule is not to setup mongo for a package unless you really
86need to as it is extra overhead.
87
88
89writing the test files
90----------------------
91
92Normally there will be a test file for each file with code in the package.
93For a file called `config.go` there should be a test file called `config_test.go`.
94
95The package should in most cases be the same as the normal files with a "_test" suffix.
96In this way, the tests are testing the same interface as any normal user of the
97package. It is reasonably common to want to modify some internal aspect of the package
98under test for the tests. This is normally handled by a file called `export_test.go`.
99Even though the file ends with `_test.go`, the package definition is the same as the
100normal source files. In this way, for the tests and only the tests, additional
101public symbols can be defined for the package and used in the tests.
102
103Here is an annotated extract from `provider/local/export_test.go`
104
105```go
106// The package is the "local" so it has access to the package symbols
107// and not just the public ones.
108package local
109
110import (
111 "github.com/juju/testing"
112 gc "launchpad.net/gocheck"
113
114 "launchpad.net/juju-core/environs/config"
115)
116
117var (
118 // checkIfRoot is a variable of type `func() bool`, so CheckIfRoot is
119 // a pointer to that variable so we can patch it in the tests.
120 CheckIfRoot = &checkIfRoot
121 // providerInstance is a pointer to an instance of a private structure.
122 // Provider points to the same instance, so public methods on that instance
123 // are available in the tests.
124 Provider = providerInstance
125)
126
127// ConfigNamespace is a helper function for the test that steps through a
128// number of private methods or variables, and is an alternative mechanism
129// to provide functionality for the tests.
130func ConfigNamespace(cfg *config.Config) string {
131 env, _ := providerInstance.Open(cfg)
132 return env.(*localEnviron).config.namespace()
133}
134```
135
136Suites and Juju base suites
137---------------------------
138
139With gocheck tests are grouped into Suites. Each suite has distinct
140set-up and tear-down logic. Suites are often composed of other suites
141that provide specific set-up and tear-down behaviour.
142
143There are three main suites:
144
145 * /testing/testbase.LoggingSuite (testing/testbase/log.go)
146 * /testing.FakeHomeSuite (testing/environ.go)
147 * /juju/testing.JujuConnSuite (juju/testing/conn.go)
148
149The second two have the LoggingSuite functionality included through
150composition. The LoggingSuite is also composed of the LoggingSuite from
151github.com/juju/testing, which brings in the CleanupSuite from the same.
152The CleanupSuite has the functionality around patching environment
153variables and normal variables for the duration of a test. It also
154provides a clean-up stack that gets called when the test teardown happens.
155
156The FakeHomeSuite creates a temporary directory and sets the HOME environment
157variable to it. It also creates ~/.juju and a simple environments.yaml file,
158~/.ssh with a fake id_rsa.pub key, it isolates the test from the JUJU_HOME,
159JUJU_ENV, and JUJU_LOGGING_CONFIG environment variables.
160
161The JujuConnSuite does this and more. It also sets up a state server and api
162server. This is one problem with the JujuConnSuite, it almost always does a
163lot more than you actually want or need. This should really be broken into
164smaller component parts that make more sense. If you can get away with not
165using the JujuConnSuite, you should try.
166
167To create a new suite composed of one or more of the suites above, you can do
168something like:
169
170```go
171type ToolsSuite struct {
172 testbase.LoggingSuite
173 dataDir string
174}
175
176var _ = gc.Suite(&ToolsSuite{})
177
178```
179
180If there is no extra setup needed, then you don't need to specify any
181set-up or tear-down methods as the LoggingSuite has them, and they are
182called by default.
183
184If you did want to do something, say, create a directory and save it in
185the dataDir, you would do something like this:
186
187```go
188func (t *ToolsSuite) SetUpTest(c *gc.C) {
189 t.LoggingSuite.SetUpTest(c)
190 t.dataDir = c.MkDir()
191}
192```
193
194If the test suite has multiple contained suites, please call them in the
195order that they are defined, and make sure something that is composed from
196the LoggingSuite is first. They should be torn down in the reverse order.
197
198Even if the code that is being tested currently has no logging in it, it
199is a good idea to use the LoggingSuite as a base for two reasons:
200 * it brings in something composed of the CleanupSuite
201 * if someone does add logging later, it is captured and doesn't polute
202 the logging output
203
204
205Patching variables and the environment
206--------------------------------------
207
208Inside a test, and assuming that the Suite has a CleanupSuite somewhere
209in the composition tree, there are a few very helpful functions.
210
211```go
212
213var foo int
214
215func (s *someTest) TestFubar(c *gc.C) {
216 // The TEST_OMG environment value will have "new value" for the duration
217 // of the test.
218 s.PatchEnvironment("TEST_OMG", "new value")
219
220 // foo is set to the value 42 for the duration of the test
221 s.PatchValue(&foo, 42)
222}
223```
224
225PatchValue works with any matching type. This includes function variables.
226
227
228Checkers
229--------
230
231Checkers are a core concept of `gocheck` and will feel familiar to anyone
232who has used the python testtools. Assertions are made on the gocheck.C
233methods.
234
235```go
236c.Check(err, gc.IsNil)
237c.Assert(something, gc.Equals, somethingElse)
238```
239
240The `Check` method will cause the test to fail if the checker returns
241false, but it will continue immediately cause the test to fail and will
242continue with the test. `Assert` if it fails will cause the test to
243immediately stop.
244
245For the purpose of further discussion, we have the following parts:
246
247 `c.Assert(observed, checker, args...)`
248
249The key checkers in the `gocheck` module that juju uses most frequently are:
250
251 * `IsNil` - the observed value must be `nil`
252 * `NotNil` - the observed value must not be `nil`
253 * `Equals` - the observed value must be the same type and value as the arg,
254 which is the expected value
255 * `DeepEquals` - checks for equality for more complex types like slices,
256 maps, or structures. This is DEPRECATED in favour of the DeepEquals from
257 the `github.com/juju/testing/checkers` covered below
258 * `ErrorMatches` - the observed value is expected to be an `error`, and
259 the arg is a string that is a regular expression, and used to match the
260 error string
261 * `Matches` - a regular expression match where the observed value is a string
262 * `HasLen` - the expected value is an integer, and works happily on nil
263 slices or maps
264
265
266Over time in the juju project there were repeated patterns of testing that
267were then encoded into new and more complicated checkers. These are found
268in `github.com/juju/testing/checkers`, and are normally imported with the
269alias `jc`.
270
271The matchers there include (not an exclusive list):
272
273 * `IsTrue` - just an easier way to say `gc.Equals, true`
274 * `IsFalse` - observed value must be false
275 * `GreaterThan` - for integer or float types
276 * `LessThan` - for integer or float types
277 * `HasPrefix` - obtained is expected to be a string or a `Stringer`, and
278 the string (or string value) must have the arg as start of the string
279 * `HasSuffix` - the same as `HasPrefix` but checks the end of the string
280 * `Contains` - obtained is a string or `Stringer` and expected needs to be
281 a string. The checker passes if the expected string is a substring of the
282 obtained value.
283 * `DeepEquals` - works the same way as the `gocheck.DeepEquals` except
284 gives better errors when the values do not match
285 * `SameContents` - obtained and expected are slices of the same type,
286 the checker makes sure that the values in one are in the other. They do
287 not have the be in the same order.
288 * `Satisfies` - the arg is expected to be `func(observed) bool`
289 often used for error type checks
290 * `IsNonEmptyFile` - obtained is a string or `Stringer` and refers to a
291 path. The checker passes if the file exists, is a file, and is not empty
292 * `IsDirectory` - works in a similar way to `IsNonEmptyFile` but passes if
293 the path element is a directory
294 * `DoesNotExist` - also works with a string or `Stringer`, and passes if
295 the path element does not exist
296
297
298
299Good tests
300----------
301
302Good tests should be:
303 * small and obviously correct
304 * isolated from any system or environment values that may impact the test

Subscribers

People subscribed via source and target branches

to status/vote changes: