The crazy idea of writing your code tests backward

Friday, April 30, 2021

Introduction

How many times you need to update Unit Tests or add new ones, and you open the Test class, and you don't understand anything? If you never encountered something like this, you are among the few lucky developers in the world. But most of you probably know what I mean.

No one wants to write harmful tests in the first place. We usually try to find good test method names, but our good intentions often get lost after that. You'll often find randomly named variables that make no sense because, in the end, the test does what it is supposed to do. You could argue that this is sufficient when writing unit tests, but what if I told you that you could do that without doing any significant refactoring?

What if you could have intend-revealing and precise naming inside your code tests by writing your tests backward? It sounds kind of strange, doesn't it? But today, I want to show you how to write this kind of code in a lean way by looking at

  • What writing your tests backward means.
  • Why it can be better than writing your tests forward.
  • How to implement it in your development process.

What does writing your tests backward mean?


In his book Xunit Test Patterns, Gerard Meszaros calls working backward "A useful little trick for writing very intent-revealing code[…]". Writing your tests from the end takes time and quite a lot of practice. That's why I try to keep it as simple as possible. The goal is to write easy-to-understand code so that other developers can understand them properly.
So how does the workflow look?

Development process when writing your tests backward

We start with the assertion first because, inside a test method, it is usually at the end. But how do we write it? We assert intend-revealing variables which at first contain no value. This way, we think more about what we want to assert, and we do not simply add already initialized variables with random names into the parameter list of the assertion.

The process after initializing the assertion can be varied, but it is helpful to start with initializing the expected argument. Because you already have its name, you know how the expected argument should behave or what value it should contain.
After that, we define and initialize the variable, which retrieves the value from the System Under Test (SUT). Here, the SUT is the method you want to test. If the method contains some parameters, fill the parameter list with some intend-revealing variables. Of course, you need to initialize those variables but writing their name first forces you to understand them better.
In object-oriented programming, a class often depends on other components. We initialize these as well, no matter if you create the actual object or create a mock. Of course, we start by writing the variable names first again and then initialize afterward.

Why can it be better than writing your tests forward?

The entire process seems awkward at first. Depending on your editor or IDE, you get a lot of error messages during the development process. So the critical question is, why should you start writing your code tests backward in the first place?
The main idea is that you don't just write the tests. You think about each defined variable's intent, and ultimately you think more about what your test is supposed to achieve. We usually try to do that through clean and readable test method naming. But often, we stop after this step. Here we go further, and we try to make everything as readable as possible.
As already mentioned, you can argue that you will probably achieve that by writing your tests forward. That's true, and it's excellent if it works for you. But let's be honest, we often tend to write random variable names in our test method and refactor them afterward.
With the new approach, we will, in most cases, get rid of the refactoring step. In the end, this will make your development process faster and your code cleaner.

How to implement it in your development process?

Clean-code is this term which, as a developer, you come across endless times. Everyone is telling you to write clean code, so your code is more maintainable and testable in the long run. Sounds nice.
If your peer or colleague cares about clean code, she or he might also mention focusing on clean code when writing your tests. At this point, it gets interesting because we often tend to apply clean code rules only to production code but not on our tests we've written.
So how would the implementation work? Let's look at a basic example. In this case, we write the test last, and we don't go the TDD route.
Here I have a basic service called RoboService, written in C#, which lets a robot walk.

GIST: https://gist.github.com/adrinamin/ec9860e2914f1c5831ff439fef1640e2.js

The service contains one dependency, IConversationHandler, which is handling the talking for the robot. We are not interested in the implementation details. The only important fact is that the Walk method manipulates a string and calls the Talk method from the IConversationHandler. After that, it returns the manipulated string. We now want to write a test for this class.

We start by defining a readable name.

Next, we continue by writing the assertion.

As you can see, there are red squiggles below the arguments because they are not defined yet. Now we want to initialize the expected value.

Next, we are invoking the SUT, which is the service method. We start by creating a new RoboService.

As you can see, the RobotService needs an instance of the ConversationHandler. Because we want to write a unit test, we submit a mock to avoid calling the actual implementation of the Talk method. In this case, we use the mocking framework, Moq, to create a new mock of the IConversationHandler interface.

Now we can invoke the Walk method:

We also introduce a new variable called inputDistance, which contains the Walk method's distance argument value.

Of course, we need to define the robot name string. Here we use a random name because we don't want to verify this parameter.

In the end, we initialize the expected variable, talkedRobotDistance, with the return value of the Walk method.

Conclusion

Writing clean and intend-revealing code tests is not easy and need a lot of practice. There are multiple ways to achieve that but writing your tests backward is worth considering. As you could see, the theoretical process and implementing these best practices do not align 100%. But it's more or less the same.  The significant benefit is that we usually avoid an extra refactoring step and our variables are easy to understand right from the beginning.

In theory, the steps are simple, but it takes some time to get used to it in practice. To sum up, you have to do the following steps for writing your tests backward:

  • Write your assertion and add the variable arguments
  • Initialize the expected argument
  • Initialize the SUT
  • Initialize the dependend-on-component

What do you think about this approach? Do have you already tried it? Let me know what you think in the comments section.

Let us know what you
think about this article