15
Concept of unit-tests writing in Golang
Unit Tests in most cases are associated with a lot of wasted time.
A test is an imitation of a user browsing the page.
Finding errors during the testing stage improves the user experience. Duh. Testing automatically catches bugs and thus builds the business’ credibility.
However, I do agree that writing tests is a long and in most cases boring process. Since usually, I am the one who writes almost all tests for my colleagues, I can tell that writing tests is more complicated than writing code.
Writing code is straightforward since you always see the final result you aim to achieve. However, writing tests involve designing all possible scenarios, analyzing data, and uncovering non-obvious use cases. Ideally, you want to test everything, even the smallest details immediately.
Even after doing it for years, to this day I still frequently get confused by loads of ideas and cannot choose immediately the needed scenario. So, I have developed some concepts for test writing. They facilitate the entire procedure and prevent me (and possibly you) from missing or forgetting something important. I believe they might be useful for you, too.
The first concept is the most important one. I divided it into two main aspects: detailed planning and proper time management.
Let’s start with the planning. Don’t start writing tests immediately after a new code line was added. In many cases, you will notice small details that need to be modified after a while. So, take a break and recheck the code once more. Make sure it is completed. Only after that, you can start writing a test.
Such an approach will help you to save plenty of time because only when the code is completed, only then you see how the page works, you can imagine the scenarios in which something can go wrong.
The second component of my concept 1 is time management.
Well, in the majority of cases, developers explain the absence of tests and testing with the lack of time. This is a straightforward path to unmanageable technical debt.
Testing is an important part of developing reliable, scalable software that solves users’ problems. It can be compared to your safety belt. You either find a bug by running a test and fix it or the same will be discovered by users and your customer support will cry in tears. Every reported bug major or minor influences’s business’ bottom line. Why use your buggy product, when the competitor product’s user experience is flawless? So plan your time properly, and covering the code with tests is key.
Obviously, you won’t be able to start testing immediately after finishing the dev part. However, taking small steps and taking an incremental approach is crucial in creating a reliable foundation for your project. Add a new scenario every day, and little by little, you will be covering your code with tests. Finally, when you are ready to present your job, the code will be covered in tests. The better and the more detailed your scenarios are, the fewer bugs will be detected later.
The more tests you run, the better it is.
As I have already mentioned, the more tests you have written, the fewer bugs the code is going to have. You can write a couple of general tests to check whether the program runs but ignore the feature set. This is not the best approach though. Sometimes, a single detail can ruin everything that you have done. I have had such cases and they taught that the devil is in the details.
I am not going to describe the cases in detail. I will just mention that in one case, the danger of the bug was in parallelism. One channel was sending data faster than the other, and it caused an error.
In the second case, the bug was not so evident. Only when I added a fault scenario, I found out where the bug was.
That`s why I am repeating it constantly: cover everything in tests.
Do you believe that a bug might hide here? - Write a test.
Do you doubt that the RPC request works properly? - Write a test or even several tests to make sure that all problems are discovered and eliminated.
Do you doubt the function set? - Yep, you know the solution. Write several scenarios to check the function set properly.
Do not be afraid of test writing. I agree that it might seem a boring and time-demanding process. You have to write the same things by changing the smallest details. But these details are the basis for the best code. These details mean that your code is tested thoroughly and you haven`t left any chance for an error.
The third concept is the most demanding and time-consuming. It is connected with the choice of testing format.
We all know that a code is written once but read multiple times. So, it has to be written clearly. People who will read it shall understand what it is about without checking loads of documents and asking for help. Such a code is also maintained and changed easily.
The same can be told about tests. But in the case of tests, you shall pay attention to one tricky detail. Even if your code might be crystal clear, it doesn't mean that it is easy to read. Most likely, dozens of scenarios were written before. The number of code lines can exceed one thousand, and only less than a half might be written by you. In such a case, all the previous scenarios and versions shall be included in testing. Think carefully about and even write down at least the main scenarios to see how you are going to test the code. Ask yourself the following questions:
- WHAT do you want to test?
- HOW do you want to test it?
- WHY do you want to test it?
Now, have a look at what you have already done. Think about what is next. Finally, ask yourself one more question: HOW can I write easy and clear tests?
I will give you a hint as to what the reply is. Everything is about names. The name of a scenario, the name of a stud, and so on - give names to everything. From the test name, it shall be clear what it is for and what one can expect from it. And when a bug occurs, the report will be very accurate, to the extent that you will even be indicated at the code line where the bug has occurred. If you give names to everything, you and others will find it easy to understand and read your code.
One more detail makes the test writing processes boring. Studs shall be written almost identically. So, you can facilitate the task for yourself and those people who are going to read and maybe complete your code in the future.
So, all studs will have something in common. All of them will have the same request and a reply. It can be modified in some parts but the meaning is unchanged. For example, you have RPC requests which are repeated. So, you can check what those small differences between them are and then, write the request and reply function in a common file to which everybody has access.
The differences between these requests and replies can be indicated in parameters (at least in static languages with which I work. Though it might be different in the case of dynamic languages). These details, the differences, will also solve the issue of different scenarios. Depending on the scenario, you indicate the needed function parameter, and then, the stud will function as expected.
The same principle is applied to test incoming data. Such tests will be similar, the differences will exist only in the tested line.
So, why shall you write one test several times if you can re-use the ready test? There is a reason why programmers have the DRY (Don`t Repeat Yourself) and KISS (Keep It Simple Stupid) concepts. These concepts allow to minimize the code in tests, the code becomes more readable, and if needed, you can easily write a couple of scenarios by making slight modifications that will not influence the other tests.
For example, have a look here:
{
name: “page-rendered-sucessfully”,
request: genHTTPRequest(“https://example.com/seo?page=1", t),
recorder: httptest.NewRecorder(),
reqMocker: successMockCalls,
validaters: successValidater,
Here, you see how it all works. Two moc functions request the same function but parameters and replies are different. So, you can increase the test coverage by keeping the test volume unchanged. The main thing here is to indicate the correct parameters for a request and a reply.
Now, let us summarize everything. Yep, test writing is not the most pleasant process especially if you don't have time for it. But test writing will save you a lot of time in the future. When you are testing your code, you detect problems and fix bugs. That's why test writing and testing are mandatory components of any development process.
My colleagues ask me to write tests even if they modified just a couple of lines. Otherwise, their code is rejected.
When a team lead and CTO see tests that check whether the added code functions correctly, they accept the code. Therefore, never ignore tests. They make you confident in your code and you will not let your team down.
Previously published at maddevs.io/blog
15