Tell me if this sounds familiar - you are learning how to test in Go, and things seem to be going great. The tutorials are all clicking, and you can't wait to start applying what you are learning in your real projects.
You fire up your editor, grab your latest project, create your first
*_test.go
source file, and suddenly it feels like you don't have a clue what you
are doing.
*You hear the sound of glass shattering*
Things were going so great. All those examples made sense, but now you don't even know where to start.
It seemed so easy to test that "Hello, world" HTTP handler, but how do you test complex handlers? You know, HTTP handlers that do something realistic like insert a record into a database, or use an API to verify someone's address.
For that matter, how do we verify that our database interactions are
working as we expected? Or maybe your app has a global
DB
variable - does that mean testing simply isn't
possible?
What about those APIs we are interacting with? Do we stub them? Do we hit the test API? What happens if we hit API rate limits or there isn't even a test API?
Alright, alright! Take a second to breathe and let me fill you in on a little secret...
Testing isn't any harder than writing any other Go code. In fact, if
we wanted we could test our code by just writing a
main
package and interacting with our application code.
We could panic when something doesn't act the way we expected, and
viola - we have a test!
But why does it feel so hard? Probably because we simplify the examples to the point that they lose all of their value.
Think about it, when is the last time you wrote a
Palindrome
function?
Never? So why are all these tutorials showing us how to test one?
Why aren't they showing us how to test realistic software? What happened to the example where we test a real HTTP handler that needs access to a data store? Or the tutorial where we build an API client and learn how to test it WITHOUT always hitting the real API. Wouldn't it be great if we could learn how to test a real web app with a real DB and a real integration to a payments API like Stripe?
In this course you will learn how to test REAL software, not palindrome functions.
We will have to look at a few isolated examples in order to learn specific testing techniques, but that isn't enough to solidify a concept so we won't stop there. We will build real projects that teach you how to apply all of these testing skills in real software.
In one project we build a web application which allows us to address the complexities that come up while testing an application that uses a database, third party APIs, and more. In another project we look at how internal testing helps us verify our intermediate steps are correct, while also discussing the downside to testing unexported functions.
You will learn about common pitfalls to avoid in order to write more testable code. You will learn how to incrementally fix code that has already succumbed to many of these pitfalls, allowing you to avoid a massive PR that makes your reviewer cry inside.
When you run into an application with a global DB variable you won't need to give up on testing. You will learn exactly how to make small, manageable changes to the code that allow you to start testing it almost immediately.
The next time you are asked whether the data store should be mocked or if a real SQL database should be used you will be able to discuss the pros and cons of both approaches with your teammates and decide on a proper plan of action.
After completing this course you will have the knowledge and the skills necessary to start testing your own projects. You will still have to put in the work, but the mystery, the confusion, and the frustration will be gone.
In short, you will be on your way to becoming the de facto testing expert on your team. You will be on the path to making your team's software a happier, healthier place to spend your day. Heck, you might even be on your way to a raise or a better offer! π
or learn about the course below
Test with Go is broken into two major sections: Lessons and Projects
Put together, the lessons and projects span 173 videos and total over 33 hours of content.
In the lessons we focus on learning the techniques necessary to test our applications. We will learn the basics, like how to write your first test and what table driven testing is, but we will also cover more advanced testing techniques like:
We will spend some time using small, isolated examples in order to learn each technique, but those will gradually become more realistic as you become familiar with testing. Before long we will be looking at tests that use a real SQL database, test helpers that enable us to test HTTP endpoints that require authentication, and more.
In the projects we will take everything we learned in the lessons and practice applying them while building real software. We will look at some of the most common mistakes you can make when designing an application, as well as how to gradually refactor your code to make it more testable. We will see first-hand how to separate your integration and unit tests, allowing you to write tests for an API client that can both be run locally and online with the real API. We will even look at how to export some of those helper functions, making it easier for others who use your libraries to write tests.
More can be seen in the individual Lessons and Projects sections below.
The lessons consist of 88 videos that will gradually walk you from the most basic exercise - like writing your first test - all the way to advanced techniques like interface test suites, dependency injection, and more.
All of the lessons are broken into sections, making it easier to quickly jump to the subject you want to learn or review. That means in a few years when you want a quick reminder on testing subprocesses you can quickly find the section you need and get back to testing your code with minimal downtime.
What is a test?
Why do tests matter?
Writing great tests
Testing with a main package
Testing with Gos testing package
What happens when we run go test
File naming conventions
Function naming conventions
Variable naming conventions
Ways to signal test failure
When to use Error vs Fatal
Writing useful failure messages
A basic example as a test case
Viewing examples in the docs
Unordered example output
Complex examples
Examples in the standard library
Table driven tests
Generating table driven test code
Subtests
Shared setup and teardown
TestMain
Running tests in parallel
Parallel subtests
Setup and teardown with parallel subtests
Gotchas with closures and parallel tests
What is a race condition
The race detection flag
Testing explicitly for race conditions
Simple comparisons
Reflect's DeepEqual function
Golden files (brief overview)
Helper comparison functions
Building things with helper functions
Generating test data
Gos quick testing package
Public testing utilities
Running specific tests
Running tests for subpackages
Skipping tests
Custom flags
Build tags
Benchmarks
Verbose testing
Code coverage
The timeout flag
Parallel testing flags
Differences between external and internal
How to write internal and external tests
When to use external tests
Exporting unexported vars, funcs, and types
When to use internal tests
Overview of test types
Unit tests
Integration tests
End-to-end tests
Which test type should I use
What is global state
Testing with global state (if you must)
What is dependency injection
DI enables implementation agnostic code
DI makes testing easier
DI and useful zero values
Removing global state with DI
Package level functions
Summary of DI
What is mocking
Types of mock objects
Why do we mock
Third party packages
Faking APIs
What are interface test suites
Interface test suite setup and teardown
Interface test suites in the wild
httptest.ResponseRecorder
httptest.Server
Build HTTP helpers
What are golden files
Updating golden files
What is a subprocess
Running the subprocess in tests
Mocking simple subprocesses
Mocking complex subprocesses
Why are dates and times problematic?
Inject your time and sleep functions
Testing timeouts
Colorizing your terminal output
Coverage info function
There are three projects in this course:
form
- a Go package (AKA a
library) used to generate HTML forms from Go structs
stripe
- an API client used to
interact with a few of the
Stripe
payment API endpoints
swag
- a web application that
allows users to order sticker packs using both the
form
and
stripe
packages we create in the first two projects
Each project is built from the ground up with the goal of teaching you about testing in real software. For instance, we will intentionally make mistakes that make our code hard to test then explore ways to make it more testable. Or in other instances we might discuss the tradeoffs of one approach vs another before ultimately moving forward and writing any code.
The videos for each project are shown below, and if you have any questions don't hesitate to reach out and ask.
*The projects are only included in the COMPLETE package
form
A Go package (AKA a library) used to generate HTML forms from Go structs
01. Topics covered in the form project
02. The first test
03. Our first bug
04. Handling multiple fields
05. Field values
06. Checking for specific attributes in a test
07. Unexported fields
08. Non-structs are invalid
09. Pointers to structs
10. Supporting more types
11. Generating HTML
12. Discussing struct tags and tests
13. Parsing struct tags
14. Applying struct tags
15. Golden test files
16. Struct tag tests in TestHTML
17. Rendering errors
18. Rendering errors
19. Detecting breaking changes with tests
stripe
An API client used to interact with a few of the Stripe payment API endpoints
01. The first test
02. Creating a customer
03. Versioning our client
04. Making the API key a flag
05. Improving our customer data
06. The charge endpoint
07. Custom error type
08. Parsing stripe errors
09. Customer endpoint errors
10. Starting on unit tests
11. Allowing customer http clients
12. Creating a recorder client
13. Persisting recorded responses
14. Making our tests cross-platform
15. Serving recorded responses
16. Unique customer per charge subtest
17. Adding tests for specific errors
18. Helper functions
swag
In this project we explore how we might approach adding tests and refactoring a web application that wasn't designed with testing in mind. We look at how to incrementally add tests and make changes so we avoid breaking any functionality or needing to make massive overhauls to the code.
The web app we add tests to allows users to order sticker packs
using both the form
and
stripe
packages we create
in the first two projects
01. What to expect
02. App overview
03. Initial db tests
04. Creating the db.Open function
05. What about mocks
06. Test harnesses and helpers
07. Reviewing tests
08. Testing specific times
09. First pass at refactoring the db pkg
10. Updating db tests
11. Testing the order flow
12. Extracting code for unit testing
13. Extracting the active campaign handler
14. Unit testing the active campaign handler
15. Table driven testing the active campaign handler
16. Refactoring campaign middleware
17. Unit testing the campaign middleware
18. Starting the orders handler
19. Testing the new order handler
20. Refactor Create order handler
21. Test: Create order handler
22. Integration testing with Stripe
23. Testing for specific Stripe failures
24. Another form of table driven tests
25. Refactor: Order middleware
26. Test: Order middleware
27. Refactor: Show order handler
28. Test: Show order handler
29. Don't get too clever
30. Integration testing the show order handler
31. Removing sql from the confirm order handler
32. Testing the database confirm order function
33. Refactor: Confirm order handler
34. Test: Confirm order handler with same address
35. Test: Confirm order handler when campaign isnt found
36. Refactoring and finishing the confirm order handler unit tests
37. Integration test: Confirm order handler
38. Setting the stripe secret key via ENV variable
39. Refactoring the routing code in main
40. Testing our asset directory
41. Mocks for testing our router
42. Testing the show order route
43. Table driven router testing
44. Additional router testing
45. Removing the tempDB type
46. Removing the DefaultDatabase package variable
47. Cleanup
48. Wrapping up
In addition to individual packages, I also offer team packages. These include everything in the complete package, but you get a discount for buying multiple copies. You can reach out to discuss a team package that fits your team's needs.
I offer a 30 day money back guarantee. If you are unsure about the course, I recommend purchasing the package you feel is best and streaming a few of the videos to see if the course is right for you. If it isn't, send me an email within 30 days and I'll issue you a refund.
This only applies to the first time you purchase a course. I do not offer multiple refunds for the same course, so if you purchase a course, request a refund, then purchase it again I will not offer a second refund. This is because I am still charged the CC processing fee on a refund, and it isn't reasonable for me to eat that cost multiple times.
I may also refuse a refund in extreme cases. For instance, if you buy the course, download a large portion of the videos, then request a refund. I have had issues with theft in the past, so I reserve the right to refuse a refund in cases like this.
If you have any questions about this policy, please reach out before purchasing. I am genuinely trying to help people and will try to work with anyone, but a few bad actors have unfortunately made it hard to offer refunds without some restrictions.
The course doesn't expire. Once you purchase, you will always have access to the videos.
If you are worried, the complete package includes a way to download all the content so that you can back everything up on your own devices as well. You know, just in case I get hit by a truck or something. π *beep* *beep*
The videos are hosted with Vimeo and are streamable through their embedded player. If you purchase the complete package I also offer DRM-free, high quality 1920x1080 mp4s that you can download.
The short answer? There isn't one.
We use TDD some in the projects, but it isn't taught as a standalone concept. That said, everything taught in this course can be applied to TDD with relative ease.
Yes, students of any kind (high school, college, bootcamp, whatever else) can send me an email with any sort of proof you are a student and I'll send you a link to purchase the course with the student discount.
Yes, I offer team packages. These include everything in the complete package, but you get a discount for buying multiple copies. Reach out for more info - jon@calhoun.io.
Jon Calhoun is a full stack web developer who teaches about Go, web development, testing, algorithms, and anything else he finds interesting. He spoke at the GothamGo conference about focusing on simplicity when writing software and is a panelist on the Go Time podcast.
Previously, Jon founded EasyPost, a shipping API that many fortune 500 companies use to power their shipping infrastructure. Before that he worked at Google as a software engineer. Before that he studied computer science at UCF.
You can find more of Jon's work, including other FREE courses and tutorials he has created, below.