Every year we hear great content about how to develop in Go, but rarely do we focus on how to test in Go. Well written tests are critical to the success of a project, and more often than not, they can help drive developers to design features in more simple and concise ways.
In this talk, I'll explain the importance of test driven development and provide some tactics for how to implement the practice in your daily work and on your respective team. I'll dive into the testing, require, and assert packages to dissect which function calls are appropriate for different use cases, and present multiple different ways to write Go tests for each scope, including unit, integration and e2e. I'll also discuss how to refactor code to make it more testable (with examples), so you can optimize and simplify Go code for robust and reliable Go tests. Lastly, I will cover race conditions to help you debug concurrency related problems. Let's write Go(od) tests!
4. Why Golang?
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
5. Why Golang?
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
6. Why Golang?
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
7. Why Golang?
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
8. Why Golang?
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
9. Testing implications
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
10. Testing implications
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
11. Testing implications
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
12. Testing implications
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
13. Testing implications
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
21. run all tests in this directory:
go test
run a single test against a fully qualified package name:
go test -v -run TestSubtotal github.com/nikkixdev/beer/api
run all tests with tags, timeout, and race detector:
go test -tags=integration -race -timeout=60s -v -run ./...
run all tests with code coverage:
go test -cover
24. // Cart represents a shopping cart.
type Cart struct {
Cases []*Case
}
// Case represents a case of beer.
type Case struct {
Count int
Beer *Beer
Price float64
}
// Beer represents a type of beer.
type Beer struct {
Brand string
Name string
Ounces float64
}
// AddCase adds a case of beer to the
shopping cart.
func (c *Cart) AddCase(beerCase *Case) {
c.Cases = append(c.Cases, beerCase)
}
// FixtureBeer creates a Beer fixture for
use in test.
func FixtureBeer(brand string, name string,
ounces float64) *Beer {
return &Beer{
Brand: brand,
Name: name,
Ounces: ounces,
}
}
25. func TestAddCaseTesting(t *testing.T) {
cart := NewCart()
if len(cart.Cases) != 0 {
t.Fatal("expected empty cart")
}
blueLight := FixtureBeer("Labatt", "Blue Light", 12.0)
cart.AddCase(FixtureCase(6, blueLight, 10.99))
if len(cart.Cases) != 1 {
t.Fatal("expected 1 case in cart")
}
}
42. var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context {
return background
}
// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
// parameter).
func TODO() Context {
return todo
}
43. // Subscription represents a shopping cart.
type Subscription struct {
Cart *Cart
Interval time.Duration
messageChan chan interface{}
}
// startSubscriptionTimer starts a timer and fires the cart to the
// order handler when the order is ready.
func (s *Subscription) startSubscriptionTimer(ctx context.Context) {
ticker := time.NewTicker(s.Interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
s.messageChan <- s.Cart
}
}
}