How to convince a developer to start testing
Let’s take a look at some of the reasons why programmers don’t write automated tests. Of course, this will not be a complete list. I just wanted to point out that most of the excuses are usually associated with a lack of understanding of the essence of automated testing, lack of knowledge of effective testing technologies, poor design of the tested code, etc.
Our experience tells us that this is the only way it seems at the time of writing the code. After some time (say, six months, or maybe even a couple of weeks), the connections of this code with another may not seem so obvious, and the code itself may not be so simple.
Of course, you have to be prudent. After all, tests are not an end in themselves, but a means for shaping the design and checking the performance of the code. You don’t have to test everything thoroughly. Personally, I often do not write tests for non-standard situations, although I write the code that checks these situations. Someone is not at all embarrassed by the increasing amount of code that needs to be reworked in the event of a change in the tested class.
Perhaps it’s all about the level of testing. Unit testing is primarily an activity aimed at shaping the design of a code. If a developer writes code that only configures the parent class or organizes several classes into one, then you need not a unit test, but a functional one. You can feel good about this edge only if you have gained some experience in development and testing.
The developers point out that it is much more difficult to write tests than the code under test itself.
There may be 2 reasons for this:
Low qualification of the developer.
A sign of poor design of the code under test.
We’ll deal with the first point a little later, but for now let’s pay attention to poor design.
At the initial stage of introducing testing into our practice, we had to constantly face a situation when it was almost impossible to write a test for any class. It was necessary to register a user, add a bunch of records to the database, have a dozen configuration files available, etc. Of course, there was little testing experience, but poor system design was the main reason. Because of this, it was difficult to write new tests, they broke very often. There was no need to talk about writing a test before implementation. The reason lay in only one thing – the classes were very dependent on each other, sometimes in a very cunning way. As a result, the system quickly became ossified, resisted changes and gradually became covered with “mold”.
Then we tried to change our tactics and try to test the simplest classes with the minimum number of dependencies. This approach is very well suited, for example, for validation rules, template system components, utility classes. Each test and class-tested was small, and even after a lot of time, it was easy to understand what was being tested and how. The stability of the system was improved.
Gradually, the tests penetrated from the lower levels of the system to the higher ones. We began to have a good understanding of what test isolation is, how to use stubs and mocks correctly, what the trouble is with static methods, what are the roles of classes, etc. Writing tests no longer seemed such a difficult process, and confidence in the code grew. Now we look at code that cannot be isolated during tests in a completely different way.
So, next time, when you abandon tests due to the complexity of their writing, think about whether your project is rotting?
Of course, if you compare the speed of writing a class with and without tests, then it is faster to write code without tests. However, the very process of writing code with tests changes significantly — you only write what is needed for the tests to run. Your classes are getting smaller and have a well-defined set of functions. That is, you write less code. This is the first thing.
As you become familiar with unit testing, you will notice that the speed of writing code, on the contrary, has increased. The reasons for this lie in the constant rhythm of work, which is becoming the norm for the TDD master: test, code, refactoring, test, code, refactoring, etc. Taking small steps, you always know what awaits you next, so you move without delay. Third, test-driven development dramatically reduces the time spent in the debugger. Again, the reasons are in the rhythm of work. By adding a small chunk of code, we immediately make sure that this chunk of code works. By changing any classes, we check that everything is in order in the system. Of course, there is always the temptation to write a large piece of code at once, but in this case, you almost certainly get a red line, and you have to roll back.
Further, assembling several parts into a coherent whole is easier if the individual parts are already well tested. That is, there is a saving of time.
The number of bugs in the code is reduced, and they are fixed faster. Plus, the likelihood of cascading errors is reduced – that is, situations when, when fixing an error in one place, we add them in another. Therefore, the beta testing and finalization phase is getting shorter in time. Especially if the project is tested using acceptance tests, when the amount of manual labor for testing the application will generally be minimal.
And finally, better design, obtained during test-driven development, helps to improve the code in the future, doing it very quickly. If your project is supposed to survive to the second version, then you will only benefit from the tests.
This is an extremely inept excuse because You can start writing tests with the simplest things, without using all the features of xUnit, mocks (mock objects), etc. Many software products use very simple scripts for self-testing. You can try to start by recording with code the actions that the developer takes for testing manually. Even if it is a collection of scattered and awkward test scripts, it is better than nothing. Try to start with simple acceptance tests and lower-grade tests. Advanced testing techniques come gradually with a deeper understanding of the subject of testing.
Most importantly, don’t rush. As soon as testing becomes a pleasant habit from the category of a burdensome process, you suddenly find yourself inventing testing techniques, and wild “hacking” will turn you back. Every developer strives to be a professional. Professionals write tests. Point. Yes, writing tests require many more advanced skills, but … we’re professionals, aren’t we?
According to many developers who oppose, unit tests are a doubling of the amount of code. I can say that it is not so. In fact, it is … threefold. However, why should you care? After all, this is the code that thoroughly checks the functionality of your working code. And the volume of this very working code with the introduction of tests, by the way, is getting smaller, believe me.
But when you change the code under test, you have to correct the code under test, and this is double or even triple work, critics will notice. The answer is yes and no.
First, with test-driven development, classes become very highly specialized with well-defined interfaces. They strive to take on as few responsibilities as possible, but those that do well. Plus, the dependencies between classes are reduced. As a result, any changes that are made to the system are local in nature and affect the minimum number of classes. Tests, constant refactoring, and rich experience are the only tools that allow you to create high-quality, and therefore commercially successful software products. Of course, someone will argue that you can design the system from the very beginning in such a way that responsibilities between classes are distributed in an optimal way, for example, using the UML. I’m just sure that all my experience in software development suggests that this is simply impossible because no one can see the whole picture, while the requirements for software products are constantly changing. Therefore – tests and only tests.
Second, consider whether you are testing your code correctly. Once on the forum, there was a question about the expediency of a test for a class, which only enters certain data into a database table and reads it. If there is only one such class, then it is not so bad. What if there are many such classes? Of course, in this case, it was much more correct to create not separate tests for each class, but an automated test for all such classes, which would generate random data for saving, and then match the written data with the read data. Separate tests could be created for special cases. In general, exactly the same rules should be applied to tests as for the code under test, for example, to avoid duplication. Try to listen to your code, both production, and test.