Lesson Weekend

Lesson goals:

  • Learn to include external files using require_once
  • Introduction to inheritance
  • Learn to use PHPUnit for BDD problems

Introduction - what are we going to learn?

Now that you have a basic understanding of the theory behind BDD, let's use it to actually create a program that returns a string of words in title case. We're going write our specs using a testing framework called PHPUnit.

Setup the project for title case.

We need to create a project directory - let's call it title_case. Inside of this top level folder should be three other folders: src, tests, and web. Our classes will go into the src folder and our specs will go into our tests folder.

Next, we need to use Composer to get the PHPUnit files. Create the composer.json file in your project folder and copy this text into it:

composer.json
{
    "require": {
        "phpunit/phpunit": "4.5.*"
    }
}

This tells Composer that PHPUnit is a dependency of your project. Now, in the Terminal, run Composer with $ composer install.

Now, let's create a file called TitleCaseGenerator.php in the src directory. This file is named with upper camel case - each word in the name is capitalized with no spaces - because it holds a class declaration of the same name. Inside of this file, we'll declare our TitleCaseGenerator class and give it a method called makeTitleCase.

src/TitleCaseGenerator.php
<?php 

    class TitleCaseGenerator
    {
        function makeTitleCase($input_title)
        {

        }
    }

?>

We are leaving the method empty for now, but when this method is finished, it should allow us to pass in a sentence as an input argument, and have it output the title-cased sentence for us to display on a web page. But, as per BDD best practices, we have to write a spec before we write any functionality.

Write first test

Now we're going to write our first, simplest test describing the most basic input and output operation for our makeTitleCase method. Create a file inside of the tests folder called TitleCaseGeneratorTest.php. Notice that this file is also named with upper camel case for the same reason as TitleCaseGenerator.php. Now type the following into the test file you just made:

tests/TitleCaseGeneratorTest.php
<?php 

    require_once "src/TitleCaseGenerator.php";

    class TitleCaseGeneratorTest extends PHPUnit_Framework_TestCase
    {

        function testMakeTitleCaseForOneWord()
        {
            //Arrange
            $test_TitleCaseGenerator = new TitleCaseGenerator;
            $input = "beowulf";

            //Act
            $result = $test_TitleCaseGenerator->makeTitleCase($input);

            //Assert
            $this->assertEquals("Beowulf", $result);
        }
    }

?>

This looks like a lot, but let's break it down into easier pieces.

The line require_once "src/TitleCaseGenerator.php"; tells PHPUnit to open the class we're going to test. To do this we use the keyword require_once to tell PHP where to find the TitleCaseGenerator.php file in relation to the project folder.

class TitleCaseGeneratorTest extends PHPUnit_Framework_TestCase {...} is the class declaration. We use the class keyword to create a class called TitleCaseGeneratorTest, which will be responsible for testing the class TitleCaseGenerator.

extends PHPUnit_Framework_TestCase means that this is a special kind of class that handles testing. This will be explained more later, but remember that we will always declare our test classes like this, using upper camel case, ending the file name with Test.php and changing only the class name based on the class it is testing:

class ClassIAmTestingTest extends PHPUnit_Framework_TestCase {...}
class ADifferentClassIAmToTestTest extends PHPUnit_Framework_TestCase {...}
...

Inside of this testing class, we will declare a method to run our first test. When we run PHPUnit, our test class will be instantiated and each of its methods will be executed. Just like in the BDD in plain English lesson, the simplest correct output generated for the title case problem is a one-word title like "beowulf" because a one-word title will always be capitalized. So our first function should test whether an input of "beowulf" produces an output of "Beowulf".

Now that we have looked at all of the test setup, let's look at the test itself. There are three parts to a PHPUnit test method: Arrange, Act, and Assert.

Arrange

tests/TitleCaseGeneratorTest.php
$test_title_case_generator = new TitleCaseGenerator;
$input = "beowulf";

First we arrange by gathering all of the materials we need to run our test. We create an instance of the class, TitleCaseGenerator, and store it in the variable $test_title_case_generator. Then we will need the input to give the method we are testing - in this case the string "beowulf". We will store this in a variable called $input.

