SlideShare a Scribd company logo
1 of 55
Download to read offline
Writing Go(od) Tests
Nikki Attea
Software Engineer
Sensu Inc
Hi, I’m Nikki.
@nikkixdev
www.nikki.dev
github.com/sensu/sensu-go
- 🏐🏖
- 🍷🍺
- 🐶🐶
Why Golang?
Why Golang?
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
Why Golang?
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
Why Golang?
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
Why Golang?
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
Why Golang?
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
Testing implications
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
Testing implications
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
Testing implications
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
Testing implications
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
Testing implications
● Statically and structurally typed
● Compiled language with large standard library
● Self-contained
● Concurrent and asynchronous
Writing (good) tests.
test-driven development
Running (go) tests.
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
main.go -> main_test.go
func TestXxx(t *testing.T) {
}
Writing (go) tests.
// 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,
}
}
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")
}
}
testing vs. stretchr/testify
func TestAddCaseAssert(t *testing.T) {
cart := NewCart()
assert.Equal(t, 0, len(cart.Cases), "expected empty cart")
blueLight := FixtureBeer("Labatt", "Blue Light", 12.0)
cart.AddCase(FixtureCase(6, blueLight, 10.99))
assert.Equal(t, 1, len(cart.Cases))
}
test-driven development
func TestSubtotal(t *testing.T) {
cart := NewCart()
assert.Equal(t, 0, len(cart.Cases))
duvelHop := FixtureBeer("Duvel", "Tripel Hop", 11.0)
cart.AddCase(FixtureCase(4, duvelHop, 14.99))
blueLight := FixtureBeer("Labatt", "Blue Light", 12.0)
cart.AddCase(FixtureCase(30, blueLight, 24.99))
assert.Equal(t, 40.0, cart.Subtotal())
}
// Subtotal calculates the subtotal of the shopping cart.
func (c *Cart) Subtotal() float64 {
var subtotal float64
for _, beerCase := range c.Cases {
subtotal += beerCase.Price
}
return subtotal
}
func TestSubtotal(t *testing.T) {
cart := NewCart()
assert.Equal(t, 0, len(cart.Cases))
duvelHop := FixtureBeer("Duvel", "Tripel Hop", 11.0)
cart.AddCase(FixtureCase(4, duvelHop, 14.99))
blueLight := FixtureBeer("Labatt", "Blue Light", 12.0)
cart.AddCase(FixtureCase(30, blueLight, 24.99))
assert.Equal(t, 39.98, cart.Subtotal())
}
func TestSubtotalSuite(t *testing.T) {
testCases := []struct {
name string
cart *Cart
subtotal float64
}{
{
name: "Empty cart",
cart: &Cart{},
subtotal: 0,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.subtotal, tc.cart.Subtotal())
})
}
}
{
name: "Party time",
cart: &Cart{Cases: []*Case{
FixtureCase(4, FixtureBeer("Duvel", "Tripel Hop", 11.0), 14.99),
FixtureCase(30, FixtureBeer("Labatt", "Blue Light", 12.0), 24.99),
FixtureCase(30, FixtureBeer("Labatt", "Blue Light", 12.0), 24.99),
}},
subtotal: 64.97,
},
{
name: "Negative",
cart: &Cart{Cases: []*Case{
FixtureCase(4, FixtureBeer("Duvel", "Tripel Hop", 11.0), -14),
FixtureCase(30, FixtureBeer("Labatt", "Blue Light", 12.0), 24),
}},
subtotal: 10.00,
},
httptest.NewServer(http.Handler)
// ProcessPayment sends the total to an external payment api.
func ProcessPayment(total float64) ([]byte, error) {
b, _ := json.Marshal(total)
resp, err := http.Post("https://www.pay-me.com",
"application/json", bytes.NewBuffer(b))
if err != nil {
return nil, err
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}
// ProcessPayment sends the total to an external payment api.
func ProcessPayment(paymentServer string, total float64)
([]byte, error) {
b, _ := json.Marshal(total)
resp, err := http.Post(paymentServer, "application/json",
bytes.NewBuffer(b))
if err != nil {
return nil, err
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}
testCases := []struct {
name string
handler http.HandlerFunc
expectedError error
expectedBody []byte
}{
{
name: "OK",
handler: func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`OK`))
w.WriteHeader(http.StatusOK)
},
expectedError: nil,
expectedBody: []byte(`OK`),
},
{
name: "Internal server error",
handler: func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
},
expectedError: fmt.Errorf("payment server error: %d", http.StatusInternalServerError),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(tc.handler))
defer ts.Close()
body, err := ProcessPayment(ts.URL, 21.11)
assert.Equal(t, tc.expectedError, err)
assert.Equal(t, tc.expectedBody, body)
})
}
// ProcessPayment sends the total to an external payment api.
func ProcessPayment(paymentServer string, total float64)
([]byte, error) {
b, _ := json.Marshal(total)
resp, err := http.Post(paymentServer, "application/json",
bytes.NewBuffer(b))
if err != nil {
return nil, err
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}
// ProcessPayment sends the total to an external payment api.
func ProcessPayment(paymentServer string, total float64)
([]byte, error) {
b, _ := json.Marshal(total)
resp, err := http.Post(paymentServer, "application/json",
bytes.NewBuffer(b))
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("payment server error: %d",
resp.StatusCode)
}
return ioutil.ReadAll(resp.Body)
}
context.Background() vs. context.TODO()
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
}
// 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
}
}
}
func TestStartSubscriptionTimer(t *testing.T) {
cart1 := &Cart{Cases: []*Case{FixtureCase(4, FixtureBeer("Duvel", "Tripel Hop", 11.0), 14)}}
cart2 := &Cart{Cases: []*Case{FixtureCase(30, FixtureBeer("Labatt", "Blue Light", 12.0), 24)}}
subscription := &Subscription{
Cart: cart1,
Interval: time.Duration(1) * time.Second,
messageChan: make(chan interface{}),
}
go subscription.startSubscriptionTimer(context.Background())
msg := <-subscription.messageChan
order, ok := msg.(*Cart)
if !ok {
t.Fatal("received invalid message on message channel")
}
assert.Equal(t, cart1, order)
subscription.Cart = cart2
msg = <-subscription.messageChan
order, ok = msg.(*Cart)
if !ok {
t.Fatal("received invalid message on message channel")
}
assert.Equal(t, cart2, order)
}
// startOrderHandler listens to the message channel and handles incoming orders.
func (o *OrderHandler) startOrderHandler(ctx context.Context) {
for {
msg, ok := <-o.messageChan
if !ok {
logger.Debug("message channel closed")
return
}
cart, ok := msg.(*Cart)
if ok {
if err := o.PlaceOrder(ctx, cart); err != nil {
logger.WithError(err).Error("error placing order")
continue
}
logger.Info("successfully placed order")
continue
}
logger.WithField("msg", msg).Errorf("received invalid message on message channel")
}
}
func TestStartOrderHandler(t *testing.T) {
handler := &OrderHandler{
messageChan: make(chan interface{}),
}
go handler.startOrderHandler(context.Background())
assert.Equal(t, 0, len(handler.ProcessedOrders))
handler.messageChan <- FixtureCart()
handler.messageChan <- FixtureCart()
handler.messageChan <- FixtureCase(30, FixtureBeer("Labatt", "Blue Light",
12.0), 24)
assert.Equal(t, 2, len(handler.ProcessedOrders))
}
Writing (good go) tests.
race conditions & race detection
$ go test -run TestStartSubscriptionTimer -race
==================
WARNING: DATA RACE
Read at 0x00c00000e5e0 by goroutine 9:
github.com/nikkixdev/beer.(*Subscription).startSubscriptionTimer()
/Users/nikkixdev/go/src/github.com/nikkixdev/beer/main.go:123 +0xb7
Previous write at 0x00c00000e5e0 by goroutine 8:
github.com/nikkixdev/beer.TestStartSubscriptionTimer()
/Users/nikkixdev/go/src/github.com/nikkixdev/beer/main_test.go:138 +0x582
testing.tRunner()
/usr/local/go/src/testing/testing.go:909 +0x199
Goroutine 9 (running) created at:
github.com/nikkixdev/beer.TestStartSubscriptionTimer()
/Users/nikkixdev/go/src/github.com/nikkixdev/beer/main_test.go:130 +0x4cf
testing.tRunner()
/usr/local/go/src/testing/testing.go:909 +0x199
==================
--- FAIL: TestStartSubscriptionTimer (2.01s)
testing.go:853: race detected during execution of test
FAIL
exit status 1
FAIL github.com/nikkixdev/beer 2.387s
main.go L123
s.messageChan <- s.Cart
main_test.go L138
subscription.Cart = cart2
sync.Mutex
type Subscription struct {
cart *Cart
interval time.Duration
messageChan chan interface{}
mu sync.Mutex
}
// GetCart safely retrieves the subscriptions shopping cart.
func (s *Subscription) GetCart() *Cart {
s.mu.Lock()
defer s.mu.Unlock()
return s.cart
}
// SetCart safely sets the subscriptions shopping cart.
func (s *Subscription) SetCart(c *Cart) {
s.mu.Lock()
defer s.mu.Unlock()
s.cart = c
}
$ go test -run ./... -race
PASS
ok github.com/nikkixdev/beer 3.113s
Thanks for listening!
@nikkixdev
www.nikki.dev
github.com/nikkixdev/beer-testing
Resources
Go docs:
golang.org
Special thanks for the gopher illustrations:
● github.com/ashleymcnamara/gophers
● github.com/marcusolsson/gophers
● github.com/MariaLetta/free-gophers-pack

