Home >Backend Development >Golang >Test-driven API Development in Go
Test-driven development is an effective method for ensuring well-tested and refactorable code. The basic idea is that you start development by writing tests. These tests clearly document expectations and create a rubric for a successful implementation. When done properly, you can clearly define the expected input/output of a function before writing any code. This has a few immediate benefits:
Now that you're convinced of the benefits, you can get started with test-driven development (TDD) by following these steps:
These steps are followed in a cycle so you are always adding more tests to challenge the current implementation.
The last step, which specifies writing the minimum amount of code, is where things can get tedious if followed rigidly. It's important to understand why this rule exists before you can determine when it's appropriate to stray from it.
You're tasked with implementing the function Add(x, y int) int. Before you jump to the implementation and just return x + y, write the simplest test: 1 + 1 == 2. Then, what is the simplest implementation that would pass the test? It's just return 2. Now your tests pass!
At this point, you realize that you need more tests, so you pick up the pace and add a few more:
Now your tests fail, so you need to fix the implementation. You can't just return 3 or return 105 this time, so you need to find a solution that works for all tests. This leads to the implementation: return x + y.
While this feels overly tedious in the trivial example, strict adherence to this method caused you to write multiple tests instead of just trusting your implementation. Of course, your initial idea to return x + y would have worked, but the point is to re-train yourself to rely on tests rather than your own understanding of the code. In the real world, you're not the only one working on this piece of code and will inevitably forget implementation details. This process forces you to write more tests and think of more ways to break the simple implementation.
Eventually, you'll gain experience and learn to find the balance that works in the different scenarios that you encounter. You'll get back to full-speed implementation of features and find that you have fewer bugs and write more maintanable code.
Let's get into a more complicated example using TDD for an HTTP REST API. This step-by-step guide uses my Go framework, babyapi, but the concepts can be applied anywhere.
babyapi uses generics to create a full CRUD API around Go structs, making it super easy to create a full REST API and client CLI. In addition to this, the babytest package provides some tools for creating end-to-end API tables tests. Using TDD at the API-level allows for fully testing the HTTP and storage layers of a new API or feature all at once.
Disclaimer: Since babyapi handles most of the implementation and also is used to generate test boilerplate, we aren't technically starting with TDD. However, we'll see how beneficial it is when adding support for PATCH requests to our API.
Create a new Go project
Create initial main.go using babyapi's simple example
Run the tests and see that they pass!
This test fails since babyapi doesn't support PATCH by default. We can fix it by implementing Patch for the TODO struct. Since we defined our feature with two tests, our simplest implementation isn't just setting Completed = true and we have to use the value from the request
Now we can change the Completed status of a TODO, but we still cannot use PATCH to modify other fields as show by this new set of tests
Update Patch to set the remaining fields
Our tests still fail since we always update the TODO with the request fields, even if they're empty. Fix this by updating the implementation to check for empty values
The new UpdateWithPatch test passes, but our previous tests fail. Since we changed Completed to be *bool, TODOs created with an empty value will show as null
Implement Render for TODO so we can treat nil as false
Implementing the PATCH feature with test-driven development resulted in a robust set of tests and a well-implemented feature. Since we started by defining the expected input and output of a PATCH request in tests, it was easy to see the issues caused by not checking for empty values in the request. Also, our pre-existing tests were able to protect from breaking changes when changing the type of Completed to *bool.
Test-driven development is an effective approach for creating fully tested and correct code. By starting with tests in mind, we can ensure that every piece of code is designed to be testable instead of letting tests be an afterthought.
If you're hesitant about adopting TDD, here are a few ideas to get started:
Even if TDD isn't a good fit for the way you write code, it's still a powerful tool to have in your belt. I encourage you to at least commit some time to trying it out and see how it affects your development process.
The above is the detailed content of Test-driven API Development in Go. For more information, please follow other related articles on the PHP Chinese website!