Act

tests/TitleCaseGeneratorTest.php
$result = $test_title_case_generator->makeTitleCase($input);

The act part is where we actually run the method that we want to test. We go into the test instance of TitleCaseGenerator and call the method makeTitleCase (which doesn’t do anything yet, but we will get there). We also pass it our $input as an argument, and put the return value from the method into a new variable called $result.

Assert

tests/TitleCaseGeneratorTest.php
$this->assertEquals("Beowulf", $result);

Now we assert that the results from our method call should equal "Beowulf". To do this, we are calling a method called assertEquals, which is built into PHPUnit. If the assertEquals method finds that its two arguments - the desired result and the actual result - are not equal, it will return false and the test will fail.

Let's run our test now!

Go into your project folder in the Terminal and run the command $ ./vendor/bin/phpunit tests to run all of the test functions in your tests folder. If you are using a Mac you can run $ export PATH=$PATH:./vendor/bin first and then you will only have to run $ phpunit tests each time you want to run your tests. Your output will look something like this:

PHPUnit 4.3.5 by Sebastian Bergmann.

F

Time: 65 ms, Memory: 2.75Mb

There was 1 failure:

1) TitleCaseGeneratorTest::testMakeTitleCaseForOneWord
Failed asserting that null matches expected 'Beowolf'.

/Users/epicodus/Desktop/title_case/tests/TitleCaseGeneratorTest.php:18

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

Failures?

There was one test and it failed, but that is the behavior we want, since we haven't defined anything in our function yet. You can see that the Terminal output tells us the function that failed was called testMakeTitleCase inside of the test class TitleCaseGeneratorTest. This is very handy if you have a lot of tests. In this case, the test Failed asserting that null matches expected 'Beowulf'. In other words, our test failed because our function makeTitleCase returned nothing - or null - when we were expecting the capitalized string "Beowulf".

Let's make the test pass. Fill in your makeTitleCase function like this:

src/TitleCaseGenerator.php
<?php 

class TitleCaseGenerator
{
    function makeTitleCase($input_title)
    {
        return ucfirst($input_title);
    }
}

?>

ucfirst is a function that uppercases the first letter of a string. And then run the $ phpunit tests command again.

PHPUnit 4.3.5 by Sebastian Bergmann.

.

Time: 23 ms, Memory: 2.75Mb

OK (1 test, 1 assertion)

When we ran our tests this time, the OK at the bottom informs us that all tests passed. It also tells us there was one test with one assertion. The assertion was the statement where we used the assertEquals method. Also, the . on the second line symbolizes a passing test. Each F is a fail and each . is a passing test.

Now we will write a spec for the second simplest behavior - a multi-word title where each word needs to be capitalized. Let's add a second test function to the test file:

tests/TitleCaseGeneratorTest.php
<?php 

    require_once "src/TitleCaseGenerator.php";

    class TitleCaseGeneratorTest extends PHPUnit_Framework_TestCase
    {

        function testMakeTitleCaseForOneWord()
        {
            //Arrange
            $test_title_case_generator = new TitleCaseGenerator;
            $input = "beowolf";

            //Act
            $result = $test_title_case_generator->makeTitleCase($input);

            //Assert
            $this->assertEquals("Beowolf", $result);
        }

        function testMakeTitleCaseForMultipleWords()
        {
            //Arrange
            $test_title_case_generator = new TitleCaseGenerator;
            $input = "the little mermaid";

            //Act
            $result = $test_title_case_generator->makeTitleCase($input);

            //Assert
            $this->assertEquals("The Little Mermaid", $result);
        }
    }

?>

The second method is the same as the first one except we have changed the name of the method, the value of the $input variable and the expected output value in our assert statement. Let's run the tests again:

PHPUnit 4.3.5 by Sebastian Bergmann.

.F

Time: 24 ms, Memory: 2.75Mb

There was 1 failure:

1) TitleCaseGeneratorTest::testMakeTitleCaseForMultipleWords
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'The Little Mermaid'
+'The little mermaid'

