tdd in go with ginkgo and gomega

Post on 05-Jul-2015

294 Views

Category:

Software

0 Downloads

Preview:

Click to see full reader

DESCRIPTION

An overview of TDD concepts, and an exploration of these concepts in Go using the Ginkgo and Gomega libraries.

TRANSCRIPT

TDD in Go with

Ginkgo and GomegaEddy Reyes - eddy@containable.io

http://www.tasqr.io

About Me

Eddy Reyes. Cofounder of Tasqr

(www.tasqr.io)

● Automates application deployment /

configuration without code or config files.

Previously hacked lots of code at:

● IBM, Lifesize, Click Security

Overview of Presentation

● Explain what TDD iso Not necessarily proselytizing that you must do it.

o I dislike many TDD-related presentations, they don’t

match my experience, they don’t stress what’s

important IMO.

● What Tools Does Go Have to Help You Do

TDD?o Ginkgo and Gomega, many others

Further TDD Reading

Uncle Bob Martin● http://blog.8thlight.com/uncle-bob/archive.html

Kent Beck● Extreme Programming Explained (book)

Is TDD Dead? (video debates)● Kent Beck, Martin Fowler, DHH

● https://www.youtube.com/watch?v=z9quxZsLcfo (part 1)

What Isn’t TDD?

● NOT just having written tests

● NOT a way to feel like you’re going fast

● NOT the Silver Bullet that saves software

engineering

● NOT like Axe, which makes you irresistible

to the opposite sex :-)

What Is TDD?

● A process you follow when writing software.

● The process goes like this:o You write a test that defines an expected outcome in

your program.

o You watch the test fail.

o You change the program until your test passes.

● Adding tests to an already substantial

untested program isn’t necessarily TDD.

How Do You Do TDD?

● In order to effectively follow the TDD pattern,

you must first understand your design.o Tests are rigid. Learning is free-form.

o You learn by hacking and breaking things.

o Tests ensure things don’t break.

● TDD helps you SHIP, not LEARN

Learn vs. Ship

● Learning is free-form exploration.

Experiment, play, break things.o DO NOT SHIP THIS CODE!

o After you learn your design, step away from the

keyboard, and start anew.

● Shippingo Code ends up in a product used by customers.

o Quality is important!

Tests Are Design!

● If tests are comprehensive, they are a formal

specification of your program.

● Writing a test is expressing the design of

your program’s functionality.

● To truly serve as a spec, tests must be:o Clean, readable

o Follow some consistent notation

Test/Spec Notation

GIVEN

● Precondition 1...N

WHEN

● Call into your program

THEN

● Assert Condition 1...N

A Test Framework Needs...

● Clean, simple notation

● Clean setup/teardown of dependencieso Idempotent, independent tests!

● Clean separation from tested code

● Simple runner mechanism

● Good reportingo test results

o coverage maps

Testing in Go

● Test runnero go test

● Separates test code from producto *_test.go

● Comes with a test writing libraryo “testing” package

● Comes with coverage reporting toolso Beautiful HTML output

“testing” Package

● Allows your to write test functions.

● Nice benchmarking utility

