Forcing Proper Implementation

We made our first test pass by hardcoding the return value:

<?php

class EmailAddressPartsExtractor
{
    public function extractDomain($emailAddress)
    {
        return 'example.com';
    }
}

Even though some people might think that’s cheating, that’s not the motivation here. The real motivation is to ensure that we do not write code that’s not covered by tests. Why do that? Because if we don’t, what’s the point of writing tests in the first place?

The main reason for writing tests is to get confidence in our code. And to get that confidence, we should only write the “real” code when there’s a failing test. Otherwise we end up with untested code.

Going back to our domain extraction code, we need to write enough tests or assertions to force us to write the actual implementation. Let’s do that now.

Triangulation

In testing parlance, triangulation is a process of adding more tests or assertions to force us into writing the real implementation of some logic. Here’s how we’re going to do that:

<?php

class EmailAddressPartsExtractorTest
    extends PHPUnit_Framework_TestCase
{
    public function testExtractDomain()
    {
        $extractor = new EmailAddressPartsExtractor();
        $this->assertEquals(
            'example.com',
            $extractor->extractDomain('elnur@example.com')
        );
        $this->assertEquals(
            'example.org',
            $extractor->extractDomain('elnur@example.org')
        );
        $this->assertEquals(
            'example.net',
            $extractor->extractDomain('elnur@example.net')
        );
    }
}

Notice that we added two more assertions with example.org and example.net domains as expected values of extracting the domain part of the elnur@example.org and elnur@example.net email addresses, respectively.

Let’s run our new test now:

 1 $ bin/phpunit --color tests
 2 PHPUnit 4.8.19 by Sebastian Bergmann and contributors.
 3 
 4 F
 5 
 6 Time: 40 ms, Memory: 4.00Mb
 7 
 8 There was 1 failure:
 9 
10 1) EmailAddressPartsExtractorTest::testExtractDomain
11 Failed asserting that two strings are equal.
12 --- Expected
13 +++ Actual
14 @@ @@
15 -'example.org'
16 +'example.com'
17 
18 /home/elnur/proj/phpunit-starter/tests/EmailAddressPartsExtractorTest.php:16
19 
20 FAILURES!
21 Tests: 1, Assertions: 2, Failures: 1.

Nice. Adding more assertions made our hardcoded logic to fail. Let’s go through the PHPUnit output.

On line 4, the dot got replaced with an F again.

On line 11, we now get a different assertion failure. That’s because this time the types of the expected and actual values match, while in the previous failure we were trying to compare null to a string. But even though the types match, the values don’t — and hence we get the failure.

We also get some new output on lines 12-16. That’s a diff output. The lines 12-13 show the legend to tell us that lines starting with minus signs are what was expected and lines starting with plus signs are the actual values. And that’s what we see on lines 15-16. Instead of the expected example.org string, we got the actual example.com string.

The last 22th line reports that 2 assertions got executed instead of 1. Why 2 and not 3? We have 3 assertions in our test, right? That’s because when an assertion fails, the test gets marked as failed and PHPUnit goes to the next test instead of executing the current test to the end.

Making the Test Pass Again

Let’s now try and get back to the nice green state of our test. We could come up with yet another hardcoded solution to make the test pass again:

<?php

class EmailAddressPartsExtractor
{
    public function extractDomain($emailAddress)
    {
        if (false !== strpos($emailAddress, 'example.com')) {
            return 'example.com';
        } elseif (false !== strpos($emailAddress, 'example.org')) {
            return 'example.org';
        } else {
            return 'example.net';
        }
    }
}

That would work, but that’s too much code. We could now go and add even more assertions to the test and that would make our test fail again. Then we’d have to add even more conditions to our hardcoded conditional logic, and so on.

The idea is to write the less amount of code to make tests pass — not more. So why bother with all that hardcoding when the actual implementation is much easier to write and requires much less code? Let’s do it properly now:

<?php

class EmailAddressPartsExtractor
{
    public function extractDomain($emailAddress)
    {
        return explode('@', $emailAddress)[1];
    }
}

All we do here is explode the passed string by the @ symbol and return the second element of the resulting array.

That’s it. That’s the actual implementation. It’s just a single line of code compared to the conditional logic shown above.

Let’s now run the test and see how it goes:

$ bin/phpunit --color tests
PHPUnit 4.8.19 by Sebastian Bergmann and contributors.

.

Time: 39 ms, Memory: 3.75Mb

OK (1 test, 3 assertions)

The F got replaced with a dot again. And the last line reports that all 3 assertions have been executed this time. Nice.

By adding more assertions, we’ve forced ourselves to provide the real implementation. The only problem here is that we’ve introduced duplication to the test method while doing that. That’s what we’re going to deal with next.