Learn Go with test-driven development

Learn Go with Tests

Art by Denise

Build Status Go Report Card

Formats

Translations

Support me

I am proud to offer this resource for free, but if you wish to give some appreciation:

Why

Table of contents

Go fundamentals

  1. Install Go - Set up environment for productivity.
  2. Hello, world - Declaring variables, constants, if/else statements, switch, write your first go program and write your first test. Sub-test syntax and closures.
  3. Integers - Further Explore function declaration syntax and learn new ways to improve the documentation of your code.
  4. Iteration - Learn about for and benchmarking.
  5. Arrays and slices - Learn about arrays, slices, len, varargs, range and test coverage.
  6. Structs, methods & interfaces - Learn about struct, methods, interface and table driven tests.
  7. Pointers & errors - Learn about pointers and errors.
  8. Maps - Learn about storing values in the map data structure.
  9. Dependency Injection - Learn about dependency injection, how it relates to using interfaces and a primer on io.
  10. Mocking - Take some existing untested code and use DI with mocking to test it.
  11. Concurrency - Learn how to write concurrent code to make your software faster.
  12. Select - Learn how to synchronise asynchronous processes elegantly.
  13. Reflection - Learn about reflection
  14. Sync - Learn some functionality from the sync package including WaitGroup and Mutex
  15. Context - Use the context package to manage and cancel long-running processes
  16. Intro to property based tests - Practice some TDD with the Roman Numerals kata and get a brief intro to property based tests
  17. Maths - Use the math package to draw an SVG clock

Build an application

Now that you have hopefully digested the Go Fundamentals section you have a solid grounding of a majority of Go's language features and how to do TDD.

This next section will involve building an application.

Each chapter will iterate on the previous one, expanding the application's functionality as our product owner dictates.

New concepts will be introduced to help facilitate writing great code but most of the new material will be learning what can be accomplished from Go's standard library.

By the end of this, you should have a strong grasp as to how to iteratively write an application in Go, backed by tests.

  • HTTP server - We will create an application which listens to HTTP requests and responds to them.
  • JSON, routing and embedding - We will make our endpoints return JSON and explore how to do routing.
  • IO and sorting - We will persist and read our data from disk and we'll cover sorting data.
  • Command line & project structure - Support multiple applications from one code base and read input from command line.
  • Time - using the time package to schedule activities.
  • WebSockets - learn how to write and test a server that uses WebSockets.

Questions and answers

I often run in to questions on the internets like

How do I test my amazing function that does x, y and z

If you have such a question raise it as an issue on github and I'll try and find time to write a short chapter to tackle the issue. I feel like content like this is valuable as it is tackling people's real questions around testing.

  • OS exec - An example of how we can reach out to the OS to execute commands to fetch data and keep our business logic testable/
  • Error types - Example of creating your own error types to improve your tests and make your code easier to work with.
  • Context-aware Reader - Learn how to TDD augmenting io.Reader with cancellation. Based on Context-aware io.Reader for Go
  • Revisiting HTTP Handlers - Testing HTTP handlers seems to be the bane of many a developer's existence. This chapter explores the issues around designing handlers correctly.

Meta / Discussion

  • Why - Watch a video, or read about why unit testing and TDD is important
  • Intro to generics - Learn how to write functions that take generic arguments and make your own generic data-structure
  • Anti-patterns - A short chapter on TDD and unit testing anti-patterns

Contributing

  • This project is work in progress If you would like to contribute, please do get in touch.
  • Read contributing.md for guidelines
  • Any ideas? Create an issue

Background

I have some experience introducing Go to development teams and have tried different approaches as to how to grow a team from some people curious about Go into highly effective writers of Go systems.

What didn't work

Read the book

An approach we tried was to take the blue book and every week discuss the next chapter along with the exercises.

I love this book but it requires a high level of commitment. The book is very detailed in explaining concepts, which is obviously great but it means that the progress is slow and steady - this is not for everyone.

I found that whilst a small number of people would read chapter X and do the exercises, many people didn't.

Solve some problems

Katas are fun but they are usually limited in their scope for learning a language; you're unlikely to use goroutines to solve a kata.

Another problem is when you have varying levels of enthusiasm. Some people just learn way more of the language than others and when demonstrating what they have done end up confusing people with features the others are not familiar with.

This ends up making the learning feel quite unstructured and ad hoc.

What did work

By far the most effective way was by slowly introducing the fundamentals of the language by reading through go by example, exploring them with examples and discussing them as a group. This was a more interactive approach than "read chapter x for homework".

