Unit Testing using GIVEN-WHEN-THEN Template(Part 1: Introduction)
Writing a unit test sometimes is not easy and take more times than expected. One of the common methods to help in writing a good test is the Given-When-Then template. The template itself is part of Behavior-driven development (BDD).
What is Unit Testing?
“Unit” can be defined as the smallest complete part of something. In this case, “unit” is a function or method of the system. Hence Unit Testing is a testing scenario for each function or method. Unit testing is automated and considered the cheapest one and the lowest part of the Test Pyramid.
Unit Testing in the overall testing process
Testing can be a complicated process and at least serve 2 purposes, first to ensure the software or system is not crashed (a.k.a System Testing) and to make sure the solution meets the user need (a.k.a User Acceptance Test/UAT)
“Can Unit Testing ensure the system is working overall?” Not really.
“Therefore, is it actually important? The user will test it anyway. It takes more time to write testing than the actual implementation.” Of course, it is important.
Why need to write Unit Testing?
- A safety-net
Unit Testing is mandatory when coming with CI/CD. Without such a mechanism as automated testing as a safety net, who can guarantee we do not deploy a defective service? - Documentation
Unit Testing also can consider better internal documentation than comments. It provides sample input and output, the prerequisite to call the function and dependency - Efficiency
Unit Testing can save a lot of time because the coder only needs to focus on a single unit without worrying it will break another part of the project (Well, at least we know if something is broken). The coder can jump directly to test a specific function/method rather than from the program entry-point (the main() function) - Code Quality
The existence of Unit Testing is one sign of a good project. Unit Testing not only said that the function/method is really working but in the process of writing it, it will affect and drive the entire project to good design.
Test-driven Development
Unit Testing is more than a mere tool to verify/ensure functionality but rather a blueprint of logical thinking. Only if you believe in this then Test-driven Development (TDD) can give its full merit. In short, TDD is about design, not testing.
TDD is a methodology to develop/refactor code driven by tests. The TDD implementation maybe slightly differs from one coder to another but basically writing test and code simultaneously. Both test and code implementation should be committed in a single git commit.
In most cases, TDD implementation begins with writing the test before the code implementation(another name of TDD is Test First Development). This very makes sense since Unit Testing acts as a thought process.
However it no harm in writing the test after code implementation as long as you make sure to deliver self-testing code. You can write the testing on your mind first then start writing the code implementation and write the actual test later. It is almost impossible to write a complete unit test before anything, the whole process is writing the test, code implementation, fixing the test, fixing the implementation, and so on.
This approach is also useful when tackling the production issue, you can write Unit Testing to reproduce the bug before refactoring/fixing it. This trick is part of the self-testing code concept as mentioned by Martin Fowler.
Make specifications for a function
Unit Testing is a thought process before the code, in other words, testing is a specification for the function. However, the function itself has inter-correlation with each other. How do we manage the specification?
function A(){
B(); // call function B
// Do something else
}function B(){
// Do something
}
Let’s say both A()
and B()
spinning a complex operation. What B()
doing will be affected what A()
do next.
“Should we re-specify everything we expect from B()
when writing unit testing for A()
?” Maybe yes but not all.
“What happens we happen if have C()
that called B()
?”
In this case, we can keep continuing re-specify each “child” function which is redundant action and not necessary. We can assume that B()
completely working perfectly or we just focus on the “parent”.
Behaviour-driven Development
Rather than focus on each function, it more makes sense to write on the overall behavioral expectation of the system and this approach called Behaviour-driver Development (BDD).
The idea is to combine Unit Testing with a business perspective. BDD is an extension of TDD yet well suited to Domain-driven Development (DDD). The testing should be a narrative and speak in Ubiquitous Language. One of the recommended patterns for the narrative is Given-When-Then (GWT).
Given-When-Then
This is a typical format of GWT
GIVEN a context
WHEN some condition
THEN expect some output
And this is how we see it on the actual code
With the GWT template, it will be easier to understand what the function/method does. Once you defined Given-When-Then, that means you already finish thinking about the control flow of your process, and eventually, coding will be easier (But this is not as easy as it's said). GWT can be useful when refactoring the code to become more readable.
In the next article, the author will try to give examples of how GWT drives the code refactoring in his favorite language Go.