Tests are necessary for speed
I constantly hear that in order to write tests, you need to increase the time estimate for the task by 1.2, 1.5, 2, 3 times. Usually, I don't argue with this, since the team knows best, but inside me there is often a simmering feeling, or at least a bubbling one. I would like to explain my view on automated tests and why I believe that such an increase is not entirely correct.
How we usually develop
In this section, I mean scenarios where we care at least a little bit about the quality. So, we have some project, let's say it's an API. Let's assume we need to extend that very API. In any case, we need to check the result, that the API works as intended. To do this, after each change, we start the process with the API. We go to some swagger or postman, make a request with the necessary data and check the result.
So... something went wrong here... Okay, I see where it went wrong, fix it. It's working now. Make further changes... and again, it's failing. Not sure where. Let me go to the debugger. Call the debugger, okay, I see what's wrong now, fix it... and again, I need to go and check the API to see if the fix is valid.
At least a few context switches are involved here. It's good if we need to open postman, but if it's a browser. There are notifications there, and suddenly we need to read our email or watch a video on YouTube.
And if we need to prepare heavy data, for which, for example, we need to call other APIs or add rows to the database.
The situation is exacerbated when we work with frontend in the browser. There's a whole wonderful world there. If we're not writing starting pages, we need to navigate through the interface, at least go through the authorization, at most, complete entire user scenarios with multiple steps.
What is the alternative
As the title of the article suggests, the alternative is tests. With a well-tuned environment and process (more on that below), you can easily get into the flow and just write quality code until your mind goes fuzzy. No switching of contexts, no mouse clicks. Get distracted by messengers, email, social networks, and so on only when you feel it's necessary.
The basis of this is continuous feedback from tests. You don't need to switch contexts to understand whether my code works or not.
And you know what? With tests, you can write code without being connected to the internet (if you don't count the absence of Stack Overflow as a blocker). I once wrote a necessary piece of functionality for integrating with a customer team while on the way to the customer, without internet access. When I got to my workplace, I ran the code and it just worked. Before that, I spent 6 hours just writing code.
Of course, not all tests contribute equally to productivity. We all know about the testing pyramid. The higher up the pyramid we climb, the less tests contribute to development productivity and the more they contribute to other characteristics. In other words, unit tests, which many criticize, actually give the greatest productivity boost.
In reality, progress has been made, and integration tests are not bad in this regard at the moment. But there is a catch. It's important to remember that setting up integration tests will take a little more time. You can't just sit down and write them. In most cases, you need to start some process and maybe bring up a couple of dependencies in Docker. But overall, when this stage is passed, writing integration tests also helps speed up the process.
Tests as a result of planning
I don't know about you, but when I write code, I often have two problems:
- The need to finish pedaling if the task goes beyond the working day and remember where to continue the next day.
- The need to get distracted by a meeting, lunch, etc., and then successfully return and continue.
As you can see, everything boils down to the same thing: you need to be able to stop and continue from where you left off.
And this is where tests come in handy. If you write tests, you can stop at any time and continue the next day.
Some simple rules for development and environment setup to make speed really fast
1. Learn to use the IDE, preferably without a mouse
I mostly use IDEs from JetBrains (Project Rider, IntelliJ IDEA, WebStorm). One of the interesting techniques that I use in these IDEs is generating code from tests. That is, instead of creating some design in code and then writing tests for it, I start writing tests directly. A good start is the test "How I want to use what I need to do". Right in the test, we start using something that doesn't exist yet. Then the magic of the IDE begins, which is good to learn and bring to automation. The IDE highlights that what I'm using in the test doesn't exist yet. Since I already like using it, I just press ALT+ENTER and voila, it offers to create different language primitives, classes, interfaces, functions, components, etc.
And I don't refuse the IDE's offer :)
We happily agree to its suggestions. After that, right in the tests we get something that can already be called. Then we repeat the steps with different other tests. When the result looks like something big, for example, we generated several classes or something finished, for example, there is already quite functional code, we can move the code to where it should reside. Press CTRL+SHIFT+O (move to file command, you may have other combinations), choose where and that's it, the IDE itself does the rest.
All this is to say that using an IDE greatly simplifies writing tests and code, and also affects speed, at least you don't have to pick up the mouse and find the right place in the interface to click on something.
2. Set up tests to be easy to run or restart
Important for all the same focus and getting constant feedback.
As a little in the previous section - modern IDEs can do this quite successfully. After making changes to the code, you need to see whether the tests passed or failed, which tests failed, and how the situation has changed since the last run. And you need to restart often, otherwise you can miss important feedback after the next change, and you will have to spend time debugging. Without this, the process will not be complete. If it takes separate efforts to run the tests, the increase in ratings due to testing will gradually return.
The ideal scenario is when the tests restart themselves on a change. Made a change - didn't press anything, and the tests reported to you that everything is good or something needs to be fixed.
3. Configure tests to provide quick feedback
Another important point is to make sure that the feedback is fast enough. As mentioned in the previous section, feedback is important after every small change in the code. But what if there are a lot of tests or they run very slowly, and the feedback takes half a minute, a minute, or more.
First - make sure that the hardware on which the software is being developed is sufficient to quickly run tests.
Second - make sure that the tests are parallelized and utilize the available hardware resources.
Third - run tests in parts. This can be implemented by structurally dividing the project and the tests components and running only the tests that relate to the current module, package, or language-supported feature. Another way is to run tests based on coverage. There are quite a few tools that calculate test coverage and build a dependency map based on it. That is, which pieces of code are touched during the execution of a particular test. Then, based on this, only the tests that affect the modified area are run. Examples of such tools:
- JetBrains Project Rider can restart tests based on .NET code coverage.
- NCrunch, a somewhat expensive tool, but it also allows you to set up a continuous optimized feedback loop from tests.
- WallabyJS - a tool for JS that analyzes code coverage and skillfully restarts tests. Plus, it has additional features such as REPL from tests.
Yes, these examples are paid, but it is an investment in your own productivity. Buying JetBrains tools for the last 6 years, I have never regretted the money spent on them.
4. Write tests so that it is easy to understand where and why they fail
Test_positive failed with reason expected true to be false.
Doesn't make sense, right?
Especially when compared to the following string:
Plain_should_have_needed_attitude_right_after_start failed with reason expected plain.attitude to be 500m but got 120m
When a test fails with the result from the first example, it will most likely require debugging. In the second case, you can assume the reason for the failure and immediately start fixing it, getting further feedback from the tests.
5. Make tests an integral part of development
The worst thing you can do is write code and tests as separate stages, for example, have separate tasks for writing code and writing tests.
This is an extremely unproductive approach. Such an approach will certainly justify a x3 increase in estimates to write tests.
Why is this bad? Because tests as a separate stage simply do not provide the value described above, they do not provide feedback after each change in the code. And as we have already established, feedback is crucial to stay focused and productive.
Ideally, practice TDD. The process is designed for feedback, small iterations, and is a formal approach that can be learned and followed. If, for some reason, TDD does not seem suitable, at least try to write tests along with the code so that the tests answer the question, "does the code I just wrote do what I intended it to do?"
I believe that if tests are written as a separate stage, it's better not to do it, because it's a waste of time. It's best to spend it on tests that are higher up the pyramid, such as end-to-end tests.
Conclusion
From my experience, I tried to describe a vision of how to stop multiplying estimates by some constant just for the sake of writing tests. This is not something that should be done to hide the cost of tests, but to show that tests are actually a tool for efficiency, provided that the development process is set up correctly. If the development process is set up properly, then over a short period of development, estimates will not increase, but decrease simply because there is no need to click endless user flows to fix a small bug - it is enough to write a few tests and verify that everything works as intended.
I deliberately did not focus on other characteristics that improve or worsen tests, as this is the subject of other conversations.
In this same article, I tried to show the effect of tests on development speed. I hope that this will help you to understand that tests are not a burden, but a tool that can help you to be more productive.