Over time the team gained a solid foundation of the grammar of the language so we could then start to build systems.

This to me seems analogous to practicing scales when trying to learn guitar.

It doesn't matter how artistic you think you are, you are unlikely to write good music without understanding the fundamentals and practicing the mechanics.

What works for me

When I learn a new programming language I usually start by messing around in a REPL but eventually, I need more structure.

What I like to do is explore concepts and then solidify the ideas with tests. Tests verify the code I write is correct and documents the feature I have learned.

Taking my experience of learning with a group and my own personal way I am going to try and create something that hopefully proves useful to other teams. Learning the fundamentals by writing small tests so that you can then take your existing software design skills and ship some great systems.

Who this is for

  • People who are interested in picking up Go.
  • People who already know some Go, but want to explore testing with TDD.

What you'll need

  • A computer!
  • Installed Go
  • A text editor
  • Some experience with programming. Understanding of concepts like if, variables, functions etc.
  • Comfortable with using the terminal

Feedback

MIT license

Logo is by egonelbre What a star!

Comments
  • I would like to translate the project to Spanish

    I would like to translate the project to Spanish

    I've been looking for guides on Testing with GO because I've kind of neglected it in the past, the best way to learn and reassure some knowledge is to teach others.

    Although the content is already written, translating it and giving it some context in Spanish is still a challenge.

    I will do it if given the permission by the owner.

    I can devote a couple hours a day, so I hopefully should be done pretty soon!

  • Maps chapter

    Maps chapter

    I would love feedback on this and potentially including it a WIP chapter so I could get more feedback.

    Resolves https://github.com/quii/learn-go-with-tests/issues/47

  • database/sql chapter (addresses #103)

    database/sql chapter (addresses #103)

    Hi!

    Thank you for this amazing book!

    I've decided to tackle the database/sql chapter (addressing #103). I took the initial code you had in the databases branch and modified it.

    It's turning out to be reaaaally wordy.

    The plan

    My plan was to:

    1. Give a brief introduction to databases and why anyone would need one.
    2. Add installation instructions for the DB of choice (PostgreSQL).
    3. Explain database migrations, why they're important, and how to write a simple go program to handle them (no offloading to libraries), testing along the way.
    4. Create CRUD operations for the bookshelf model, writing unit and integration tests along the way.
    5. Close with an explanation that while integration tests are slow, this is what CI is really meant for. Then explain briefly what CI is and how it improves the development cycle.

    Inquiries

    • You mention brevity in the contributing guidelines, I feel I'm breaking this rule by being so verbose. Yet databases are a deep and complex subject with a lot of branches; it feels inappropriate to start the chapter without some context. I've also added a lot of external links for further reading.
      • Should I hold back on the links? Maybe add a section at the end titled "further reading"?
    • I ended up writing a lot of non-go code so far, mainly in the database setup. If this is unwelcome I can offload it to external guides. Please advise.
    • The Database Migrations section is turning out to be a bit overlong, I feel that adding that and then a section on CRUD methods, with unit and integration tests, would turn out to be a very very long chapter.
      • Should it be split into several chapters instead?
        • If this would be the case, I believe naming the subsection "Integration tests" would be more appropriate.
      • In case it's too long, the migrations part can be gutted entirely, and focus only on the CRUD and database operation.

    Thanks!

  • We would like to translate the project into Chinese

    We would like to translate the project into Chinese

    Hello, I'm a gopher, I founded the Go Chinese community https://studygolang.com in 2013. In order to better promote the go language, the introduction of foreign excellent information to China, in November 2017, I organized the GCTT (Go Chinese Translation Team). 2 months ago, I saw your articles about TDD, and GCTT start translating, and later discovered https://github.com/quii/learn-go-with-tests this project, thanks for providing such a good project, GCTT wants to translate it into Chinese, I wonder if we can do that? Looking forward to your reply. Thank you!

  • Question recommended setup for working through this

    Question recommended setup for working through this

    I created a folder and started working through the code examples.

    I got to here https://github.com/quii/learn-go-with-tests/tree/master/integers#examples

    And realised that godoc wont work for this example as the code is outside of the GOPATH.

    Is there a recommendation on how you should setup your project / directory structure / location that will work with the tutorial?

    Thanks, Damian.

  • I want to translate the project into Korean

    I want to translate the project into Korean

    Hello. I want to translate this amazing project into Korean.

    I already found an issue that translate into Korean, but I saw it stopped since 1 years ago. Therefore, I posted issue where the Korean translation was originally proceed.

    I'm already doing the translation and it's expected to be finished within a month.

    github.com/MiryangJung/learn-go-with-tests-ko

    I will request for help from the Korea Developers Community.

    If the original translator allows the translation, Can I proceed?

    Thank you.

  • Increase consistency of math tutorial with other tutorials

    Increase consistency of math tutorial with other tutorials

    Thanks for the tutorial, @gypsydave5!

    I noticed various inconsistencies with the math tutorial as compared to the other tutorials in terms of test output display and package organization. Specifically:

    • Instead of displaying entire test display output, I change it to only show the single line and report ("clockface_test.go:17: Got {0 0}, wanted {150 60}"). I could see the motivation for including the line above that line, since it includes the test name, but in my opinion, the additional lines underneath just add noise and indentation that decrease the tutorial readability.
    • Some of the tests seem to assume they're in a separate package from the functionality they're testing (clockface.SecondHand). I change them to assume they're in same package.
    • I trimmed the output for tests passing output. The other tutorials don't display tests passing output, so I wasn't sure what would be consistent, I'm not sure this change of mine is helpful/accurate.

    I'll add in-line comments as well.

  • map chapter

    map chapter

    We've got a few chapters in progress that uses maps but have not actually explained them!

    Could be a potentially interesting chapter as they can be used in a number of ways

    • Key/Value store (obviously)
    • Sets

    Could cover that they are not safe for concurrent use

  • goroutine leak in the

    goroutine leak in the "select" chapter

    Hello,

    in the select chapter, the ping function

    func ping(url string) chan bool {
        ch := make(chan bool)                 // <==== HERE
        go func() {
            http.Get(url)
            ch <- true
        }()
        return ch
    }
    

    introduces 1 or 2 goroutine leaks (depending on when the timeout expires) when used in the select statement in Racer:

    func Racer(a, b string, timeout time.Duration) (winner string, error error) {
        select {
        case <-ping(a):
            return a, nil
        case <-ping(b):
            return b, nil
        case <-time.After(timeout):
            return "", fmt.Errorf("timed out waiting for %s and %s", a, b)
        }
    }
    

    The fix is to follow the approach used by time.After, which has a channel with buffer length 1.

    The code below (self-contained, ready to run) shows how to reproduce:

    // https://quii.gitbook.io/learn-go-with-tests/go-fundamentals/select
    package main
    
    import (
    	"flag"
    	"fmt"
    	"net/http"
    	"net/http/httptest"
    	"runtime"
    	"time"
    )
    
    // FIXME as-it, it leaks:
    // - one of the ping goroutines, if the timeout expires after
    // - both the ping goroutines, if the timeout expires before
    func Racer(a, b string, timeout time.Duration, chBufLen int) (string, error) {
    	select {
    	case <-ping(a, chBufLen):
    		return a, nil
    	case <-ping(b, chBufLen):
    		return b, nil
    	case <-time.After(timeout):
    		return "", fmt.Errorf("timed out (%v) waiting for %v and %v", timeout, a, b)
    	}
    }
    
    func ping(URL string, chBufLen int) chan struct{} {
    	// The fix is to add a buffer of 1 to the channel (as for example is done by time.After)
    	ch := make(chan struct{}, chBufLen)
    	go func() {
    		http.Get(URL)
    		ch <- struct{}{}
    	}()
    	return ch
    }
    
    func main() {
    	var chBufLen = flag.Int("chbuflen", 0, "Ping channel buffer lenght")
    	flag.Parse()
    
    	server := newDelayedServer(10 * time.Millisecond)
    	defer server.Close()
    
    	for {
    		fmt.Printf("%v\n", runtime.NumGoroutine())
    		Racer(server.URL, server.URL, 1000*time.Millisecond, *chBufLen)
    		time.Sleep(500 * time.Millisecond)
    	}
    }
    
    func newDelayedServer(delay time.Duration) *httptest.Server {
    	return httptest.NewServer(http.HandlerFunc(
    		func(w http.ResponseWriter, r *http.Request) {
    			time.Sleep(delay)
    			w.WriteHeader(http.StatusOK)
    		}))
    }
    

    Example output:

    $ ./webrace
    2
    9
    10
    11
    12
    13
    14
    ...
    
    $ ./webrace -chbuflen 1
    2
    8
    8
    8
    8
    8
    ...
    
  • Select example: the program will wait all channel finish. it not return when the fast one finished

    Select example: the program will wait all channel finish. it not return when the fast one finished

    when i change the select example ,make the delay time to 20 second. image the test will take 20s to finish . but as the tutor , it should not,it should return as soon as the faster one finished

    but when i replace select with switch .it can return when the fast one finished why?

  • Fix PDF generation with SVG

    Fix PDF generation with SVG

    As of 10.0.1 I have disabled PDF generation as the content has changed in a way which currently breaks it.

    • maths chapter added some SVG which doesn't work with the current version of pandoc the build script is using
    • tried updating but ended up using a different docker image, but that one didn't have language support for the Chinese characters
  • Fine tune language, punctuation, and markdown formatting

    Fine tune language, punctuation, and markdown formatting

    👋 Hello - thanks for maintaining learn-go-with-tests. This PR seeks to make a few tweaks...

    • properly format the markdown blockquote and link at https://quii.gitbook.io/learn-go-with-tests/go-fundamentals/mocking#mocking-1
    • fine tune some language to be a bit more clear
    • include previously missing commas and periods
    • correctly use the two word verb phrase "clean up" rather than the one-word noun ("cleanup")
    • correct comma placement

    If I'm not mistaken, there are a number of other similar grammar and spelling errors throughout learn-go-with-tests too. I've attempted to keep this PR relatively-ish small and easy to review, but if you're receptive to the changes, I'm happy to continue opening similar follow-on PRs (or to add additional commits to this PR, if you prefer).

  • Arrays and Slices. A better refactor can be given

    Arrays and Slices. A better refactor can be given

    In this code (link: https://github.com/quii/learn-go-with-tests/blob/main/arrays-and-slices.md#write-enough-code-to-make-it-pass-4):

    func SumAllTails(numbersToSum ...[]int) []int {
    	var sums []int
    	for _, numbers := range numbersToSum {
    		if len(numbers) == 0 {
    			sums = append(sums, 0)
    		} else {
    			tail := numbers[1:]
    			sums = append(sums, Sum(tail))
    		}
    	}
    
    	return sums
    }
    

    We can refactor it, so we can omit the else block by introducing the statement continue:

    func SumAllTails(numbersToSum ...[]int) []int {
    	var sums []int
    	for _, numbers := range numbersToSum {
    		if len(numbers) == 0 {
    			sums = append(sums, 0)
                            continue
    		}
    	       tail := numbers[1:]
    	       sums = append(sums, Sum(tail))	
    	}
    
    	return sums
    }
    
  • context.md assertWasCancelled() and assertWasNotCancelled

    context.md assertWasCancelled() and assertWasNotCancelled

    Hello, Not sure if I'm simply misunderstanding the following. The text reads:

    type SpyStore struct {
    	response  string
    	cancelled bool
    	t         *testing.T
    }
    
    func (s *SpyStore) assertWasCancelled() {
    	s.t.Helper()
    	if !s.cancelled {
    		s.t.Error("store was not told to cancel")
    	}
    }
    
    func (s *SpyStore) assertWasNotCancelled() {
    	s.t.Helper()
    	if s.cancelled {
    		s.t.Error("store was told to cancel")
    	}
    }
    

    Should the Errors be reversed? In the text, it seems like it is saying "if not cancelled, throw an error because the store was not told to cancel". Is it intended to say: "if not cancelled, throw an error because the store was told to cancel"?

    type SpyStore struct {
    	response  string
    	cancelled bool
    	t         *testing.T
    }
    
    func (s *SpyStore) assertWasCancelled() {
    	s.t.Helper()
    	if !s.cancelled {
    		s.t.Error("store was told to cancel")
    	}
    }
    
    func (s *SpyStore) assertWasNotCancelled() {
    	s.t.Helper()
    	if s.cancelled {
    		s.t.Error("store was not told to cancel")
    	}
    }
    

    Thanks for your time

  • "hello world" text should explain the difference between `:=` vs. `=`

    First of all! Huge thank you for this amazing material! I'm new to TDD and Golang and I'm learning a lot! :heart:


    As a person who has some years of experience with other programming languages but zero knowledge about Golang, I was a little surprised/confused when I saw statements like these ones:

      prefix := englishHelloPrefix
      prefix = spanishHelloPrefix
    

    I googled and learned that we use:

    In order to prevent people from having to leave the book to research this information, I suggest to put a short explanation about that in the text of the "Hello, World" chapter.

    This can be especially enlightening for people coming from some interpreted languages background, where there's no clear difference between "declaration" and "assignment".

Behaviour Driven Development tests generator for Golang
Behaviour Driven Development tests generator for Golang

gherkingen It's a Behaviour Driven Development (BDD) tests generator for Golang. It accept a *.feature Cucumber/Gherkin file and generates a test boil

Dec 16, 2022
Simple test driven approach in "GOLANG"
Simple test driven approach in

Testing in GOLANG Usage Only test go test -v Coverage go test -cover or go test -coverprofile=coverage.out go tool cover -html=coverage.out Benchmark

Dec 5, 2021
go-test-trace is like go test but it also generates distributed traces.
go-test-trace is like go test but it also generates distributed traces.

go-test-trace go-test-trace is like go test but it also generates distributed traces. Generated traces are exported in OTLP to a OpenTelemetry collect

Jan 5, 2023
Flugel Test Documentation for steps to run and test the automatio
Flugel Test Documentation for steps to run and test the automatio

Flugel Test Documentation Documentation for steps to run and test the automation #Test-01 1 - Local Test Using Terratest (End To End) 1- By runing " t

Nov 13, 2022
Test-assignment - Test assignment with golang
Test-assignment - Test assignment with golang

test-assignment We have a two steam of data and we need to save it in the map: I

Jan 19, 2022
testcase is an opinionated behavior-driven-testing library

Table of Contents testcase Guide Official API Documentation Getting Started / Example Modules Summary DRY Modularization Stability Case Study About te

Nov 10, 2022
Markdown based document-driven RESTful API testing.
Markdown based document-driven RESTful API testing.

silk Markdown based document-driven web API testing. Write nice looking Markdown documentation (like this), and then run it using the silk command Sim

Dec 18, 2022
A yaml data-driven testing format together with golang testing library

Specimen Yaml-based data-driven testing Specimen is a yaml data format for data-driven testing. This enforces separation between feature being tested

Nov 24, 2022
Fundamental-Go - A comprehensive and FREE Online Go Development tutorial going step-by-step into the world of Go
Fundamental-Go - A comprehensive and FREE Online Go Development tutorial going step-by-step into the world of Go

FREE Reverse Engineering Self-Study Course HERE Fundamental Go The book and code

Mar 18, 2022
Test your command line interfaces on windows, linux and osx and nodes viá ssh and docker

Commander Define language independent tests for your command line scripts and programs in simple yaml files. It runs on windows, osx and linux It can

Dec 17, 2022
Run a real Postgres database locally on Linux, OSX or Windows as part of another Go application or test
Run a real Postgres database locally on Linux, OSX or Windows as part of another Go application or test

embedded-postgres Run a real Postgres database locally on Linux, OSX or Windows as part of another Go application or test. When testing this provides

Dec 27, 2022
End to end functional test and automation framework
End to end functional test and automation framework

Declarative end to end functional testing (endly) This library is compatible with Go 1.12+ Please refer to CHANGELOG.md if you encounter breaking chan

Jan 6, 2023
Test your code without writing mocks with ephemeral Docker containers 📦 Setup popular services with just a couple lines of code ⏱️ No bash, no yaml, only code 💻

Gnomock – tests without mocks ??️ Spin up entire dependency stack ?? Setup initial dependency state – easily! ?? Test against actual, close to product

Dec 29, 2022
go-carpet - show test coverage in terminal for Go source files
go-carpet - show test coverage in terminal for Go source files

go-carpet - show test coverage for Go source files To view the test coverage in the terminal, just run go-carpet. It works outside of the GOPATH direc

Jan 8, 2023
http integration test framework

go-hit hit is an http integration test framework written in golang. It is designed to be flexible as possible, but to keep a simple to use interface f

Dec 29, 2022
Go testing in the browser. Integrates with `go test`. Write behavioral tests in Go.
Go testing in the browser. Integrates with `go test`. Write behavioral tests in Go.

GoConvey is awesome Go testing Welcome to GoConvey, a yummy Go testing tool for gophers. Works with go test. Use it in the terminal or browser accordi

Dec 30, 2022
A Go test assertion library for verifying that two representations of JSON are semantically equal
A Go test assertion library for verifying that two representations of JSON are semantically equal

jsonassert is a Go test assertion library for verifying that two representations of JSON are semantically equal. Usage Create a new *jsonassert.Assert

Jan 4, 2023
Ruby on Rails like test fixtures for Go. Write tests against a real database

testfixtures Warning: this package will wipe the database data before loading the fixtures! It is supposed to be used on a test database. Please, doub

Jan 8, 2023
A tool for generating self-contained, type-safe test doubles in go

counterfeiter When writing unit-tests for an object, it is often useful to have fake implementations of the object's collaborators. In go, such fake i

Jan 5, 2023