1. TDD in Go with
Ginkgo and Gomega
Eddy Reyes - eddy@containable.io
http://www.tasqr.io
2. About Me
Eddy Reyes. Cofounder of Tasqr
(www.tasqr.io)
● Automates application deployment /
configuration without code or config files.
Previously hacked lots of code at:
● IBM, Lifesize, Click Security
3. Overview of Presentation
● Explain what TDD is
o Not necessarily proselytizing that you must do it.
o I dislike many TDD-related presentations, they don’t
match my experience, they don’t stress what’s
important IMO.
● What Tools Does Go Have to Help You Do
TDD?
o Ginkgo and Gomega, many others
4. Further TDD Reading
Uncle Bob Martin
● http://blog.8thlight.com/uncle-bob/archive.html
Kent Beck
● Extreme Programming Explained (book)
Is TDD Dead? (video debates)
● Kent Beck, Martin Fowler, DHH
● https://www.youtube.com/watch?v=z9quxZsLcfo (part 1)
5. What Isn’t TDD?
● NOT just having written tests
● NOT a way to feel like you’re going fast
● NOT the Silver Bullet that saves software
engineering
● NOT like Axe, which makes you irresistible
to the opposite sex :-)
6. What Is TDD?
● A process you follow when writing software.
● The process goes like this:
o You write a test that defines an expected outcome in
your program.
o You watch the test fail.
o You change the program until your test passes.
● Adding tests to an already substantial
untested program isn’t necessarily TDD.
7. How Do You Do TDD?
● In order to effectively follow the TDD pattern,
you must first understand your design.
o Tests are rigid. Learning is free-form.
o You learn by hacking and breaking things.
o Tests ensure things don’t break.
● TDD helps you SHIP, not LEARN
8. Learn vs. Ship
● Learning is free-form exploration.
Experiment, play, break things.
o DO NOT SHIP THIS CODE!
o After you learn your design, step away from the
keyboard, and start anew.
● Shipping
o Code ends up in a product used by customers.
o Quality is important!
9. Tests Are Design!
● If tests are comprehensive, they are a formal
specification of your program.
● Writing a test is expressing the design of
your program’s functionality.
● To truly serve as a spec, tests must be:
o Clean, readable
o Follow some consistent notation
10. Test/Spec Notation
GIVEN
● Precondition 1...N
WHEN
● Call into your program
THEN
● Assert Condition 1...N
11. A Test Framework Needs...
● Clean, simple notation
● Clean setup/teardown of dependencies
o Idempotent, independent tests!
● Clean separation from tested code
● Simple runner mechanism
● Good reporting
o test results
o coverage maps
12. Testing in Go
● Test runner
o go test
● Separates test code from product
o *_test.go
● Comes with a test writing library
o “testing” package
● Comes with coverage reporting tools
o Beautiful HTML output
13. “testing” Package
● Allows your to write test functions.
● Nice benchmarking utility
● Tests are basically blocks of code :(
func TestXXX(t *testing.T) {
// setup stuff
// call your code
if something.State != WhatIWant {
t.Fail()
}
}
14. Better Test-Writing Library
● go command has great tooling.
● Need a better notation for specifying tests.
…
Ginkgo and Gomega is a step forward!
… but not perfect!
15. Ginkgo and Gomega
● Ginkgo
o Compatible with “go test” but also comes with its
own test runner
o BDD-style test notation
o Expressive structuring of tests
● Gomega
o “Matching” library
o Allows for expressive assert-like statements
16. Ginkgo
● Tests expressed as English statements
o In the BDD tradition of Cucumber, et. al.
var _ = Describe(“My component”, func() {
It(“Does something special”, func() {
MyFunction()
Expect(MyVar).To(Equal(10))
})
})
17. Gomega
● Assertion language
Expect(number).To(Equal(10))
Expect(value).To(BeNumerically(“<”, 20))
Expect(someMap).To(Equal(otherMap)) // does reflect.DeepEqual()
Expect(someMap).To(BeEquivalent(otherMap)) // allows different types
Expect(myPtr).ToNot(BeNil())
Expect(flag).To(BeFalse())
Expect(err).ToNot(HaveOccurred())
Expect(err).To(MatchError(“my error message”))
18. Ginkgo and Dependencies
● setup/teardown:
var _ = Describe(“my config object”, func() {
var (
configObj *config.MyConfig
tempDir string
)
BeforeEach(func() {
var err error
tempDir, err = ioutil.TempDir(“”, “test-dir”)
Expect(err).ToNot(HaveOccurred())
configObj = config.NewConfig()
})
19. Ginkgo and Dependencies
It(“Saves itself to file”, func() {
configObj.Save(tempDir)
restoredConfig := config.FromFile(configObj.Filename)
Expect(*restoredConfig).To(Equal(*configObj))
})
AfterEach(func() {
os.RemoveAll(tempDir)
})
}) // end Describe()
20. Ginkgo and Dependencies
● Ginkgo heavily relies on closures and
closure variables to manage dependencies.
o Resist the temptation to share state among tests!
● Nested dependencies
o Within a Describe block, you can nest:
Context block
More It’s and Before/AfterEach’s
21. Nested Dependencies
var _ = Describe(“My component”)
var configObj config.MyConfig
BeforeEach(func() {
configObj = config.NewConfig()
})
It(“Does test1”, func() {
// configObj is setup
})
23. Testing Notation + Ginkgo
● Obviously, Ginkgo was not designed with my
exact notation in mind…
● However, it maps to it without too much
difficulty.
24. Testing Notation + Ginkgo
var _ = Describe(“my component”, func() {
// GIVEN
BeforeEach(func () {
})
It(“Does something useful”, func() {
// WHEN
CallSomething()
// THEN
Expect(stuff).To((Equal(something))
})
})
25. Alternate Notation Style
var _ = Describe(“my component”, func() {
BeforeEach(func() {...}) //
GIVEN
It(“Does something useful”, func() { // WHEN
closureVar = CallSomething()
})
AfterEach(func() {
Expect(closureVar).To(Equal(something)) // THEN
})
})
26. Ginkgo Bootstrap
● Ginkgo integrates with go test
● You must use the ginkgo command to
generate boilerplate code:
$ cd mypackage/ # within $GOPATH/src
$ ginkgo bootstrap
…
$ ls
mypackage_suite_test.go
27. Ginkgo Bootstrap
package mypackage_test // NOT THE SAME AS mypackage!!!
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestConfig(t *testing.T) { // go test integration boilerplate!
RegisterFailHandler(Fail)
RunSpecs(t, "Mypackage Suite")
}
28. Ginkgo Generate
● The generated suite calls the generated test
files:
$ ginkgo generate
…
$ ls
mypackage_suite_test.go mypackage_test.go
…
$ cat mypackage_test.go
...
29. Ginkgo Generate
package mypackage_test // NOT THE SAME AS mypackage!!
import (
. “mypackage”
. “github.com/onsi/ginkgo”
. “github.com/onsi/gomega”
)
var _ = Describe(“Mypackage test”, func() {
})
30. Assessing Ginkgo/Gomega
Test Notation
● The BDD-style english annotations help
readability a little bit...
● Closures are a slippery slope!
o Too much code: declare closure vars, must also
initialize in BeforeEach to ensure test
idempotency!
31. Assessing Ginkgo/Gomega
● Complex dependencies are doable
o Accomplished by nesting Describe/Context/
BeforeEach’s
o However, your tests must be inserted in ever-more
nested closures
Can get kinda complicated!
● Gomega assertions
o Flow nicely when read aloud
o Too complex as a formal notation
32. Assessing Ginkgo/Gomega
● Overall test notation grade: C+/B-o
Depends on my mood and how complicated my test
needs to get.
o Worst part about it is that your test file easily grows
way too long, and thus harder to comprehend as a
spec.
● Good enough to get our job done with high
quality at Tasqr.