More Related Content

What's hot

Critical errors in CryEngine V code
Critical errors in CryEngine V codeCritical errors in CryEngine V code
Critical errors in CryEngine V codePVS-Studio
 
Build microservice with gRPC in golang
Build microservice with gRPC in golangBuild microservice with gRPC in golang
Build microservice with gRPC in golangTing-Li Chou
 
Event-sourced architectures with Akka - Sander Mak
Event-sourced architectures with Akka - Sander MakEvent-sourced architectures with Akka - Sander Mak
Event-sourced architectures with Akka - Sander MakNLJUG
 
InterConnect: Server Side Swift for Java Developers
InterConnect:  Server Side Swift for Java DevelopersInterConnect:  Server Side Swift for Java Developers
InterConnect: Server Side Swift for Java DevelopersChris Bailey
 
Final project powerpoint template (fndprg) (1)
Final project powerpoint template (fndprg) (1)Final project powerpoint template (fndprg) (1)
Final project powerpoint template (fndprg) (1)jewelyngrace
 
IBM Cloud University: Java, Node.js and Swift
IBM Cloud University: Java, Node.js and SwiftIBM Cloud University: Java, Node.js and Swift
IBM Cloud University: Java, Node.js and SwiftChris Bailey
 
GeeCON 2014 - Functional Programming without Lambdas
GeeCON 2014 - Functional Programming without LambdasGeeCON 2014 - Functional Programming without Lambdas
GeeCON 2014 - Functional Programming without LambdasMattias Severson
 