/Users/epicodus/Desktop/title_case/tests/TitleCaseGeneratorTest.php:31

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

The last line Tests: 2, Assertions: 2, Failures: 1 says there were two tests and one of them failed - which makes sense because we just wrote a new test. You can also see that at the top there is both a . and an F, also meaning one pass and one fail.

The testMakeTitleCaseForMultipleWords test is failing. The expected capitalized output, 'The Little Mermaid', is next to a -. The actual result returned, 'The little mermaid', is next to a +. This returned value makes sense because all we have told our function to do so far is capitalize the first letter of the first word of its input. Now let's make this test pass. Change your makeTitleCase method in your src/TitleCaseGenerator.php file to look like this:

src/TitleCaseGenerator.php
function makeTitleCase($input_title)
{
    $input_array_of_words = explode(" ", $input_title);
    $output_titlecased = array();
    foreach ($input_array_of_words as $word) {
        array_push($output_titlecased, ucfirst($word));
    }
    return implode(" ", $output_titlecased);
}

We use the built-in explode function to split our input string into an array of separate words by telling it to look for space - " " - characters. Then we create an empty array called $output_titlecased, and for each word in $input_array_of_words we capitalize it and then push it into the $output_titlecased array. Finally, we use the implode function to turn $output_titlecased back into a string with words separated by spaces and then return it. Let’s run our tests again to see if they both pass.

PHPUnit 4.3.5 by Sebastian Bergmann.

..

Time: 24 ms, Memory: 2.75Mb

OK (2 tests, 2 assertions)

Hooray! Both tests are passing! Now we can move forward with confidence and and keep writing tests and making them pass for progressively more complex behavior.

A final word about the extends keyword

$this->assertEquals("The Little Mermaid", $result);

We are calling the assertEquals method on the $this variable because that method belongs to the TitleCaseGeneratorTest class. But why? We never declared this method.

Remember the first line?

class TitleCaseGeneratorTest extends PHPUnit_Framework_TestCase { ... }

The extends keyword means that our class is an extension of an existing class which is built into the PHPUnit framework. We say that our class TitleCaseGeneratorTest inherits methods from PHPUnit_Framework_TestCase. This is how our class knows what to do when we call assertEquals. The method has already been declared in the PHPUnit_Framework_TestCase class.

This is an example of inheritance - an important Object-Oriented Programming concept we will use more later. But basically, it means a child class like ours can be given properties and methods defined in a parent class like PHPUnit_Framework_TestCase so that we don't have to repeat our declarations. We can keep our classes smaller and more powerful.

Another example

Let's look at one more example that should be fairly familiar from the previous lessons on testing: leap year. First let's set up our project. We need to create a project folder called leap_year with src, tests, and web folders. Add a composer.json file with the same code as the previous example and run $ composer install.

Remember from the BDD in Plain English lesson, we decided that our first spec should be: "The Leap Year program says a year is not a leap year if the year is not divisible by 4. For example, we expect it to tell us that 1993 is not a leap year."

Now let's write our test first:

tests/LeapYearGeneratorTest.php
<?php

    require_once "src/LeapYearGenerator.php";

    class LeapYearGeneratorTest extends PHPUnit_Framework_TestCase
    {

        function testCheckLeapYearIsFalse()
        {
            //Arrange
            $test_leap_year_generator = new LeapYearGenerator;
            $input = 1993;

            //Act
            $result = $test_leap_year_generator->checkLeapYear($input);

            //Assert
            $this->assertEquals(false, $result);
        }
    }

?>

This should seem pretty similar to the title case example. This time, we are expecting the result of our function to be false so we include that as the first argument to the assertEquals method. When you run $ phpunit tests, you should get Failed asserting that null matches expected false. Let's make this code pass.

src/LeapYearGenerator.php
<?php

    class LeapYearGenerator
    {
        function checkLeapYear($input_year)
        {
            if (($input_year % 4) != 0)
                return false;
        }
    }

?>

