DEV Community

loading...

The importance of test consistency

David Llop
I'm a web developer using Laravel (PHP) and Vue.js. From Girona, Catalonia. I'm always asking `how does this work?` I'm always saying `never stop learning.`
Originally published at davidllop.com on ・3 min read

Recently I've discovered the benefits of including feature and unit tests in my applications which helps me find bugs faster.

Today I found a small bug in one of the largest test suite our team has that I was unaware until today.

This test suite contains many tests that work with dates, like hotel bookings or daily price changes. I discovered the
issue today when the month changed from June to July, let me explain that.

The user needs to be able to modify the price each week but only on certain days, for example every weekend of September.
So the request receives a date_begin, a date_end and an array of weekdays:

/** @test */
public function only_weekdays_selected_are_created()
{
    // ... Preparing required variables and data on the database

    $this->actingAs($user)->post('/prices/create', [
        'price' => 46,
        'date_begin' => Carbon::now()->addMonth()->firstOfMonth(Carbon::MONDAY)->format('Y-m-d'),
        'date_end' => Carbon::now()->addMonth()->firstOfMonth(Carbon::FRIDAY)->format('Y-m-d'),
        'weekdays' => ['wed']
    ]);

    $this->assertDatabaseHas('price_days', [
        'day' => Carbon::now()->addMonth()->firstOfMonth(Carbon::WEDNESDAY)->format('Y-m-d'),
        'price' => 46
    ]);
}

Have you spotted the bug yet? Well, I'll write it down anyway to force myself to remember it.

The code is inserting the first Wednesday of the first complete week into database, which is August 8 2018, and the
assertion is looking for the first Wednesday, which is August 1 2018.

After refactoring a bit, the issue is resolved but it forced me to handle this kind of assertions from another point of
view. Since we are a small team of developers, and all of us needs to write tests on this project, I thought about creating
some kind of helper (it has become an easily used word in programming, don't you think?) to handle that date-stuff for us.

I decided to create a class inside the tests folder and name it TestCarbon. This name is nothing close to definitive
by the way, but is the first thing that came into my mind since it's only for testing and it's related to Carbon.

And this class looks very similar to this:


namespace Tests;

class TestCarbon
{

    public function today()
    {
        return Carbon::now();
    }

    public function firstMondayOfNextMonth()
    {
        return self::today()->addMonth()->firstOfMonth(Carbon::MONDAY);
    }

    public function firstWednesdayOfNextMonth()
    {
        return self::firstMondayOfNextMonth()->addDays(2);
    }

    public function firstFridayOfNextMonth()
    {
        return self::firstMondayOfNextMonth()->addDays(4);
    }

}

Then after refactoring the test, again, it looks like this:

/** @test */
public function only_weekdays_selected_are_created()
{
    // ... Preparing required variables and data on the database

    $this->actingAs($user)->post('/prices/create', [
        'price' => 46,
        'date_begin' => TestCarbon::firstMondayOfNextMonth()->format('Y-m-d'),
        'date_end' => TestCarbon::firstFridayOfNextMonth()->format('Y-m-d'),
        'weekdays' => ['wed']
    ]);

    $this->assertDatabaseHas('price_days', [
        'day' => TestCarbon::firstWednesdayOfNextMonth()->format('Y-m-d'),
        'price' => 46
    ]);
}

I know I know, firstWednesdayOfNextMonth is lying (at least in August 2018), but firstWednesdayInsideFirstFullWeekOfNextMonth
seemed too long for me 😅.

I showed the code to the team and they thought it was a good addition to our test suite. They started to use it with their
own tests and adding methods for returning the dates they use the most. It turned out that I had a few tests
with similar dates but not the same as the three used by TestCarbon. But with this class in mind, instead of
creating new methods for new days, I modified those tests to match this dates and they're still passing. So I was able
to remove some magic dates, so to speak, from the code.

Later, I thought about the Magic string problem and I remembered an app in
which I have a few tests interacting with strings all the time. It was about colors and they were passed
through the request like yellow, green, orange... This time though, while I was creating this TestColors class
into this test suite, I realised and thought

why I'm not using this for the main app too?

So I moved that class to a folder freely named helpers and refactored both the code in the app and the tests. Now it's
using this class which contains all the colors I need as constants such as const YELLOW = 'yellow';. This allowed
me to remove a lot of magic strings from all the application and everything looks even more consistent now.

Discussion (0)