SlideShare a Scribd company logo
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 code
PVS-Studio
 
Build microservice with gRPC in golang
Build microservice with gRPC in golangBuild microservice with gRPC in golang
Build microservice with gRPC in golang
Ting-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 Mak
NLJUG
 
Code as Risk
Code as RiskCode as Risk
Code as Risk
Kevlin Henney
 
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
Chris 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 Swift
Chris Bailey
 
GeeCON 2014 - Functional Programming without Lambdas
GeeCON 2014 - Functional Programming without LambdasGeeCON 2014 - Functional Programming without Lambdas
GeeCON 2014 - Functional Programming without Lambdas
Mattias Severson
 
Friend this-new&delete
Friend this-new&deleteFriend this-new&delete
Friend this-new&delete
Shehzad Rizwan
 
Event-sourced architectures with Akka
Event-sourced architectures with AkkaEvent-sourced architectures with Akka
Event-sourced architectures with Akka
Sander Mak (@Sander_Mak)
 
Flamingo Training - Hello World
Flamingo Training - Hello WorldFlamingo Training - Hello World
Flamingo Training - Hello World
i-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.js
cacois
 
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
 
Kotlin
KotlinKotlin
Rcpp11
Rcpp11Rcpp11

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 ease
KAI 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 / underscore
Nicolas 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 / underscore
Nicolas Carlo
 
Cocoa heads 09112017
Cocoa heads 09112017Cocoa heads 09112017
Cocoa heads 09112017
Vincent Pradeilles
 
如何透過 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 .NET
Marcin Tyborowski
 
Server Side Swift: Vapor
Server Side Swift: VaporServer Side Swift: Vapor
Server Side Swift: Vapor
Paweł Kowalczuk
 
gRPC in Go
gRPC in GogRPC in Go
gRPC in Go
Almog Baku
 
Func up your code
Func up your codeFunc up your code
Func up your code
Maciej Komorowski
 
Intro to Asynchronous Javascript
Intro to Asynchronous JavascriptIntro to Asynchronous Javascript
Intro to Asynchronous Javascript
Garrett Welson
 
Bot builder v4 HOL
Bot builder v4 HOLBot builder v4 HOL
Bot builder v4 HOL
Cheah Eng Soon
 
The evolution of asynchronous JavaScript
The evolution of asynchronous JavaScriptThe evolution of asynchronous JavaScript
The evolution of asynchronous JavaScript
Alessandro Cinelli (cirpo)
 
An Introduction to Property Based Testing
An Introduction to Property Based TestingAn Introduction to Property Based Testing
An Introduction to Property Based Testing
C4Media
 
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
Frank 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.pdf
fashiongallery1
 
Introduction to Swift
Introduction to SwiftIntroduction to Swift
Introduction to Swift
Matteo Battaglio
 
RxSwift to Combine
RxSwift to CombineRxSwift to Combine
RxSwift to Combine
Bo-Young Park
 

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

Webinar: Salesforce Document Management 2.0 - Smarter, Faster, Better
Webinar: Salesforce Document Management 2.0 - Smarter, Faster, BetterWebinar: Salesforce Document Management 2.0 - Smarter, Faster, Better
Webinar: Salesforce Document Management 2.0 - Smarter, Faster, Better
XfilesPro
 
Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...
Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...
Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...
Globus
 
BoxLang: Review our Visionary Licenses of 2024
BoxLang: Review our Visionary Licenses of 2024BoxLang: Review our Visionary Licenses of 2024
BoxLang: Review our Visionary Licenses of 2024
Ortus Solutions, Corp
 
Using IESVE for Room Loads Analysis - Australia & New Zealand
Using IESVE for Room Loads Analysis - Australia & New ZealandUsing IESVE for Room Loads Analysis - Australia & New Zealand
Using IESVE for Room Loads Analysis - Australia & New Zealand
IES VE
 
How Recreation Management Software Can Streamline Your Operations.pptx
How Recreation Management Software Can Streamline Your Operations.pptxHow Recreation Management Software Can Streamline Your Operations.pptx
How Recreation Management Software Can Streamline Your Operations.pptx
wottaspaceseo
 
