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