In this code block, we use the modulus operator (%), which gives the remainder of dividing the number by 4. If the remainder is 0, then the number is evenly divisible by 4. So, we're saying that if it's not divisible by 4, return false. We use the ! - not - operator to check if it is not equal. If it is, we are not doing anything yet, since we haven't written a spec for it. Run your tests, and see it pass!

One thing to notice here is that we've omitted the curly brackets in our branching. If your branching has a single line of code after the conditional it is not necessary to include curly brackets. This may feel a little strange, but it's a construction you'll see and can be helpful to use. Especially in cases where there are many conditionals and just a single return statement after each.

Let's run through another cycle of BDD for this problem. Here is the next spec that we came up with: "The Leap Year program says a year is a leap year if it is divisible by 4. For example, we expect it to tell us that 2004 is a leap year."

Now, in LeapYearGeneratorTest.php, we'll translate this into code:

tests/LeapYearGeneratorTest.php
<?php

    require_once "src/LeapYearGenerator.php";

    class LeapYearGeneratorTest extends PHPUnit_Framework_TestCase
    {

        function testCheckLeapYearIsFalse()
        {
            //Arrange
            $test_leap_year_generator = new LeapYearGenerator;
            $input = 1993;

            //Act
            $result = $test_leap_year_generator->checkLeapYear($input);

            //Assert
            $this->assertEquals(false, $result);
        }

        function testCheckLeapYearIsTrue()
        {
            //Arrange
            $test_leap_year_generator = new LeapYearGenerator;
            $input = 2004;

            //Act
            $result = $test_leap_year_generator->checkLeapYear($input);

            //Assert
            $this->assertEquals(true, $result);
        }
    }

?>

Here is the code to make this pass:

src/LeapYearGenerator.php
<?php

class LeapYearGenerator
{
    function checkLeapYear($input_year)
    {
        if (($input_year % 4) != 0)
            return false;
        else
            return true;
    }
}

?>

Now, our tests should be passing. Remember, each time we get our tests passing, we should look for opportunities to refactor. Let's try this:

src/LeapYearGenerator.php
<?php

class LeapYearGenerator
{
    function checkLeapYear($input_year)
    {
        return (($input_year % 4) == 0);
    }
}

?>

(($input_year % 4) == 0) will return true if it is true and false if it is false, so it doesn't need to be in an if statement.

When creating your project folders, make sure to include src, tests, and web. Start your PHP server in the web folder. Classes go in the src folder and tests go in the tests folder.

Add a composer.json file with the following:

{
    "require": {
        "phpunit/phpunit": "4.5.*"
    }
}

Run $ composer install in your project folder in the terminal.

Create a file for the class we will be creating in upper camel case in the src folder. For example src/TitleCaseGenerator.php. We also need to create a class file for the tests that we will be writing to test our TitleCaseGenerator class. We will call it TitleCaseGeneratorTest.php. It needs to have Test.php at the end so that PHPUnit knows it is a test file that needs to be run.

Here is an example test file:

<?php 

    require_once "src/TitleCaseGenerator.php";

    class TitleCaseGeneratorTest extends PHPUnit_Framework_TestCase
    {

        function testMakeTitleCaseForOneWord()
        {
            //Arrange
            $test_title_case_generator = new TitleCaseGenerator;
            $input = "beowulf";

            //Act
            $result = $test_title_case_generator->makeTitleCase($input);

            //Assert
            $this->assertEquals("Beowulf", $result);
        }
    }

?>

The line require_once "src/TitleCaseGenerator.php"; tells PHPUnit to use the class we're going to test. extends PHPUnit_Framework_TestCase means that this is a special kind of class that handles testing.

There are three parts to a PHPUnit test method: Arrange, Act, and Assert. Arrange gathers all of the "materials" we need to run our tests, like creating instances of classes or setting variables. Act runs the actual method that we are testing. Assert tells our tests what to expect from the output of our method.

Use the command $ ./vendor/bin/phpunit tests to run the tests. If you would like to just use $ phpunit tests, you can first run $ export PATH=$PATH:./vendor/bin on a Mac.