Accelerate Enterprise Software Engineering with Platformless
Accelerate Enterprise Software Engineering with PlatformlessAccelerate Enterprise Software Engineering with Platformless
Accelerate Enterprise Software Engineering with Platformless
WSO2
 
Cyaniclab : Software Development Agency Portfolio.pdf
Cyaniclab : Software Development Agency Portfolio.pdfCyaniclab : Software Development Agency Portfolio.pdf
Cyaniclab : Software Development Agency Portfolio.pdf
Cyanic lab
 
Providing Globus Services to Users of JASMIN for Environmental Data Analysis
Providing Globus Services to Users of JASMIN for Environmental Data AnalysisProviding Globus Services to Users of JASMIN for Environmental Data Analysis
Providing Globus Services to Users of JASMIN for Environmental Data Analysis
Globus
 
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptx
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptxTop Features to Include in Your Winzo Clone App for Business Growth (4).pptx
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptx
rickgrimesss22
 
Globus Compute Introduction - GlobusWorld 2024
Globus Compute Introduction - GlobusWorld 2024Globus Compute Introduction - GlobusWorld 2024
Globus Compute Introduction - GlobusWorld 2024
Globus
 
Lecture 1 Introduction to games development
Lecture 1 Introduction to games developmentLecture 1 Introduction to games development
Lecture 1 Introduction to games development
abdulrafaychaudhry
 
In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...
In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...
In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...
Juraj Vysvader
 
RISE with SAP and Journey to the Intelligent Enterprise
RISE with SAP and Journey to the Intelligent EnterpriseRISE with SAP and Journey to the Intelligent Enterprise
RISE with SAP and Journey to the Intelligent Enterprise
Srikant77
 
SOCRadar Research Team: Latest Activities of IntelBroker
SOCRadar Research Team: Latest Activities of IntelBrokerSOCRadar Research Team: Latest Activities of IntelBroker
SOCRadar Research Team: Latest Activities of IntelBroker
SOCRadar
 
May Marketo Masterclass, London MUG May 22 2024.pdf
May Marketo Masterclass, London MUG May 22 2024.pdfMay Marketo Masterclass, London MUG May 22 2024.pdf
May Marketo Masterclass, London MUG May 22 2024.pdf
Adele Miller
 
First Steps with Globus Compute Multi-User Endpoints
First Steps with Globus Compute Multi-User EndpointsFirst Steps with Globus Compute Multi-User Endpoints
First Steps with Globus Compute Multi-User Endpoints
Globus
 
Navigating the Metaverse: A Journey into Virtual Evolution"
Navigating the Metaverse: A Journey into Virtual Evolution"Navigating the Metaverse: A Journey into Virtual Evolution"
Navigating the Metaverse: A Journey into Virtual Evolution"
Donna Lenk
 
Vitthal Shirke Microservices Resume Montevideo
Vitthal Shirke Microservices Resume MontevideoVitthal Shirke Microservices Resume Montevideo
Vitthal Shirke Microservices Resume Montevideo
Vitthal Shirke
 
Custom Healthcare Software for Managing Chronic Conditions and Remote Patient...
Custom Healthcare Software for Managing Chronic Conditions and Remote Patient...Custom Healthcare Software for Managing Chronic Conditions and Remote Patient...
Custom Healthcare Software for Managing Chronic Conditions and Remote Patient...
Mind IT Systems
 
A Comprehensive Look at Generative AI in Retail App Testing.pdf
A Comprehensive Look at Generative AI in Retail App Testing.pdfA Comprehensive Look at Generative AI in Retail App Testing.pdf
A Comprehensive Look at Generative AI in Retail App Testing.pdf
kalichargn70th171
 

Recently uploaded (20)

Webinar: Salesforce Document Management 2.0 - Smarter, Faster, Better
Webinar: Salesforce Document Management 2.0 - Smarter, Faster, BetterWebinar: Salesforce Document Management 2.0 - Smarter, Faster, Better
Webinar: Salesforce Document Management 2.0 - Smarter, Faster, Better
 
Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...
Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...
Innovating Inference - Remote Triggering of Large Language Models on HPC Clus...
 