Friend this-new&delete
Friend this-new&deleteFriend this-new&delete
Friend this-new&deleteShehzad Rizwan
 
Flamingo Training - Hello World
Flamingo Training - Hello WorldFlamingo Training - Hello World
Flamingo Training - Hello Worldi-love-flamingo
 
Chapter i c#(console application and programming)
Chapter i c#(console application and programming)Chapter i c#(console application and programming)
Chapter i c#(console application and programming)Chhom Karath
 
Avoiding Callback Hell with Async.js
Avoiding Callback Hell with Async.jsAvoiding Callback Hell with Async.js
Avoiding Callback Hell with Async.jscacois
 
Workshop: Async and Parallel in C#
Workshop: Async and Parallel in C#Workshop: Async and Parallel in C#
Workshop: Async and Parallel in C#Rainer Stropek
 
Switch case and looping
Switch case and loopingSwitch case and looping
Switch case and loopingChaAstillas
 

What's hot (17)

Critical errors in CryEngine V code
Critical errors in CryEngine V codeCritical errors in CryEngine V code
Critical errors in CryEngine V code
 
Build microservice with gRPC in golang
Build microservice with gRPC in golangBuild microservice with gRPC in golang
Build microservice with gRPC in golang
 
Event-sourced architectures with Akka - Sander Mak
Event-sourced architectures with Akka - Sander MakEvent-sourced architectures with Akka - Sander Mak
Event-sourced architectures with Akka - Sander Mak
 
