The First Test
Remember our sample project? We need a system that prevents users from signing up with email addresses having particular characteristics. One of those characteristics is the use of a domain like example.com. So the first thing we need is to extract the domain part from an email address. Let’s start with a test for a class that will extract parts of an email address.
Writing the Test
We’re going to start with a test instead of writing the actual code and then adding tests for it. A benefit of that approach is to be able to figure out the most convenient way to use our class as a client. This leads to better code because if it’s hard to use a class in tests, it’s going to be hard to use for clients as well.
Another benefit of writing tests first is to define the boundaries of what our “real” code is supposed to do. If all tests pass, we’re either done or have to add more tests.
We will come back to these and other benefits of writing tests first later — after you’ve had enough of instant gratification of writing a couple of tests and making them pass. For now, let’s get to work.
We need a directory to put our tests into. Let’s create a directory called tests in the directory of the project.
Now, let’s create the file for the test class we’re going to write. Let’s call it EmailAddressPartsExtractorTest.php.
Finally, let’s write the test itself:
1 <?php
2
3 class EmailAddressPartsExtractorTest
4 extends PHPUnit_Framework_TestCase
5 {
6 public function testExtractDomain()
7 {
8 $extractor = new EmailAddressPartsExtractor();
9 $this->assertEquals(
10 'example.com',
11 $extractor->extractDomain('elnur@example.com')
12 );
13 }
14 }
On line 3, we define the EmailAddressPartsExtractorTest test class that extends PHPUnit_Framework_TestCase. PHPUnit_Framework_TestCase is the class all PHPUnit test classes need to extend in order to be treated as such by PHPUnit. If we didn’t, PHPUnit would just skip that class instead of looking for test methods in it.
On line 6, we define the testExtractDomain() test method. PHPUnit knows that it’s a test method because of the test prefix. Not all methods in a test class have to be test methods. If you define a method without the test prefix, PHPUnit will just skip it.
On line 8, we create the $extractor instance of the EmailAddressPartsExtractor class. Don’t worry that the class doesn’t exist yet; we’ll get to it soon.
On lines 9-12, we call the assertEquals() method that comes with the PHPUnit_Framework_TestCase class that our test class extends. We pass two arguments to it: the expected result and the actual result of calling the extractDomain() method of our not yet existing class. According to the name of the method, it’s supposed to extract the domain part of an email address passed to it.
Let’s now run the test and see how it goes:
$ bin/phpunit tests
PHPUnit 4.8.19 by Sebastian Bergmann and contributors.
PHP Fatal error: Class 'EmailAddressPartsExtractor' not found in /home/elnu\
r/proj/phpunit-starter/tests/EmailAddressPartsExtractorTest.php on line 8
It failed as expected. Let’s fix the problem by defining the missing class.
Making the Test Pass
Before we add the missing class, we need to create a directory for the “real” code of our application. Remember the autoload section from we added to the composer.json file? It points to the src directory of our project. Let’s create that directory then.
Now that we have the directory, let’s create the file that will hold the class. Let’s name it EmailAddressPartsExtractor.php and fill it with the following code:
<?php
class EmailAddressPartsExtractor
{
}
As you can see, that’s not much. But let’s rerun our test again:
$ bin/phpunit tests
PHPUnit 4.8.19 by Sebastian Bergmann and contributors.
PHP Fatal error: Call to undefined method EmailAddressPartsExtractor::extra\
ctDomain() in /home/elnur/proj/phpunit-starter/tests/EmailAddressPartsExtrac\
torTest.php on line 11
We solved the previous problem but ran into another one — the missing method. Let’s add it:
<?php
class EmailAddressPartsExtractor
{
public function extractDomain($emailAddress)
{
}
}
And let’s rerun the test again:
1 $ bin/phpunit tests
2 PHPUnit 4.8.19 by Sebastian Bergmann and contributors.
3
4 F
5
6 Time: 38 ms, Memory: 4.00Mb
7
8 There was 1 failure:
9
10 1) EmailAddressPartsExtractorTest::testExtractDomain
11 Failed asserting that null matches expected 'example.com'.
12
13 /home/elnur/proj/phpunit-starter/tests/EmailAddressPartsExtractorTest.php:12
14
15 FAILURES!
16 Tests: 1, Assertions: 1, Failures: 1.
That’s much better. Now, instead of PHP itself crashing with fatal errors because of an undefined class or method, we get PHP executing properly and instead see our PHPUnit assertion fail. As you can see, we get a completely different output in this case. Let’s go through it.
On line 4, we see an F that stands for “failure”. We get a single character for each test we execute. Since we only have one test now, all we see is a single character. We’ll see more when we add more tests.
On line 6, PHPUnit reports how much time it took to run all tests and how much RAM was used by PHP.
Line 8 is self-explanatory.
On lines 10-11, PHPUnit tells us about the failed assertion. First it tells us which test has failed and then reports the reason it failed. That reason is specific to each assertion method. In our example, we use assertEquals() that expects the expected and actual values to match. Since our extractDomain() method doesn’t have any code in it, it returns null. And that’s what the assertion failure tells us about.
On line 13, PHPUnit reports the test class and the exact line the of the failed assertion. Since we split the assertion over 4 lines of code, it points to the last line of that assertEquals() method call.
On line 16, PHPUnit tells us about the total number of tests methods run, the total number of assertions executed, and the total number of failed assertions. Since a test method can have multiple assertions in it, that makes sense.
What’s left now is to make the test pass. Let’s do that now:
<?php
class EmailAddressPartsExtractor
{
public function extractDomain($emailAddress)
{
return 'example.com';
}
}
1 $ bin/phpunit --color tests
2 PHPUnit 4.8.19 by Sebastian Bergmann and contributors.
3
4 .
5
6 Time: 37 ms, Memory: 3.75Mb
7
8 OK (1 test, 1 assertion)
Yay! It’s so much nicer to see green instead of red. Our first test is passing. And the output is much shorter this time. That’s because there are no failures for PHPUnit to report about.
The F on the line 4 got replaced with a dot. A dot means a passing test.
And the last 8th line says OK and reports how many test methods and assertions were executed. Since there are no failures here, PHPUnit doesn’t mention them.
But wait! Something is definitely wrong with our domain extracting implementation. Instead of writing some real logic, we just hardcoded the expected result. Isn’t that cheating?
Well, not really. I mean, we made the test pass, right? Who cares how exactly we did that? Oh, you do? Okay. Scroll to the next chapter and we’ll deal with that there.