BoxLang: Review our Visionary Licenses of 2024
BoxLang: Review our Visionary Licenses of 2024BoxLang: Review our Visionary Licenses of 2024
BoxLang: Review our Visionary Licenses of 2024
 
Using IESVE for Room Loads Analysis - Australia & New Zealand
Using IESVE for Room Loads Analysis - Australia & New ZealandUsing IESVE for Room Loads Analysis - Australia & New Zealand
Using IESVE for Room Loads Analysis - Australia & New Zealand
 
How Recreation Management Software Can Streamline Your Operations.pptx
How Recreation Management Software Can Streamline Your Operations.pptxHow Recreation Management Software Can Streamline Your Operations.pptx
How Recreation Management Software Can Streamline Your Operations.pptx
 
Accelerate Enterprise Software Engineering with Platformless
Accelerate Enterprise Software Engineering with PlatformlessAccelerate Enterprise Software Engineering with Platformless
Accelerate Enterprise Software Engineering with Platformless
 
Cyaniclab : Software Development Agency Portfolio.pdf
Cyaniclab : Software Development Agency Portfolio.pdfCyaniclab : Software Development Agency Portfolio.pdf
Cyaniclab : Software Development Agency Portfolio.pdf
 
Providing Globus Services to Users of JASMIN for Environmental Data Analysis
Providing Globus Services to Users of JASMIN for Environmental Data AnalysisProviding Globus Services to Users of JASMIN for Environmental Data Analysis
Providing Globus Services to Users of JASMIN for Environmental Data Analysis
 
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptx
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptxTop Features to Include in Your Winzo Clone App for Business Growth (4).pptx
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptx
 
Globus Compute Introduction - GlobusWorld 2024
Globus Compute Introduction - GlobusWorld 2024Globus Compute Introduction - GlobusWorld 2024
Globus Compute Introduction - GlobusWorld 2024
 
Lecture 1 Introduction to games development
Lecture 1 Introduction to games developmentLecture 1 Introduction to games development
Lecture 1 Introduction to games development
 
In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...
In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...
In 2015, I used to write extensions for Joomla, WordPress, phpBB3, etc and I ...
 
RISE with SAP and Journey to the Intelligent Enterprise
RISE with SAP and Journey to the Intelligent EnterpriseRISE with SAP and Journey to the Intelligent Enterprise
RISE with SAP and Journey to the Intelligent Enterprise
 
SOCRadar Research Team: Latest Activities of IntelBroker
SOCRadar Research Team: Latest Activities of IntelBrokerSOCRadar Research Team: Latest Activities of IntelBroker
SOCRadar Research Team: Latest Activities of IntelBroker
 
May Marketo Masterclass, London MUG May 22 2024.pdf
May Marketo Masterclass, London MUG May 22 2024.pdfMay Marketo Masterclass, London MUG May 22 2024.pdf
May Marketo Masterclass, London MUG May 22 2024.pdf
 
First Steps with Globus Compute Multi-User Endpoints
First Steps with Globus Compute Multi-User EndpointsFirst Steps with Globus Compute Multi-User Endpoints
First Steps with Globus Compute Multi-User Endpoints
 
Navigating the Metaverse: A Journey into Virtual Evolution"
Navigating the Metaverse: A Journey into Virtual Evolution"Navigating the Metaverse: A Journey into Virtual Evolution"
Navigating the Metaverse: A Journey into Virtual Evolution"
 
Vitthal Shirke Microservices Resume Montevideo
Vitthal Shirke Microservices Resume MontevideoVitthal Shirke Microservices Resume Montevideo
Vitthal Shirke Microservices Resume Montevideo
 
Custom Healthcare Software for Managing Chronic Conditions and Remote Patient...
Custom Healthcare Software for Managing Chronic Conditions and Remote Patient...Custom Healthcare Software for Managing Chronic Conditions and Remote Patient...
Custom Healthcare Software for Managing Chronic Conditions and Remote Patient...
 
A Comprehensive Look at Generative AI in Retail App Testing.pdf
A Comprehensive Look at Generative AI in Retail App Testing.pdfA Comprehensive Look at Generative AI in Retail App Testing.pdf
A Comprehensive Look at Generative AI in Retail App Testing.pdf
 

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