Change the problem to write simpler tests.
Sometimes, writing tests for a piece of code is difficult but can be simplified by changing the problem we are trying to solve.
A very good example of this I know is about testing something time-related. Controlling time is something we cannot do. No matter what we try, time passes. We could use tools to control the system's clock and see where it goes, but we sometimes can find a simpler solution just by having another perspective about the problem we're trying to solve.
Imagine that you're working on a food inventory system that can tell if a dish is edible right now.
For instance, Panna Cottas can be eaten two days after they've been cooked. One possible way to implement that logic is the following code:
class PannaCotta {
public function __construct(
private readonly DateTimeImmutable $cookingDate
)
{}
public function isEdible(): bool {
$now = new DateTimeImmutable('now');
$twoDaysAfterCookingTime = $this->cookingDate->modify('+ 2 days');
return $now <= $twoDaysAfterCookingTime;
}
}
That code is pretty straightforward, but the dependency on time with the call to new DateTimeImmutable('now')
makes it difficult to test.
A test that ensures that a Panna Cotta cooked less than two days ago is edible will start failing at some point (in two days max, actually) if we hard-code the value of the cooking date. We could write our test to use a relative date, but they make tests harder to understand and can cause flakiness. That's not a satisfying solution either.
A better solution is to look for a solution to control the date used for the comparison, and this is where changing the problem can help us to write more straightforward test. Instead of trying to know if a panna cotta is edible right now, we can change the problem to ask if a panna cotta is edible at some date. After all, you might want to know if the panna cotta you have will still be edible tomorrow and, if so, choose something else for dessert right now.
Once we decide to move to that new problem, we can modify the PannaCotta
class code to this:
class PannaCotta {
public function __construct(
private readonly DateTimeImmutable $cookingDate
)
{}
public function isEdibleOn(DateTimeImmutable $potentialEatingDate): bool {
$twoDaysAfterCookingTime = $this->cookingDate->modify('+ 2 days');
return $potentialEatingDate <= $twoDaysAfterCookingTime;
}
}
We've renamed the isEdible
method to isEdibleOn
and have introduced a DateTimeImmutable
parameter representing the date we might eat the panna cotta, $potentialEatingDate
.
Testing that code is now a piece of cake, because we control the cooking date and the date at which we might eat the panna cotta:
class PannaCottaTest {
/**
* @test
*/
public function is_edible_up_to_two_days_after_cooking(
DateTimeImmutable $potentialEatingDate
): bool {
$pannaCotta = new PannaCotta(new DateTimeImmutable('2017-05-18'));
$this->assertTrue(
$pannaCotta->isEdibleOn(new DateTimeImmutable('20127-05-19')
);
}
}
As you can see, we didn't have to use some fancy tooling to create the test. Just a change of perspective and reframing the problem.
Dan North wrote an article about rethinking the problem we're trying to solve and discovering a missing concept in the domain you might be interested in as well. Funnily enough, it's also about time.
Forcing us to think harder about the problem is a really lovely side-effect of testing. When a test is difficult to write, seems odd to read, forces us to instantiate things we are not going to use anyway - something we can detect when we see a dummy (an article by me about that, in French)- it's a good clue that we should take a few minutes to rethink the problem or our design.
If you need some help with your test I think I can help. Have a look at my video course in French or let's have a chat and see what we can do together.
- Improve your automated testing : You will learn how to fix your tests and make them pass from things that slow you down to things that save you time. This is a self-paced video course in French.
- Helping your teams: I help software teams deliver better software sooner. We'll work on technical issues with code, test or architecture, or the process and organization depending on your needs. Book a free call where we'll discuss how things are going on your side and how I can help you.
- Deliver a talk in your organization: I have a few talks that I enjoy presenting, and I can share with your organization(meetup, conference, company, BBL). If you feel that we could work on a new topic together, let's discuss that.