Code as Risk
Code as RiskCode as Risk
Code as Risk
 
InterConnect: Server Side Swift for Java Developers
InterConnect:  Server Side Swift for Java DevelopersInterConnect:  Server Side Swift for Java Developers
InterConnect: Server Side Swift for Java Developers
 
Final project powerpoint template (fndprg) (1)
Final project powerpoint template (fndprg) (1)Final project powerpoint template (fndprg) (1)
Final project powerpoint template (fndprg) (1)
 
IBM Cloud University: Java, Node.js and Swift
IBM Cloud University: Java, Node.js and SwiftIBM Cloud University: Java, Node.js and Swift
IBM Cloud University: Java, Node.js and Swift
 
GeeCON 2014 - Functional Programming without Lambdas
GeeCON 2014 - Functional Programming without LambdasGeeCON 2014 - Functional Programming without Lambdas
GeeCON 2014 - Functional Programming without Lambdas
 
Friend this-new&delete
Friend this-new&deleteFriend this-new&delete
Friend this-new&delete
 
Event-sourced architectures with Akka
Event-sourced architectures with AkkaEvent-sourced architectures with Akka
Event-sourced architectures with Akka
 
Flamingo Training - Hello World
Flamingo Training - Hello WorldFlamingo Training - Hello World
Flamingo Training - Hello World
 
Chapter i c#(console application and programming)
Chapter i c#(console application and programming)Chapter i c#(console application and programming)
Chapter i c#(console application and programming)
 
Avoiding Callback Hell with Async.js
Avoiding Callback Hell with Async.jsAvoiding Callback Hell with Async.js
Avoiding Callback Hell with Async.js
 
Workshop: Async and Parallel in C#
Workshop: Async and Parallel in C#Workshop: Async and Parallel in C#
Workshop: Async and Parallel in C#
 
Switch case and looping
Switch case and loopingSwitch case and looping
Switch case and looping
 
Kotlin
KotlinKotlin
Kotlin
 
Rcpp11
Rcpp11Rcpp11
Rcpp11
 

Similar to Writing Go(od) Tests (FOSDEM 2020)

外部環境への依存をテストする
外部環境への依存をテストする外部環境への依存をテストする
外部環境への依存をテストするShunsuke Maeda
 
GDG Devfest 2019 - Build go kit microservices at kubernetes with ease
GDG Devfest 2019 - Build go kit microservices at kubernetes with easeGDG Devfest 2019 - Build go kit microservices at kubernetes with ease
GDG Devfest 2019 - Build go kit microservices at kubernetes with easeKAI CHU CHUNG
 
Chaining and function composition with lodash / underscore
Chaining and function composition with lodash / underscoreChaining and function composition with lodash / underscore
Chaining and function composition with lodash / underscoreNicolas Carlo
 
