How to start writing tests in 10 steps of 10 minutes
- Tutorial
Let me guess: you agree that writing tests is good. This increases the reliability of the system, speeds up development, it is easy and pleasant to maintain a project with good test coverage, and TDD is almost an ideal development process. But not in your project. That is, it is cool, but, unfortunately, there is so much work now - just a blockage. A lot of tasks, only critical bugs - two dozen, plus we urgently need to add this module and write a letter to the customer ... So, we will probably tighten the tests at the end, if time remains. Or in the next project. No, well, it’ll definitely be easier there. Probably.
How did you find out the situation?
So - all this nonsense. The scope of IT is endless, like the universe, a lot of work will always be. You can either start writing tests right now, or never do it. I sketched a short plan on how to start doing it in 10 steps, step by step per day, 10 minutes per step. And when I say “10 minutes” I do not mean “3 and a half hours” and not “well, some time, better longer,” namely 600 seconds. If you do not have 600 seconds of free time per day, urgently change the project, work, profession, country of residence (underline as necessary), because this is not life, but some kind of hard labor. Go.
Do not try to start writing your own framework from scratch - do you need it? Spending a week on choosing the optimal framework (yes, I saw such an estimate of the time for this in the plans) is also stupid. Here's a recipe for you: type in Google the best test framework for% language% site: stackoverflow.com. Open the first 5 links. Close those where the rating of the question or first answer is near zero. From the remaining tabs, you can safely take any recommended framework from the first three with a maximum rating. With a probability of 99.5%, it will suit you. Since you have spent 3 minutes at this step, the remaining 7 can be spent on going to the framework site and see examples of its use. Most likely, everything will be simple and clear there (otherwise he would not be in the top of the recommendations). But if you don’t, choose another one using the same algorithm.
Write Hello, world! we just spit. Here, for example, in C ++.
Now let's do two things.
Firstly, we take out the generation of the displayed text in separate functions. Yes, in two. This is so that later they can be tested.
Secondly, we take out the written functions somewhere from this file. Depending on the approach and language used, these may simply be separate code files or a library. This is necessary in order to then call these functions from tests.
We will have this:
The connection of the framework to the project is probably very well written on the framework website. Or on stackoverflow. Or on Habré. Here I, for example, once described the Google Test connection . Usually, it all comes down to creating a new project of a console executable application (in script languages - a separate script), connecting a framework with a pair of include (import \ using) to it, connecting test code to the project (including the files themselves with the code, or connecting the library) - that's all . If you do not believe that this step can be done in 10 minutes, open Youtube, write the name of your framework in the search and watch 20 videos of approximately the same content that prove it.
First we need to find out:
These questions are usually answered by all the same articles about which I spoke above. Do not go straight into the wilds of the framework. Start with simple tests that test simple things. Here, after all, it’s like in sports - you don’t need to immediately tear a lot of weight, you must first put the right technique.
Here, for example, a couple of tests for our Hello world! on the above Google Test:
We already know how to connect the framework to the project. Remember, they did at step number 3? Everything worked out. Now let's do it for a combat project. Put all the necessary framework files for yourself under SVN \ Git \ TFS \ what-you-there. Make a test project. Connect the framework to it. Include the build of the test project in the build process of your product. Check the build in debug and release configurations. Commit the test project, run the build on the build server. Everything should be ok. Do not load your colleagues with the appearance of a test project yet - firstly, you haven’t broken anything, and secondly, you have nothing to brag about yet.
Do you remember how we brought above from Hello world! part of the functionality in external code? Pay attention to how these functions turned out: they do not depend on global variables, or on the state of any objects, or on external data from files or databases. The result depends only on the arguments passed. Find something similar in your project. Surely you have any functions for converting something somewhere, serialization / deserialization, packing / unpacking, encryption / decryption, etc. Do not think about how much you need and useful functionality you are testing. Your task is to write a simple test, but for a combat project. Run, see "1 test passed successfully."
Incidentally, it is at this stage that very often skeptics get insights. Suddenly it turns out that the simplest test, for the most basic functionality - suddenly failed. We climb into the code - and suddenly we find something like
And it turns out that the main code just so far called this function with such arguments that everything was ok, but at any moment this could change.
You already know how to test simple things. Now figure out how to test something that has external dependencies. See how your framework suggests preparing to run the test and clean up after it. Understand what moki and stubs are . Think about how to test some of your code that reads data from a file or from a database. Is it easy to swap an input source? Maybe you should slightly modify the code to make it easier? Do it if necessary. Write a test for this code.
What does your work on a bug usually look like? You take it from the bugtracker, try to reproduce it, if it doesn’t work, return it to the tester, if it works, debug it to understand its location, find a piece of code with an error, fix it, test it, give it to the tester. Excellent. And now, when working on the next bug, between the steps “find an error” and “correct” add another step - write a test for this error. So that it crashes for the current code. This is a huge buzz, to fix the code - and do not bother to test it with your hands, but run the test that fell a couple of minutes ago and see "successfully" at its output. In addition to this aesthetic pleasure, this test can be given to the tester and used in the future for regression testing (and also for testing side branches of the product, the project “in the field”, etc.). Of course, not all and not always possible to test like this, it is difficult with the UI, with cross-browser compatibility, with multi-threading. Do not bother if writing a test takes you many, many hours. In the end, this technology is designed to make your life easier, and not to make you dance to your tune.
What does your work usually look like when developing new functionality? You probably think first. Then you design what you will do - sketch out the names of interfaces, classes, then the names of methods, fill them with implementation, run, debug. Well, almost nothing needs to be changed. Just at that moment when you already have interfaces, classes and names of methods, but there is no implementation yet - write tests for them. Unpretentious - called the method - checked the result. Please note that already at this stage you will notice the inconsistency of some names, the lack or excess of arguments in the methods, unnecessary or missing dependencies, etc. At the same time, there is almost nothing to fix it now (after all, the implementation has not yet been written) . Corrected the architecture, added tests, launched - saw a bunch of failed tests. Excellent, it should be so. We wrote an implementation, ran the tests - saw most of them passed, fixed the errors, succeeded in passing all the tests successfully - well done. Do you feel how good it has become, what moral satisfaction you have received? It slightly resembles the pleasure of getting some achievements in the game. And why? But because it can be measured! “The code passes 18 tests with a test coverage of 90%” - it sounds cooler, much cooler than “well, the feature seems to be implemented, I poked it a bit, it seems it does not crash”. It gives the right to be proud. You go home - and you clearly understand that something did useful in a day, this “something” is measurable, tangible, real. Do you feel how good it has become, what moral satisfaction you have received? It slightly resembles the pleasure of getting some achievements in the game. And why? But because it can be measured! “The code passes 18 tests with a test coverage of 90%” - it sounds cooler, much cooler than “well, the feature seems to be implemented, I poked it a bit, it seems it does not crash”. It gives the right to be proud. You go home - and you clearly understand that something did useful in a day, this “something” is measurable, tangible, real. Do you feel how good it has become, what moral satisfaction you have received? It slightly resembles the pleasure of getting some achievements in the game. And why? But because it can be measured! “The code passes 18 tests with a test coverage of 90%” - it sounds cooler, much cooler than “well, the feature seems to be implemented, I poked it a bit, it seems it does not crash”. It gives the right to be proud. You go home - and you clearly understand that something did useful in a day, this “something” is measurable, tangible, real. It gives the right to be proud. You go home - and you clearly understand that something did useful in a day, this “something” is measurable, tangible, real. It gives the right to be proud. You go home - and you clearly understand that something did useful in a day, this “something” is measurable, tangible, real.
Tests make little sense if you don't run them. Running them manually is long and pointless. Surely you have a build server with some TeamCity or CruiseControl where your product is going. So, most good build servers immediately, out of the box, support running tests and even parse their logs and draw beautiful reports. The correspondence here, of course, is not “everyone is compatible with everyone”, but if you took the test framework on the advice at the beginning of the article, the chances that everything will work are very high. For example, the TeamCity and Google Test that I mentioned are very friendly with each other.
So decide whether it was worth it or not.
Somewhere after the 8th is a good time to present a test project to your team. Explain in 2-3 paragraphs what and how, show a simple test example, notice that, they say, “feel free to add your own tests”, but don’t really push so far. If it wasn’t accepted to write tests for you, most likely the first impression will be cautious skepticism and misunderstanding. This is quickly treated after the second or third mention at the meeting that, they say, “we found this bug thanks to the test” or “and here the test is written and we will immediately find out if it breaks again”. Programmers are rational people, they will understand and catch up.
How did you find out the situation?
So - all this nonsense. The scope of IT is endless, like the universe, a lot of work will always be. You can either start writing tests right now, or never do it. I sketched a short plan on how to start doing it in 10 steps, step by step per day, 10 minutes per step. And when I say “10 minutes” I do not mean “3 and a half hours” and not “well, some time, better longer,” namely 600 seconds. If you do not have 600 seconds of free time per day, urgently change the project, work, profession, country of residence (underline as necessary), because this is not life, but some kind of hard labor. Go.
1. Choosing a test framework
Do not try to start writing your own framework from scratch - do you need it? Spending a week on choosing the optimal framework (yes, I saw such an estimate of the time for this in the plans) is also stupid. Here's a recipe for you: type in Google the best test framework for% language% site: stackoverflow.com. Open the first 5 links. Close those where the rating of the question or first answer is near zero. From the remaining tabs, you can safely take any recommended framework from the first three with a maximum rating. With a probability of 99.5%, it will suit you. Since you have spent 3 minutes at this step, the remaining 7 can be spent on going to the framework site and see examples of its use. Most likely, everything will be simple and clear there (otherwise he would not be in the top of the recommendations). But if you don’t, choose another one using the same algorithm.
2. We write Hello world!
Write Hello, world! we just spit. Here, for example, in C ++.
Hello world!
#include
using namespace std;
int main()
{
cout << "Hello world!" << endl;
return 0;
}
Now let's do two things.
Firstly, we take out the generation of the displayed text in separate functions. Yes, in two. This is so that later they can be tested.
Hello world! after refactoring
#include
#include
using namespace std;
string GetHello()
{
return "Hello";
}
string GetAdressat(string adressat)
{
return adressat;
}
int main()
{
cout << GetHello() + " " + GetAdressat("world") + "!" << endl;
return 0;
}
Secondly, we take out the written functions somewhere from this file. Depending on the approach and language used, these may simply be separate code files or a library. This is necessary in order to then call these functions from tests.
We will have this:
HelloFunctions.h
#include
using namespace std;
string GetHello();
string GetAdressat(string adressat);
HelloFunctions.cpp
#include "HelloFunctions.h"
string GetHello()
{
return "Hello";
}
string GetAdressat(string adressat)
{
return adressat;
}
HelloWorld.cpp
#include
#include "HelloFunctions.h"
using namespace std;
int main()
{
cout << GetHello() + " " + GetAdressat("world") + "!" << endl;
return 0;
}
3. Connect the framework to Hello world!
The connection of the framework to the project is probably very well written on the framework website. Or on stackoverflow. Or on Habré. Here I, for example, once described the Google Test connection . Usually, it all comes down to creating a new project of a console executable application (in script languages - a separate script), connecting a framework with a pair of include (import \ using) to it, connecting test code to the project (including the files themselves with the code, or connecting the library) - that's all . If you do not believe that this step can be done in 10 minutes, open Youtube, write the name of your framework in the search and watch 20 videos of approximately the same content that prove it.
4. Understanding the capabilities of the framework
First we need to find out:
- How to write one unit test
- How to run unit tests
These questions are usually answered by all the same articles about which I spoke above. Do not go straight into the wilds of the framework. Start with simple tests that test simple things. Here, after all, it’s like in sports - you don’t need to immediately tear a lot of weight, you must first put the right technique.
Here, for example, a couple of tests for our Hello world! on the above Google Test:
#include "HelloFunctions.h"
#include "gtest/gtest.h"
class CHelloTest : public ::testing::Test {
};
TEST_F(CHelloTest, CheckGetHello)
{
ASSERT_TRUE(GetHello() == "Hello");
}
TEST_F(CHelloTest, GetAdressat)
{
ASSERT_TRUE(GetAdressat("world") == "world");
ASSERT_FALSE(GetAdressat("not world") == "world");
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
5. Connect the framework to this project
We already know how to connect the framework to the project. Remember, they did at step number 3? Everything worked out. Now let's do it for a combat project. Put all the necessary framework files for yourself under SVN \ Git \ TFS \ what-you-there. Make a test project. Connect the framework to it. Include the build of the test project in the build process of your product. Check the build in debug and release configurations. Commit the test project, run the build on the build server. Everything should be ok. Do not load your colleagues with the appearance of a test project yet - firstly, you haven’t broken anything, and secondly, you have nothing to brag about yet.
6. Testing something simple
Do you remember how we brought above from Hello world! part of the functionality in external code? Pay attention to how these functions turned out: they do not depend on global variables, or on the state of any objects, or on external data from files or databases. The result depends only on the arguments passed. Find something similar in your project. Surely you have any functions for converting something somewhere, serialization / deserialization, packing / unpacking, encryption / decryption, etc. Do not think about how much you need and useful functionality you are testing. Your task is to write a simple test, but for a combat project. Run, see "1 test passed successfully."
Incidentally, it is at this stage that very often skeptics get insights. Suddenly it turns out that the simplest test, for the most basic functionality - suddenly failed. We climb into the code - and suddenly we find something like
return 12; // TODO: implement later
And it turns out that the main code just so far called this function with such arguments that everything was ok, but at any moment this could change.
7. Testing something more complicated
You already know how to test simple things. Now figure out how to test something that has external dependencies. See how your framework suggests preparing to run the test and clean up after it. Understand what moki and stubs are . Think about how to test some of your code that reads data from a file or from a database. Is it easy to swap an input source? Maybe you should slightly modify the code to make it easier? Do it if necessary. Write a test for this code.
8. We are writing a bug test
What does your work on a bug usually look like? You take it from the bugtracker, try to reproduce it, if it doesn’t work, return it to the tester, if it works, debug it to understand its location, find a piece of code with an error, fix it, test it, give it to the tester. Excellent. And now, when working on the next bug, between the steps “find an error” and “correct” add another step - write a test for this error. So that it crashes for the current code. This is a huge buzz, to fix the code - and do not bother to test it with your hands, but run the test that fell a couple of minutes ago and see "successfully" at its output. In addition to this aesthetic pleasure, this test can be given to the tester and used in the future for regression testing (and also for testing side branches of the product, the project “in the field”, etc.). Of course, not all and not always possible to test like this, it is difficult with the UI, with cross-browser compatibility, with multi-threading. Do not bother if writing a test takes you many, many hours. In the end, this technology is designed to make your life easier, and not to make you dance to your tune.
9. First time TDD
What does your work usually look like when developing new functionality? You probably think first. Then you design what you will do - sketch out the names of interfaces, classes, then the names of methods, fill them with implementation, run, debug. Well, almost nothing needs to be changed. Just at that moment when you already have interfaces, classes and names of methods, but there is no implementation yet - write tests for them. Unpretentious - called the method - checked the result. Please note that already at this stage you will notice the inconsistency of some names, the lack or excess of arguments in the methods, unnecessary or missing dependencies, etc. At the same time, there is almost nothing to fix it now (after all, the implementation has not yet been written) . Corrected the architecture, added tests, launched - saw a bunch of failed tests. Excellent, it should be so. We wrote an implementation, ran the tests - saw most of them passed, fixed the errors, succeeded in passing all the tests successfully - well done. Do you feel how good it has become, what moral satisfaction you have received? It slightly resembles the pleasure of getting some achievements in the game. And why? But because it can be measured! “The code passes 18 tests with a test coverage of 90%” - it sounds cooler, much cooler than “well, the feature seems to be implemented, I poked it a bit, it seems it does not crash”. It gives the right to be proud. You go home - and you clearly understand that something did useful in a day, this “something” is measurable, tangible, real. Do you feel how good it has become, what moral satisfaction you have received? It slightly resembles the pleasure of getting some achievements in the game. And why? But because it can be measured! “The code passes 18 tests with a test coverage of 90%” - it sounds cooler, much cooler than “well, the feature seems to be implemented, I poked it a bit, it seems it does not crash”. It gives the right to be proud. You go home - and you clearly understand that something did useful in a day, this “something” is measurable, tangible, real. Do you feel how good it has become, what moral satisfaction you have received? It slightly resembles the pleasure of getting some achievements in the game. And why? But because it can be measured! “The code passes 18 tests with a test coverage of 90%” - it sounds cooler, much cooler than “well, the feature seems to be implemented, I poked it a bit, it seems it does not crash”. It gives the right to be proud. You go home - and you clearly understand that something did useful in a day, this “something” is measurable, tangible, real. It gives the right to be proud. You go home - and you clearly understand that something did useful in a day, this “something” is measurable, tangible, real. It gives the right to be proud. You go home - and you clearly understand that something did useful in a day, this “something” is measurable, tangible, real.
10. We fasten the launch of tests to the CI server
Tests make little sense if you don't run them. Running them manually is long and pointless. Surely you have a build server with some TeamCity or CruiseControl where your product is going. So, most good build servers immediately, out of the box, support running tests and even parse their logs and draw beautiful reports. The correspondence here, of course, is not “everyone is compatible with everyone”, but if you took the test framework on the advice at the beginning of the article, the chances that everything will work are very high. For example, the TeamCity and Google Test that I mentioned are very friendly with each other.
Afterword
A meticulous reader may notice that points starting somewhere from the seventh to the eighth most likely will not fit into the “10 minutes a step” headline. What can I say? Consider that I, a bad person, have slightly pinned you. However, if you practice these points with righteous indignation, then:- You already have a project to which tests are fastened. They start up, work, there are more than zero of them, and they already benefit you.
- You have gained experience in all this business.
- The second time you will get seriously faster.
So decide whether it was worth it or not.
Somewhere after the 8th is a good time to present a test project to your team. Explain in 2-3 paragraphs what and how, show a simple test example, notice that, they say, “feel free to add your own tests”, but don’t really push so far. If it wasn’t accepted to write tests for you, most likely the first impression will be cautious skepticism and misunderstanding. This is quickly treated after the second or third mention at the meeting that, they say, “we found this bug thanks to the test” or “and here the test is written and we will immediately find out if it breaks again”. Programmers are rational people, they will understand and catch up.