github.com/jonsyu1/godel@v0.0.0-20171017211503-64567a0cf169/docs/Integration-tests.md (about) 1 Summary 2 ------- 3 The `github.com/palantir/godel/pkg/products` package can be used to write integration tests that run against the build 4 artifacts of a product and test tags can be used to define test sets for integration tests. 5 6 Tutorial start state 7 -------------------- 8 9 * `$GOPATH/src/github.com/nmiyake/echgo` exists and is the working directory 10 * Project contains `godel` and `godelw` 11 * Project contains `main.go` 12 * Project contains `.gitignore` that ignores IDEA files 13 * Project contains `echo/echo.go`, `echo/echo_test.go` and `echo/echoer.go` 14 * `godel/config/dist.yml` is configured to build `echgo` 15 * Project is tagged as 0.0.1 16 * `godel/config/dist.yml` is configured to create distributions for `echgo` 17 * Project is tagged as 0.0.2 18 * Go files have license headers 19 * `godel/config/generate.yml` is configured to generate string function 20 * `godel/config/exclude.yml` is configured to ignore all `.+_string.go` files 21 22 ([Link](https://github.com/nmiyake/echgo/tree/1982133dbe7c811f1e2d71f4dcc25ff20f84146a)) 23 24 Write tests that run using build artifacts 25 ------------------------------------------ 26 27 `echgo` currently has unit tests that test the contracts of the `echgo` package. Unit tests are a great way to test the 28 API contracts of packages, and in an ideal world all of the packages for a project having tests that verify the package 29 APIs would be sufficient to ensure the correctness of an entire program. 30 31 However, in many cases there exists behavior that can only be tested in a true end-to-end workflow. For example, `echgo` 32 currently has some logic in its `main.go` file that parses the command-line flags, determines what functions to call 33 based on flags and ultimately prints the output to the console. If we want to test things such as what happens when 34 invalid values are supplied as flags, how multiple command-line arguments are parsed or the exit codes of the program, 35 there is not a straightforward way to write that test. 36 37 The `github.com/palantir/godel/pkg/products` packages provides functionality that makes it easy to write such tests for 38 projects that use gödel to build their products. The `products` package provides functions that ensure that specified 39 products are built using the build configuration defined for the product and provides a path to the built executable 40 that can be used for testing. 41 42 We need to add `github.com/palantir/godel/pkg/products` as a vendored dependency for the project. Start by cloning the 43 gödel project: 44 45 ``` 46 ➜ mkdir -p $GOPATH/github.com/palantir && cd $_ 47 ➜ git clone https://github.com/palantir/godel.git 48 Cloning into 'godel'... 49 remote: Counting objects: 3210, done. 50 remote: Total 3210 (delta 0), reused 0 (delta 0), pack-reused 3209 51 Receiving objects: 100% (3210/3210), 3.65 MiB | 1.92 MiB/s, done. 52 Resolving deltas: 100% (1382/1382), done. 53 Checking connectivity... done. 54 ``` 55 56 There are multiple different ways to vendor dependencies. For the purposes of this tutorial, we will forego formal 57 vendoring and vendor the dependency manually. 58 59 ``` 60 ➜ cd $GOPATH/src/github.com/nmiyake/echgo 61 ➜ mkdir -p vendor/github.com/palantir/godel/pkg/products 62 ➜ cp $GOPATH/src/github.com/palantir/godel/pkg/products/* vendor/github.com/palantir/godel/pkg/products/ 63 ``` 64 65 Run the following to define a test that tests the behavior of invoking `echgo` with an invalid echo type and run the 66 test (this test is still in the iteration phase, so it simply prints the result of the output rather than asserting 67 against it): 68 69 ``` 70 ➜ mkdir -p integration_test 71 ➜ echo '// Copyright (c) 2017 Author Name 72 // 73 // Licensed under the Apache License, Version 2.0 (the "License"); 74 // you may not use this file except in compliance with the License. 75 // You may obtain a copy of the License at 76 // 77 // http://www.apache.org/licenses/LICENSE-2.0 78 // 79 // Unless required by applicable law or agreed to in writing, software 80 // distributed under the License is distributed on an "AS IS" BASIS, 81 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 82 // See the License for the specific language governing permissions and 83 // limitations under the License. 84 85 package integration_test 86 87 import ( 88 "fmt" 89 "os/exec" 90 "testing" 91 92 "github.com/palantir/godel/pkg/products" 93 ) 94 95 func TestInvalidType(t *testing.T) { 96 echgoPath, err := products.Bin("echgo") 97 if err != nil { 98 panic(err) 99 } 100 cmd := exec.Command(echgoPath, "-type", "invalid", "foo") 101 output, err := cmd.CombinedOutput() 102 if err != nil { 103 t.Errorf("cmd %v failed with error %v. Output: %s", cmd.Args, err, string(output)) 104 } 105 fmt.Printf("%q", string(output)) 106 fmt.Println() 107 }' > integration_test/integration_test.go 108 ➜ go test -v ./integration_test 109 === RUN TestInvalidType 110 "invalid echo type: invalid\n" 111 --- PASS: TestInvalidType (1.43s) 112 PASS 113 ok github.com/nmiyake/echgo/integration_test 1.566s 114 ``` 115 116 The `products.Bin("echgo")` call uses gödel to build the `echgo` product (if needed) and returns a path to the binary 117 that was built. Because this is a path to a valid binary, `exec.Command` can be use to invoke it. This allows the test 118 to specify arguments, hook up input/output streams, check error values and assert various behavior. 119 120 In this case, the output seems reasonable -- it prints `invalid echo type: invalid\n`. However, note that the error was 121 `nil` -- this is a bug. If the specified echo type was invalid, then the program should return with a non-zero exit 122 code, which should cause `cmd.CombinedOutput` to return an error. 123 124 Fix the bug by updating `main.go` and then re-run the test: 125 126 ``` 127 ➜ echo '// Copyright (c) 2017 Author Name 128 // 129 // Licensed under the Apache License, Version 2.0 (the "License"); 130 // you may not use this file except in compliance with the License. 131 // You may obtain a copy of the License at 132 // 133 // http://www.apache.org/licenses/LICENSE-2.0 134 // 135 // Unless required by applicable law or agreed to in writing, software 136 // distributed under the License is distributed on an "AS IS" BASIS, 137 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 138 // See the License for the specific language governing permissions and 139 // limitations under the License. 140 141 package main 142 143 import ( 144 "flag" 145 "fmt" 146 "os" 147 "strings" 148 149 "github.com/nmiyake/echgo/echo" 150 ) 151 152 var version = "none" 153 154 func main() { 155 versionVar := flag.Bool("version", false, "print version") 156 typeVar := flag.String("type", echo.Simple.String(), "type of echo") 157 flag.Parse() 158 if *versionVar { 159 fmt.Println("echgo version:", version) 160 return 161 } 162 typ, err := echo.TypeFrom(*typeVar) 163 if err != nil { 164 fmt.Println("invalid echo type:", *typeVar) 165 os.Exit(1) 166 } 167 echoer := echo.NewEchoer(typ) 168 fmt.Println(echoer.Echo(strings.Join(flag.Args(), " "))) 169 }' > main.go 170 ➜ go test -v ./integration_test 171 === RUN TestInvalidType 172 "invalid echo type: invalid\n" 173 --- FAIL: TestInvalidType (1.38s) 174 integration_test.go:33: cmd [/Volumes/git/go/src/github.com/nmiyake/echgo/build/0.0.2-4-g1982133.dirty/darwin-amd64/echgo -type invalid foo] failed with error exit status 1. Output: invalid echo type: invalid 175 FAIL 176 exit status 1 177 FAIL github.com/nmiyake/echgo/integration_test 1.564s 178 ``` 179 180 We can see that the test now fails as expected. Since this is the expected behavior, update the test to pass when this 181 happens and run the test again: 182 183 ``` 184 ➜ echo '// Copyright (c) 2017 Author Name 185 // 186 // Licensed under the Apache License, Version 2.0 (the "License"); 187 // you may not use this file except in compliance with the License. 188 // You may obtain a copy of the License at 189 // 190 // http://www.apache.org/licenses/LICENSE-2.0 191 // 192 // Unless required by applicable law or agreed to in writing, software 193 // distributed under the License is distributed on an "AS IS" BASIS, 194 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 195 // See the License for the specific language governing permissions and 196 // limitations under the License. 197 198 package integration_test 199 200 import ( 201 "os/exec" 202 "testing" 203 204 "github.com/palantir/godel/pkg/products" 205 ) 206 207 func TestInvalidType(t *testing.T) { 208 echgoPath, err := products.Bin("echgo") 209 if err != nil { 210 panic(err) 211 } 212 cmd := exec.Command(echgoPath, "-type", "invalid", "foo") 213 output, err := cmd.CombinedOutput() 214 gotOutput := string(output) 215 if err == nil { 216 t.Errorf("expected command %v to fail. Output: %s", cmd.Args, gotOutput) 217 } 218 wantOutput := "invalid echo type: invalid\\n" 219 if wantOutput != gotOutput { 220 t.Errorf("invalid output: want %q, got %q", wantOutput, gotOutput) 221 } 222 wantErr := "exit status 1" 223 gotErr := err.Error() 224 if wantErr != gotErr { 225 t.Errorf("invalid error output: want %q, got %q", wantErr, gotErr) 226 } 227 }' > integration_test/integration_test.go 228 ➜ go test -v ./integration_test 229 === RUN TestInvalidType 230 --- PASS: TestInvalidType (0.51s) 231 PASS 232 ok github.com/nmiyake/echgo/integration_test 0.707s 233 ``` 234 235 We can see that the test now passes. The test will now run when `./godelw test` is invoked. 236 237 One thing to note about this construction is that the `go build` and `go install` commands will currently not work for 238 `integration_test` because the directory contains only tests: 239 240 ``` 241 ➜ go build ./integration_test 242 go build github.com/nmiyake/echgo/integration_test: no non-test Go files in /Volumes/git/go/src/github.com/nmiyake/echgo/integration_test 243 ``` 244 245 We can work around this by adding a `doc.go` file to the directory to act as a placeholder: 246 247 ``` 248 ➜ echo '// Copyright (c) 2017 Author Name 249 // 250 // Licensed under the Apache License, Version 2.0 (the "License"); 251 // you may not use this file except in compliance with the License. 252 // You may obtain a copy of the License at 253 // 254 // http://www.apache.org/licenses/LICENSE-2.0 255 // 256 // Unless required by applicable law or agreed to in writing, software 257 // distributed under the License is distributed on an "AS IS" BASIS, 258 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 259 // See the License for the specific language governing permissions and 260 // limitations under the License. 261 262 // Package integration contains integration tests. 263 package integration' > integration_test/doc.go 264 ``` 265 266 Verify that building the directory no longer fails: 267 268 ``` 269 ➜ go build ./integration_test 270 ``` 271 272 Run `./godelw test` to verify that this test is run: 273 274 ``` 275 ➜ ./godelw test 276 ok github.com/nmiyake/echgo 0.189s [no tests to run] 277 ok github.com/nmiyake/echgo/echo 0.201s 278 ok github.com/nmiyake/echgo/generator 0.179s [no tests to run] 279 ok github.com/nmiyake/echgo/integration_test 0.614s 280 ``` 281 282 The configuration in `godel/config/test.yml` can be used to group tests into tags. Update the configuration as follows: 283 284 ``` 285 ➜ echo 'tags: 286 integration: 287 names: 288 - "^integration_test$"' > godel/config/test.yml 289 ``` 290 291 This configuration defines a tag named "integration" that matches any directories named "integration_test". Run the 292 following command to run only the tests that match the "integration" tag: 293 294 ``` 295 ➜ ./godelw test --tags=integration 296 ok github.com/nmiyake/echgo/integration_test 0.583s 297 ``` 298 299 By default, the `./godelw test` task runs all tests (all tagged and untagged tests). Multiple tags can be specified by 300 separating them with a comma. Specifying `all` will run all tagged tests, while specifying `none` will run all tests 301 that do not match any tags. 302 303 Commit these changes by running the following: 304 305 ``` 306 ➜ git add godel main.go integration_test vendor 307 ➜ git commit -m "Add integration tests" 308 [master 676aad3] Add integration tests 309 6 files changed, 236 insertions(+), 1 deletion(-) 310 create mode 100644 integration_test/doc.go 311 create mode 100644 integration_test/integration_test.go 312 create mode 100644 vendor/github.com/palantir/godel/pkg/products/products.go 313 create mode 100644 vendor/github.com/palantir/godel/pkg/products/products_test.go 314 ``` 315 316 Tutorial end state 317 ------------------ 318 319 * `$GOPATH/src/github.com/nmiyake/echgo` exists and is the working directory 320 * Project contains `godel` and `godelw` 321 * Project contains `main.go` 322 * Project contains `.gitignore` that ignores IDEA files 323 * Project contains `echo/echo.go`, `echo/echo_test.go` and `echo/echoer.go` 324 * `godel/config/dist.yml` is configured to build `echgo` 325 * Project is tagged as 0.0.1 326 * `godel/config/dist.yml` is configured to create distributions for `echgo` 327 * Project is tagged as 0.0.2 328 * Go files have license headers 329 * `godel/config/generate.yml` is configured to generate string function 330 * `godel/config/exclude.yml` is configured to ignore all `.+_string.go` files 331 * `integration_test` contains integration tests 332 * `godel/config/test.yml` is configured to specify the "integration" tag 333 334 ([Link](https://github.com/nmiyake/echgo/tree/676aad36a5c355af826397be682f49bbb4a9ed20)) 335 336 Tutorial next step 337 ------------------ 338 339 [Sync documentation with GitHub wiki](https://github.com/palantir/godel/wiki/GitHub-wiki)