Introduction to the New Asynchronous API in the .NET Driver
Introduction to the New Asynchronous API in the .NET DriverIntroduction to the New Asynchronous API in the .NET Driver
Introduction to the New Asynchronous API in the .NET DriverMongoDB
 
Chaining et composition de fonctions avec lodash / underscore
Chaining et composition de fonctions avec lodash / underscoreChaining et composition de fonctions avec lodash / underscore
Chaining et composition de fonctions avec lodash / underscoreNicolas Carlo
 
如何透過 Go-kit 快速搭建微服務架構應用程式實戰
如何透過 Go-kit 快速搭建微服務架構應用程式實戰如何透過 Go-kit 快速搭建微服務架構應用程式實戰
如何透過 Go-kit 快速搭建微服務架構應用程式實戰KAI CHU CHUNG
 
History of asynchronous in .NET
History of asynchronous in .NETHistory of asynchronous in .NET
History of asynchronous in .NETMarcin Tyborowski
 
Intro to Asynchronous Javascript
Intro to Asynchronous JavascriptIntro to Asynchronous Javascript
Intro to Asynchronous JavascriptGarrett Welson
 
An Introduction to Property Based Testing
An Introduction to Property Based TestingAn Introduction to Property Based Testing
An Introduction to Property Based TestingC4Media
 
Non Blocking I/O for Everyone with RxJava
Non Blocking I/O for Everyone with RxJavaNon Blocking I/O for Everyone with RxJava
Non Blocking I/O for Everyone with RxJavaFrank Lyaruu
 
Using c++Im also using a the ide editor called CodeLiteThe hea.pdf
Using c++Im also using a the ide editor called CodeLiteThe hea.pdfUsing c++Im also using a the ide editor called CodeLiteThe hea.pdf
Using c++Im also using a the ide editor called CodeLiteThe hea.pdffashiongallery1
 

Similar to Writing Go(od) Tests (FOSDEM 2020) (20)

外部環境への依存をテストする
外部環境への依存をテストする外部環境への依存をテストする
外部環境への依存をテストする
 
GDG Devfest 2019 - Build go kit microservices at kubernetes with ease
GDG Devfest 2019 - Build go kit microservices at kubernetes with easeGDG Devfest 2019 - Build go kit microservices at kubernetes with ease
GDG Devfest 2019 - Build go kit microservices at kubernetes with ease
 
Chaining and function composition with lodash / underscore
Chaining and function composition with lodash / underscoreChaining and function composition with lodash / underscore
Chaining and function composition with lodash / underscore
 
Introduction to the New Asynchronous API in the .NET Driver
Introduction to the New Asynchronous API in the .NET DriverIntroduction to the New Asynchronous API in the .NET Driver
Introduction to the New Asynchronous API in the .NET Driver
 
Chaining et composition de fonctions avec lodash / underscore
Chaining et composition de fonctions avec lodash / underscoreChaining et composition de fonctions avec lodash / underscore
Chaining et composition de fonctions avec lodash / underscore
 
Cocoa heads 09112017
Cocoa heads 09112017Cocoa heads 09112017
Cocoa heads 09112017
 
如何透過 Go-kit 快速搭建微服務架構應用程式實戰
如何透過 Go-kit 快速搭建微服務架構應用程式實戰如何透過 Go-kit 快速搭建微服務架構應用程式實戰
如何透過 Go-kit 快速搭建微服務架構應用程式實戰
 
History of asynchronous in .NET
History of asynchronous in .NETHistory of asynchronous in .NET
History of asynchronous in .NET
 
Server Side Swift: Vapor
Server Side Swift: VaporServer Side Swift: Vapor
Server Side Swift: Vapor
 
C# labprograms
C# labprogramsC# labprograms
C# labprograms
 
gRPC in Go
gRPC in GogRPC in Go
gRPC in Go
 
Func up your code
Func up your codeFunc up your code
Func up your code
 
