Clean tests as your solid documentation
Posted at: 6-11-2023

Why testing and applying clean code to them 🔗

Hello!

In this blog we'll talk about the way we are writing tests. Tests by default, are something we should always have and there are some very good reasons for it.

Tests help us verify the integrity of our system. It validates the behaviour we expect and provides a strong fallback when we accidently change the functionality while not having this intention. Furthermore, it allows us to refactor existing code into something cleaner if necessary without being afraid of breaking or changing functionality.

Let's take a car as example here. If your car had no checks when your motor overheated, you could be in danger without even knowing it. Think about it in the same way for your tests. Without tests you have no feedback mechanism that will warn you if something goes wrong in your application. Of course, the time and places are different but it all comes down to the same thing. If something breaks you want to have that feedback so you may act upon it. There are many more reasons why we should have tests but further on we'll treat a different topic.

Let's continue with the way that tests are being written. These most likely come along in formats like "Given_When_Then", "Arrange_Act_Assert" and probably some others. Great! But most of the times they are written in a technical way. Let's have a look at what Martin Fowler says about Code As Documentation.

Quoting him: "If there's no will to make code clear, then there's little chance it will spring into clarity all by itself. So the first step to clear code is to accept that code is documentation, and then put the effort in to make it be clear. I think this comes down to what was taught to most programmers when they began to program."

Notice how he talks about code. Your tests are also part of that code. They are a crucial and important part of your application and should also submit to these rules. Keep your tests simple, clean and readable. Don't overextend logic in them and think how your next fellow developer can easily understand what is going on. Perhaps even your functional analysts and business if you're using a specific testing framework (specflow).

Let's have a look at this simple example:

Add_InvalidItem_ShouldNotBeAdded {...}

I've seen a decent amount of tests being written in this style. Without reading the code you may have no clue about what is going on. And reading code takes time. Not understanding also takes time because you want to clarify things. But what if our code is taken as documentation and you would verbally read this single line to someone of your business, then what would they understand of it? What if we could make the test more explicit in what it is testing, but on a more functional level? Let's write the test name so it gives out its intention.

Imagen that we actually meant this with the above test:

Customer_adds_out_of_stock_item_to_shopping_cart_not_added() {...}

Those are two very different things, right? This is what most of the times happens when you read code that means something else. I know that my example is perhaps a bit exaggerated but at least it reveals what I am talking about. Because probably you'll firstly try to form a theory of what it could be. After that, you'll need to confirm that theory by checking the test and in most of the times, the code as well. Just to find out something else is going on than expected. We want to avoid this overhead and make things much more simple by doing this. If all tests are written like this, we could even give these names to our functional analysts or business. They will perfectly understand these rules and may act upon them to create new requirements.

Let's have a few more examples and see what makes more sense to faster understand what's going on.

Add_NullItem_ShouldNotBeAdded {...}
Add_Item_ItemIsAdded {...}

Let's rewrite them and reveal their intention:

Customer_adds_unknown_item_receives_warning() {...}

Imagen that it happens. Your customer is adding something not defined, something null. We discussed this with business and we label this as something unknown. When we add an unknown item, we want our customer to see a specific warning.

Customer_adds_item_to_shopping_cart() {...}

Anyone could add an item anywhere. We can make it more explicit by defining the scenario of business: it's a customer adding an item to their shopping cart. The previous named test allows us to interpret it more widely.

I hope that the above content brings out what I am trying to say. Of course, the format being used here is not a must. I just feel that it suits nicely. Last but not least, I have to give credits to Vladimir Khorikov, who inspired me to write about this. Because of him, I started to think twice when writing tests. You can find more about his view on this topic here.

The awareness for code readability is being put more and more to the spotlight lately, with very good reasons.