● Tests are basically blocks of code :(func TestXXX(t *testing.T) {

// setup stuff

// call your code

if something.State != WhatIWant {

t.Fail()

}

}

Better Test-Writing Library

● go command has great tooling.

● Need a better notation for specifying tests.

Ginkgo and Gomega is a step forward!

… but not perfect!

Ginkgo and Gomega

● Ginkgoo Compatible with “go test” but also comes with its

own test runner

o BDD-style test notation

o Expressive structuring of tests

● Gomegao “Matching” library

o Allows for expressive assert-like statements

Ginkgo

● Tests expressed as English statementso In the BDD tradition of Cucumber, et. al.

var _ = Describe(“My component”, func() {

It(“Does something special”, func() {

MyFunction()

Expect(MyVar).To(Equal(10))

})

})

Gomega

● Assertion languageExpect(number).To(Equal(10))

Expect(value).To(BeNumerically(“<”, 20))

Expect(someMap).To(Equal(otherMap)) // does reflect.DeepEqual()

Expect(someMap).To(BeEquivalent(otherMap)) // allows different types

Expect(myPtr).ToNot(BeNil())

Expect(flag).To(BeFalse())

Expect(err).ToNot(HaveOccurred())

Expect(err).To(MatchError(“my error message”))

Ginkgo and Dependencies● setup/teardown:

var _ = Describe(“my config object”, func() {

var (

configObj *config.MyConfig

tempDir string

)

BeforeEach(func() {

var err error

tempDir, err = ioutil.TempDir(“”, “test-dir”)

Expect(err).ToNot(HaveOccurred())

configObj = config.NewConfig()

})

Ginkgo and DependenciesIt(“Saves itself to file”, func() {

configObj.Save(tempDir)

restoredConfig := config.FromFile(configObj.Filename)

Expect(*restoredConfig).To(Equal(*configObj))

})

AfterEach(func() {

os.RemoveAll(tempDir)

})

}) // end Describe()

Ginkgo and Dependencies

● Ginkgo heavily relies on closures and

closure variables to manage dependencies.o Resist the temptation to share state among tests!

● Nested dependencieso Within a Describe block, you can nest:

Context block

More It’s and Before/AfterEach’s

Nested Dependenciesvar _ = Describe(“My component”)

var configObj config.MyConfig

BeforeEach(func() {

configObj = config.NewConfig()

})

It(“Does test1”, func() {

// configObj is setup

})

Nested DependenciesContext(“With a fake HTTP server configured”, func() {

var testServer httptest.Server

BeforeEach(func() {

testServer = httptest.NewServer(<fake handler>)

})

It(“Does test2”, func() {

configObj.ServerUrl = testServer.URL

configObj.DoSomething() ...

})

})

Testing Notation + Ginkgo

● Obviously, Ginkgo was not designed with my

exact notation in mind…

● However, it maps to it without too much

difficulty.

Testing Notation + Ginkgovar _ = Describe(“my component”, func() {

// GIVEN

BeforeEach(func () {

})

It(“Does something useful”, func() {

// WHEN

CallSomething()

// THEN

Expect(stuff).To((Equal(something))

})

})

Alternate Notation Stylevar _ = Describe(“my component”, func() {

BeforeEach(func() {...}) //

GIVEN

It(“Does something useful”, func() { // WHEN

closureVar = CallSomething()

})

AfterEach(func() {

Expect(closureVar).To(Equal(something)) // THEN

})

})

Ginkgo Bootstrap

● Ginkgo integrates with go test

● You must use the ginkgo command to

generate boilerplate code:

$ cd mypackage/ # within $GOPATH/src

$ ginkgo bootstrap

$ ls

mypackage_suite_test.go

Ginkgo Bootstrappackage mypackage_test // NOT THE SAME AS mypackage!!!

import (

. "github.com/onsi/ginkgo"

. "github.com/onsi/gomega"

"testing"

)

func TestConfig(t *testing.T) { // go test integration boilerplate!

RegisterFailHandler(Fail)

RunSpecs(t, "Mypackage Suite")

}

Ginkgo Generate

● The generated suite calls the generated test

files:$ ginkgo generate

$ ls

mypackage_suite_test.go mypackage_test.go

$ cat mypackage_test.go

...

Ginkgo Generatepackage mypackage_test // NOT THE SAME AS mypackage!!

import (

. “mypackage”

. “github.com/onsi/ginkgo”

. “github.com/onsi/gomega”

)

var _ = Describe(“Mypackage test”, func() {

})

Assessing Ginkgo/Gomega

Test Notation

● The BDD-style english annotations help

readability a little bit...

● Closures are a slippery slope!o Too much code: declare closure vars, must also

initialize in BeforeEach to ensure test

idempotency!

Assessing Ginkgo/Gomega

● Complex dependencies are doableo Accomplished by nesting Describe/Context/

BeforeEach’s

o However, your tests must be inserted in ever-more

nested closures

Can get kinda complicated!

● Gomega assertionso Flow nicely when read aloud

o Too complex as a formal notation

Assessing Ginkgo/Gomega

● Overall test notation grade: C+/B-o Depends on my mood and how complicated my test

needs to get.

o Worst part about it is that your test file easily grows

way too long, and thus harder to comprehend as a

spec.

● Good enough to get our job done with high

quality at Tasqr.

Go TDD Roundup

Questions?

top related