Intro to Asynchronous Javascript
Intro to Asynchronous JavascriptIntro to Asynchronous Javascript
Intro to Asynchronous Javascript
 
Bot builder v4 HOL
Bot builder v4 HOLBot builder v4 HOL
Bot builder v4 HOL
 
The evolution of asynchronous JavaScript
The evolution of asynchronous JavaScriptThe evolution of asynchronous JavaScript
The evolution of asynchronous JavaScript
 
An Introduction to Property Based Testing
An Introduction to Property Based TestingAn Introduction to Property Based Testing
An Introduction to Property Based Testing
 
Non Blocking I/O for Everyone with RxJava
Non Blocking I/O for Everyone with RxJavaNon Blocking I/O for Everyone with RxJava
Non Blocking I/O for Everyone with RxJava
 
Using c++Im also using a the ide editor called CodeLiteThe hea.pdf
Using c++Im also using a the ide editor called CodeLiteThe hea.pdfUsing c++Im also using a the ide editor called CodeLiteThe hea.pdf
Using c++Im also using a the ide editor called CodeLiteThe hea.pdf
 
Introduction to Swift
Introduction to SwiftIntroduction to Swift
Introduction to Swift
 
RxSwift to Combine
RxSwift to CombineRxSwift to Combine
RxSwift to Combine
 

Recently uploaded

