Will you write less tests with Integrated or Unit tests?

In this article I’m going to talk about Unit/Solitary/Mockist/London Style tests and compare them to Integrated/Social/Classicist/Detroit Style tests. This won’t be a thorough review. I’m actually going to focus on one specific argument: If you choose one over the other, will you write more or less tests? There are many other important aspects worth considering (the speed of your tests, the confidence they give you, the way they impact your code design, etc.), but I’m just going to focus on the number of tests you have to write.

If you’re unfamiliar with these concepts, the terminology in Martin Fowler’s Unit Test article can be a good primer. Here’s an image for reference: Solitary vs Sociable

The reason I’m writing this article is because I saw a video a few years ago called “Integrated Tests Are A Scam”.

J.B. Rainsberger – Integrated Tests Are A Scam from devtraining on Vimeo.

The main theme of this video is that integrated (AKA Sociable) tests are generally inferior to mockist/unit/Solitary tests and one of J.B. Rainsberger’s arguments is that it doesn’t make sense to use integrated tests when you think about things mathematically. Here’s some code to explain the argument:

If we wanted to completely test all the paths, how many tests would we have to write? Well if we’re writing mockist style unit tests, we’d have to write 6:

  • When testing A, we’d mock out B and write a test for when maybeEven is even and another test for when maybeEven is odd.
  • When testing B, we’d mock out C and write a test for when maybeFavoriteNumber is your favorite number and another test for when maybeFavoriteNumber isn’t.
  • When testing C, we’d write a test for when maybeFactorial is a factorial and another test for when maybeFactorial isn’t.

But, if we were writing integrated tests, we’d have to write 8 tests. Each of these would test A‘s execute method and the input would have to satisfy all these conditions:

  • even? == true, favoriteNumber? == true, factorial? == true
  • even? == true, favoriteNumber? == false, factorial? == false
  • even? == true, favoriteNumber? == false, factorial? == true
  • even? == true, favoriteNumber? == true, factorial? == false
  • even? == false, favoriteNumber? == false, factorial? == true
  • even? == false, favoriteNumber? == false, factorial? == false
  • even? == false, favoriteNumber? == true, factorial? == true
  • even? == false, favoriteNumber? == true, factorial? == false

The more classes and paths you have, the more integrated tests you have to write relative to the unit tests. This argument never felt right to me but it took over a year for me to figure out why. The scenario assumes that both branch paths lead into the same class and method. That’s not always true. What if your code looks like this?

In this scenario, each branch leads to a different class instead of the same class. How many unit tests will we have to write to test all paths? Just like before, you’d have to write 6 tests:

  • When testing X, we’d mock out Y/Z and write a test for when maybeEven is even and another test for when maybeEven is odd.
  • When testing Y, we’d write a test for when maybeFavoriteNumber is your favorite number and another test for when maybeFavoriteNumber isn’t.
  • When testing Z, we’d write a test for when maybeFactorial is a factorial and another test for when maybeFactorial isn’t.

But, how many integrated tests would we have to write?

  • even? == true, favoriteNumber? == true
  • even? == true, favoriteNumber? == false
  • even? == false, factorial? == true
  • even? == false, factorial? == false

Now we only have to write 4 integrated tests. If you look at the first two, factorial? is irrelevant because execution doesn’t go into Z when the number is even . If you look at the last two, favoriteNumber? is irrelevant because execution doesn’t go into Y if the number is odd.

Back to the original question: Will you write less tests with Integrated or Unit tests? It depends on the shape of your code. If your conditional branches converge into the same class and methods, you’ll write less tests with unit tests. If your conditional branches diverge into different classes and methods, you’ll write less tests with integrated tests.

Join the email list for bonus content.

Leave a Reply

Your email address will not be published. Required fields are marked *