Misconceptions about automated tests

Misconceptions about automated tests

I’m not religious about it, but I think that having good set of automated tests is one of the best ways of ensuring high quality of the software. Still, often I see otherwise competent developers saying things like

  • “You’re not going to catch the errors anyway so it’s just unnecessary work”
  • “My code is so simple and easy to understand, it doesn’t need any tests”
  • “Having unit tests is just extra maintenance because the tests will fail anyway when code is changed”

I know there’s tons of articles about automated testing already, but I wanted to write my opinion, perhaps just out of frustration of having to deal with such people.

“Not going to catch errors anyway”

This is probably the most common misunderstanding. Finding errors in your code is not the only reason for writing automated tests. I’d argue it’s not even the most important reason. There’s plenty of other reasons for tests:

  • They act as living documentation for your code. Documentation, even code comments, becomes outdated very easily. Tests force you to update them when the specs change and this is good because it ensures the tests are always up to date. You can even write your tests in a natural language and show them to the customer!
  • Having tests helps with fighting against your code becoming rigid. Got a new user story that requires you to make changes to existing code? What could potentially break and how do you make sure it still works? You might not even know about all the things the system does. This uncertainty leads to fear of changing the code. You have to know more about the system before changing anything, or risk breaking the production system. This adds up to technical debt.
  • Writing tests is the best way to make sure your code stays testable. Sure, your code might be really simple now, but then other people extend it, write more code that depends on it. At some point you realize the system has actually become pretty complicated, and since your code isn’t written to be testable, you first need to spend days of refactoring it so that you can test it. Most people just don’t, especially if majority of the existing code was written by someone else.
  • And yes, reading the code more thoroughly while writing the tests, thinking about the different use cases, you might also find new ways to break your code, before it’s broken in production by the end user. That said, I think manual testing and code reviews are still the best ways of finding bugs in existing code.

I’d also like to mention test driven development (TDD), which is a method for writing tests, but is not at all related to finding errors in your code. Instead, it’s a great way of breaking a complex problem into smaller pieces, which is what I use it for when appropriate. Ending up with a comprehensive test suite is a positive side effect.

“Too simple to test”

I agree that there’s a lot of code that doesn’t need to be tested. However, what qualifies as “trivial” or “simple” is subjective.  My definition of trivial code is pretty narrow: if the code takes inputs and does something with them, other than assigns them into variables, that code is not trivial.

People are often blind to problems in their own code. Maybe it’s simple to you now, but how about that new programmer who just joined your project?

Most of the time, it’s not about single lines of code, it’s about what your piece code does as a whole. I think it’s easy to miss the forest for the trees when only looking at the structure of the code, and not what it actually does.

Furthermore, code that is simple to write and read is usually also simple to test. Don’t make the mistake of writing tests for the easy cases and ignoring the code that is more difficult to test though, because it’s true that the more complex the code is, the more likely it is to have errors.

“Tests are just extra maintenance and they break all the time”

If it feels like:

  • Your tests are nothing but a burden
  • They take a lot of effort to write and maintain
  • They keep failing not because something is actually wrong, but because you changed the implementation

You’re probably doing it wrong. Tests are supposed to be simple. A single test case is usually just a few lines of code, even if the piece of code being tested was much longer. If your test case becomes longer than 10 lines you should probably think about what you’re doing and break some dependencies. Sometimes I see people testing multiple things in a single test case. Avoid that – writing multiple test cases should be cheap and it makes those tests easier to understand.

If you’re testing the same method with multiple inputs and expectations, usually you should parameterize the test:

string GetHello(string person) {
  return "Hello, " + person;
}

void TestHello(string expected, string person) {
  var result = GetHello(person);
  Assert.Equals(expected, result);
}

TestHello("Hello, John", "John");
TestHello("Hello, ", null);

Keep the implementation in mind and use it as a guideline, but avoid tying your tests too much to the implementation. I dislike mock frameworks based on setups because often they lead to relying on unnecessary expectations about the code:

interface IProductRepository {
  Product GetProduct(int id);
  Product[] GetProducts(int[] ids);
}

void DeleteProducts(int[] productIds) {
  foreach (var productId in productIds) {
    DeleteProduct(repository.GetProduct(productId));
  }
}

Given the code above that deletes a number of products from the repository using their IDs, you’d probably want to mock the repository. Maybe the repository calls a SQL database and you don’t want to (and probably shouldn’t) include that in your test. But which method do you mock, GetProduct or GetProducts? How do you know which method is used by the code under test? What if the code is changed so that it uses the other method call instead? Your test is going to fail because the setup is no longer matched, even if there’s nothing wrong with the code. How to prevent that? I prefer writing fake implementations that mimic the real thing. For example in this case it would be simple to write an in-memory repository that returns the same products regardless of which method was called.

Nothing new here anyway

Most of the stuff I said here has already been said better somewhere else. I once read this book “The Art of Unit Testing“, I recommend you to do the same if you’re not convinced. Or some other good book about unit testing.

Comments are closed.