(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...gurkirankumar98700
 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataBradBedford3
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...MyIntelliSource, Inc.
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number SystemsJheuzeDellosa
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Modelsaagamshah0812
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...stazi3110
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEOrtus Solutions, Corp
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfkalichargn70th171
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsAlberto González Trastoy
 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...Christina Lin
 
Introduction to Decentralized Applications (dApps)
Introduction to Decentralized Applications (dApps)Introduction to Decentralized Applications (dApps)
Introduction to Decentralized Applications (dApps)Intelisync
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...OnePlan Solutions
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfkalichargn70th171
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxComplianceQuest1
 
chapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptchapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptkotipi9215
 
why an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfwhy an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfjoe51371421
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityNeo4j
 

Recently uploaded (20)

(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number Systems
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Models
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
 
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
 
Introduction to Decentralized Applications (dApps)
Introduction to Decentralized Applications (dApps)Introduction to Decentralized Applications (dApps)
Introduction to Decentralized Applications (dApps)
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
 
chapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptchapter--4-software-project-planning.ppt
chapter--4-software-project-planning.ppt
 
Exploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the ProcessExploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the Process
 
why an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfwhy an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdf
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered Sustainability
 

Writing Go(od) Tests (FOSDEM 2020)

  • 1. Writing Go(od) Tests Nikki Attea Software Engineer Sensu Inc
  • 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
  • 15.
  • 16.
  • 17.
  • 18.
  • 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
  • 22. main.go -> main_test.go func TestXxx(t *testing.T) { }
  • 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") } }
  • 27. func TestAddCaseAssert(t *testing.T) { cart := NewCart() assert.Equal(t, 0, len(cart.Cases), "expected empty cart") blueLight := FixtureBeer("Labatt", "Blue Light", 12.0) cart.AddCase(FixtureCase(6, blueLight, 10.99)) assert.Equal(t, 1, len(cart.Cases)) }
  • 29. func TestSubtotal(t *testing.T) { cart := NewCart() assert.Equal(t, 0, len(cart.Cases)) duvelHop := FixtureBeer("Duvel", "Tripel Hop", 11.0) cart.AddCase(FixtureCase(4, duvelHop, 14.99)) blueLight := FixtureBeer("Labatt", "Blue Light", 12.0) cart.AddCase(FixtureCase(30, blueLight, 24.99)) assert.Equal(t, 40.0, cart.Subtotal()) }
  • 30. // Subtotal calculates the subtotal of the shopping cart. func (c *Cart) Subtotal() float64 { var subtotal float64 for _, beerCase := range c.Cases { subtotal += beerCase.Price } return subtotal }
  • 31. func TestSubtotal(t *testing.T) { cart := NewCart() assert.Equal(t, 0, len(cart.Cases)) duvelHop := FixtureBeer("Duvel", "Tripel Hop", 11.0) cart.AddCase(FixtureCase(4, duvelHop, 14.99)) blueLight := FixtureBeer("Labatt", "Blue Light", 12.0) cart.AddCase(FixtureCase(30, blueLight, 24.99)) assert.Equal(t, 39.98, cart.Subtotal()) }
  • 32. func TestSubtotalSuite(t *testing.T) { testCases := []struct { name string cart *Cart subtotal float64 }{ { name: "Empty cart", cart: &Cart{}, subtotal: 0, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { assert.Equal(t, tc.subtotal, tc.cart.Subtotal()) }) } }
  • 33. { name: "Party time", cart: &Cart{Cases: []*Case{ FixtureCase(4, FixtureBeer("Duvel", "Tripel Hop", 11.0), 14.99), FixtureCase(30, FixtureBeer("Labatt", "Blue Light", 12.0), 24.99), FixtureCase(30, FixtureBeer("Labatt", "Blue Light", 12.0), 24.99), }}, subtotal: 64.97, }, { name: "Negative", cart: &Cart{Cases: []*Case{ FixtureCase(4, FixtureBeer("Duvel", "Tripel Hop", 11.0), -14), FixtureCase(30, FixtureBeer("Labatt", "Blue Light", 12.0), 24), }}, subtotal: 10.00, },
  • 35. // ProcessPayment sends the total to an external payment api. func ProcessPayment(total float64) ([]byte, error) { b, _ := json.Marshal(total) resp, err := http.Post("https://www.pay-me.com", "application/json", bytes.NewBuffer(b)) if err != nil { return nil, err } defer resp.Body.Close() return ioutil.ReadAll(resp.Body) }
  • 36. // ProcessPayment sends the total to an external payment api. func ProcessPayment(paymentServer string, total float64) ([]byte, error) { b, _ := json.Marshal(total) resp, err := http.Post(paymentServer, "application/json", bytes.NewBuffer(b)) if err != nil { return nil, err } defer resp.Body.Close() return ioutil.ReadAll(resp.Body) }
  • 37. testCases := []struct { name string handler http.HandlerFunc expectedError error expectedBody []byte }{ { name: "OK", handler: func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(`OK`)) w.WriteHeader(http.StatusOK) }, expectedError: nil, expectedBody: []byte(`OK`), }, { name: "Internal server error", handler: func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) }, expectedError: fmt.Errorf("payment server error: %d", http.StatusInternalServerError), }, }
  • 38. for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(tc.handler)) defer ts.Close() body, err := ProcessPayment(ts.URL, 21.11) assert.Equal(t, tc.expectedError, err) assert.Equal(t, tc.expectedBody, body) }) }
  • 39. // ProcessPayment sends the total to an external payment api. func ProcessPayment(paymentServer string, total float64) ([]byte, error) { b, _ := json.Marshal(total) resp, err := http.Post(paymentServer, "application/json", bytes.NewBuffer(b)) if err != nil { return nil, err } defer resp.Body.Close() return ioutil.ReadAll(resp.Body) }
  • 40. // ProcessPayment sends the total to an external payment api. func ProcessPayment(paymentServer string, total float64) ([]byte, error) { b, _ := json.Marshal(total) resp, err := http.Post(paymentServer, "application/json", bytes.NewBuffer(b)) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode >= 400 { return nil, fmt.Errorf("payment server error: %d", resp.StatusCode) } return ioutil.ReadAll(resp.Body) }
  • 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 } } }
  • 44. func TestStartSubscriptionTimer(t *testing.T) { cart1 := &Cart{Cases: []*Case{FixtureCase(4, FixtureBeer("Duvel", "Tripel Hop", 11.0), 14)}} cart2 := &Cart{Cases: []*Case{FixtureCase(30, FixtureBeer("Labatt", "Blue Light", 12.0), 24)}} subscription := &Subscription{ Cart: cart1, Interval: time.Duration(1) * time.Second, messageChan: make(chan interface{}), } go subscription.startSubscriptionTimer(context.Background()) msg := <-subscription.messageChan order, ok := msg.(*Cart) if !ok { t.Fatal("received invalid message on message channel") } assert.Equal(t, cart1, order) subscription.Cart = cart2 msg = <-subscription.messageChan order, ok = msg.(*Cart) if !ok { t.Fatal("received invalid message on message channel") } assert.Equal(t, cart2, order) }
  • 45. // startOrderHandler listens to the message channel and handles incoming orders. func (o *OrderHandler) startOrderHandler(ctx context.Context) { for { msg, ok := <-o.messageChan if !ok { logger.Debug("message channel closed") return } cart, ok := msg.(*Cart) if ok { if err := o.PlaceOrder(ctx, cart); err != nil { logger.WithError(err).Error("error placing order") continue } logger.Info("successfully placed order") continue } logger.WithField("msg", msg).Errorf("received invalid message on message channel") } }
  • 46. func TestStartOrderHandler(t *testing.T) { handler := &OrderHandler{ messageChan: make(chan interface{}), } go handler.startOrderHandler(context.Background()) assert.Equal(t, 0, len(handler.ProcessedOrders)) handler.messageChan <- FixtureCart() handler.messageChan <- FixtureCart() handler.messageChan <- FixtureCase(30, FixtureBeer("Labatt", "Blue Light", 12.0), 24) assert.Equal(t, 2, len(handler.ProcessedOrders)) }
  • 48. race conditions & race detection
  • 49.
  • 50. $ go test -run TestStartSubscriptionTimer -race ================== WARNING: DATA RACE Read at 0x00c00000e5e0 by goroutine 9: github.com/nikkixdev/beer.(*Subscription).startSubscriptionTimer() /Users/nikkixdev/go/src/github.com/nikkixdev/beer/main.go:123 +0xb7 Previous write at 0x00c00000e5e0 by goroutine 8: github.com/nikkixdev/beer.TestStartSubscriptionTimer() /Users/nikkixdev/go/src/github.com/nikkixdev/beer/main_test.go:138 +0x582 testing.tRunner() /usr/local/go/src/testing/testing.go:909 +0x199 Goroutine 9 (running) created at: github.com/nikkixdev/beer.TestStartSubscriptionTimer() /Users/nikkixdev/go/src/github.com/nikkixdev/beer/main_test.go:130 +0x4cf testing.tRunner() /usr/local/go/src/testing/testing.go:909 +0x199 ================== --- FAIL: TestStartSubscriptionTimer (2.01s) testing.go:853: race detected during execution of test FAIL exit status 1 FAIL github.com/nikkixdev/beer 2.387s
  • 51. main.go L123 s.messageChan <- s.Cart main_test.go L138 subscription.Cart = cart2
  • 53. type Subscription struct { cart *Cart interval time.Duration messageChan chan interface{} mu sync.Mutex } // GetCart safely retrieves the subscriptions shopping cart. func (s *Subscription) GetCart() *Cart { s.mu.Lock() defer s.mu.Unlock() return s.cart } // SetCart safely sets the subscriptions shopping cart. func (s *Subscription) SetCart(c *Cart) { s.mu.Lock() defer s.mu.Unlock() s.cart = c }
  • 54. $ go test -run ./... -race PASS ok github.com/nikkixdev/beer 3.113s
  • 55. Thanks for listening! @nikkixdev www.nikki.dev github.com/nikkixdev/beer-testing Resources Go docs: golang.org Special thanks for the gopher illustrations: ● github.com/ashleymcnamara/gophers ● github.com/marcusolsson/gophers ● github.com/MariaLetta/free-gophers-pack