Unit Tests
Tests are extremely important in software, especially unit tests. I would go as far as saying that tests should be a core part of all the code that you write. Tests however, are also very unpopular among programmers, and many programmers don’t write them at all. That’s right. I’m saying that 90% of the programmers I have ever met almost never write tests for their code. Writing tests seem like a waste of time to most people, despite how important they are, and are thrown to the back of a priorities list that is topped with new features and urgent bug fix tasks.
It is incredible how many programmers actually just code and not write tests. Usually when a programmer fixes a bug or modifies some code, all that he/she would do after it finally compiles is to start the program up, and fiddle around with it a bit to see if it works, and checks it in. I’m sure you can see why this is a bad way to test your changes. For one thing, your program might only work under the very special circumstances you are currently in, which means it only works for you at the time. When you don’t write a test that tests that your change will continue to work the way you expect it to, you risk letting it break when it gets run under different circumstances, or after another change has been made to the program. But that’s pretty obvious to anyone who has worked on a sizeable software project before. Any change is likely to break something, and this is called Regression. Again, familiar term.
Most programmers say that they shouldn’t be the ones writing tests, but the people who test the software. While this is true for requirements testing, I personally feel that if the person who made the change to the code didn’t write the unit tests, who would? Requirements testing are very overall tests which treat the code that its testing like a black box. However, unit tests are there to ensure that regression does not happen, and are extremely targeted tests. Whenever a person makes changes to a codebase, whether to add a feature or fix a bug, that person is the single most qualified person to write those unit tests, not to mention the intimate knowledge possessed by that person of the inner workings of the change that was made and that person’s ability to treat the code changes like a white box and test it that way, by providing pathological inputs to the code to ensure that it functions under the same pressures it was written for.
Some managers too, block the writing of tests because of the view that “tests don’t make money”. I’m glad I don’t currently work under such managers, but I have heard stories from friends who do. I must admit that they don’t make money, but they certainly prevent you from losing money. Whenever a software project gets big, a large amount of time and effort is spent in fixing bugs that are progressively detected. This is called paying off “technical debt”, and that “technical debt” accumulates as more bugs creep into the codebase as a result of the absence of tests that keep an eye on the bugs. Most of the bugs I have seen were bugs caused by myself and for which I did not write tests for, and which only worked for me and a certain time and date and machine, but no other. Drawing from all those bitter experiences, I have developed a certain liking for tests.
However, simply writing tests for your code simply isn’t enough. You need to make sure that if your test fails, it would provide a scope that would allow you to narrow the problem down to a tiny section of your code. Software in practice is almost never trivial, and there are times when you use a piece of code it would draw from a myriad of data inputs and APIs to get its job done. In the company I work for, we write mapping software. This means that our software has an input from data that vendors provide. Often, we write tests that not only test our own code, but also the correctness of the data provided to us by a vendor. For example, a vendor may have a “Broadway” in New York, but another vendor may not. A key component in our software is Geocoding, which allows a person to find the location of a road by simply typing in its name and city. This means that whenever we write a Geocoder test to find a street, its failure could be due to the data not being right, or that the Geocoder could well be malfunctioning. In big software projects, problems like these come often. Which is why you should also write a subset test. Continuing the example of the Geocoder, in addition to the Geocoder test that I wrote, I also wrote a test that makes sure that the street “Broadway” actually existed in the in the data, so that I can tell what’s going on by looking at both tests and see which ones fail.
Tests also should not be too complicated. I’ve seen some tests that are about 400 lines long and have an extreme amount of cyclomatic complexity with if branches and loops in them. These sorts of complexities create the risk of the test itself failing to run properly, which means its less reliable, and if it fails it doesn’t provide the tester with an objective view of what’s not working. A test should be short, and what it is doing should be almost immediately obvious by simply looking at it. A complex test requires that someone test the test, which usually defeats the purpose of writing the test in the first place. Also, sometimes people find themselves writing tests that are complex because the API that the test is testing is hard to use. In that case, the API has already failed the usability test, and should be rewritten.
In short, tests are extremely important because they are like guards that keep an eye on the sewers where bugs have historically crawled out of. If you find that writing a test for something is like writing a full-fledged application, it might help to know that perhaps you aren’t writing a test that is targeted enough, or you may be testing something that isn’t testable, and hence something that isn’t usable, in which case, you might want to revise your code again.
No comments yet.