PHP

How to Learn PHP Unit Testing With Katas

All PHP code gets tested. Most of it is tested by the developer when they check to make sure the webpage looks right in their browser; some when you run an API response tool, like Postman. Some of it is tested when that same developer writes automated tests and runs them against it—this is our focus. Sometimes code is first tested when the unfortunate client or user feels like using the feature and tests the developer’s work. Hopefully, they’re not disappointed.

The most common and clear reason to use automated testing is it helps us verify our code works correctly without having to walk through an elaborate and complex set of “readying” actions to get to the code we want to test right now.

While I’m not certain, I suspect I’m not the only developer in the world who has ever spent hours bug-fixing code at the end of complicated setup procedure involving forms, file uploads, and a whole lot of stuff I don’t enjoy filling with “dummy” data.

When I look back at those times, about 20 percent of my time was spent thinking about the code and solving the concrete problems. The other 80% was going through the five-step process of selecting uploads, sending them, setting the many values the form required, and then (and only then) finding out if the edits I’d made had fixed my problem. Or, if I’d made a new stupid error like a missing semicolon.

That’s why you should be interested in unit testing and Test-Driven Development (TDD)—because it makes your life as a programmer better. It would have saved me hours of work in situations like those outlined in the last paragraph. TDD and automated testing let us focus on what we’re there for: solving complicated problems with code, and leaving the computers to do more of the rest.

What Is Unit Testing?

It’s useful before we get too deep into this discussion to clearly define our terms. Unit testing, automated testing, and TDD are all related but distinct things. They’re categories which contain each other, i.e., “automated testing” contains all “unit tests.” And, “unit testing” is generally considered to contain TDD, though not quite as precisely.

We call something “automated testing” if it is a test of a system that can run without human intervention. That’s the broadest level.

Within that, you’ll most often hear people talk about unit tests. This is a controversially used term. Some people insist a unit under such a test must be a function or method one to 10 lines long. Others insist even a whole system can be tested as part of a unit.

We don’t need to adjudicate the whole thing here. Suffice it to say a unit test tests a software system without human intervention.

What is Test-Driven Development?

TDD is a subset of unit testing as discussed above, but it’s also a little different. TDD isn’t quite a type of unit testing because it is actually a practice or course of action the developer follows. In short, it is the practice of letting your (unit) tests drive your development.

In practice, then, TDD is often spoken about as a Red, Green, Refactor cycle. That means we first write the test. This just-written test failing is what people call a “red,” and lets us know that the test is truly driving the process.

After they have seen the test fail, the developer will then write as little code as possible to make the test start to pass or turn green. How strictly people hew to “as little as possible” is another contour of the debate one could lose hours on. (To preview it: is return 2 a line of code you should ever write when you’re creating a calculator? Opinions differ.)

The final step of TDD, “refactoring” means to change the internal design of the system without changing its external behavior. Martin Fowler, the author of the seminal refactoring book, argues that without automated testing no changes to code can properly be called refactorings. With tests, we redesign the internals of our code when we feel it’s necessary. Then we run the tests without changing them to make sure they’re all still passing. In this way, we test-drive out the design of a software system.

Why Do Unit Testing or TDD?

Whether you’re doing unit testing or TDD, some of the benefits are the same. In either case, you can use the tests to verify the code works as intended and to prevent a breaking change from hitting your code base. Running tests to prevent “regressions”–code that used to work, no longer working–is both common and important.

There are people who insist regression testing is explicitly not the point of TDD. The point of TDD is that there is a process by which your code comes to exist which produces better code than simply having tests assures. Only a fool wouldn’t rely on the tests they create in the process of TDD for at least some small amount of anti-regression benefit.

The PHP Tools You Need for TDD

You’ll need a few things to do TDD—a text editor, first. This can be as complex as Visual Studio Code, PhpStorm or Sublime Text. It can also be good old Windows Notepad.

You’ll also need access to a command line like Bash. If you’re doing modern PHP, you’re probably pretty comfortable with this as it’s how most of us turn on and interact with Composer.

And finally, you’ll need PHPUnit. What’s PHPUnit? The source for it, phpunit.de, says it clearly:

PHPUnit is a programmer-oriented testing framework for PHP. It is an instance of the xUnit architecture for unit testing frameworks.

The reference to xUnit may feel like extra complexity, but it isn’t really too much. It’s identifying that PHPUnit shares a history with tools like JUnit, the Java unit testing library. There are others like it (sUnit, nUnit) and as we’re programmers, we love to make that variable first letter or phrase into a variable.

So PHPUnit is a testing framework which allows us to make assertions about our code. You can reasonably think of PHPUnit as a convenient wrapper for things you can declare will be true about your code at a certain point. PHPUnit scores and tracks whether or not they are.

PHPUnit does have features other than assertions, but we’ll not get into those. You can get a whole lot of value from testing with only assertions and PHPUnit’s runner for it.

What are Code Katas?

The final piece of the puzzle, at last. This article’s title may have intrigued you because of the name of “katas.” Kata (literally: “form”), is a Japanese word used to describe detailed choreographed patterns of movements practiced either solo or in pairs. It’s often the term used to describe practicing specific movements in martial arts.

Code katas are practice movements we can use to sharpen our testing—most specifically TDD—muscles in the same way martial arts katas are used to make people better.

There are, in various lists you’ll find online, more katas than I can count. At the end of this article, I’ll share a short list of some of the better places to look. Today we’ll focus on some specific steps of working through a single kata. What all katas have in common is that you’ll generally work through one like this:

  1. Start by having a reasonable test setup. For me, that’s usually two files open, one for my tests and one for my production code.
  2. Read over the direction for the kata you’re doing. In general, this will be pretty short, a few paragraphs at most.
  3. For about 25 minutes, work through the Red, Green, Refactor process mentioned above (and elaborated more below), solving the kata.
  4. After your time finishes, put your fingers down and throw the code away.

It can feel weird the first few times you do this to stop abruptly, in whatever state of completion you find yourself and then to throw away the work. But those steps are crucial to the process. Remember, we’re writing katas to get better at the TDD process and not to finish something we need to do for work. We do katas to hone technique rather than to complete a goal.

The Kata We’ll Be Doing

Our kata is pretty simple: we’re making a Roman numeral converter. It’ll take Arabic (or normal) numbers and return the string that represents them in the (strange and inefficient) Roman numbering system.

So when we pass 1 we’ll get I and for 2018 we’ll get MMXVIII. I could go on, but think an encyclopedia would fill in any gap in understanding you may have better than I can.

If time allows, the next step of the kata is to build the reverse direction: something that takes strings of Roman numerals and returns the integer. I rarely get to this in the time I allow myself for katas, but you may find time.

Set Up Steps for Our Roman Numeral Kata

The “Getting Started” page from PHPUnit (https://phpunit.de/getting-started/phpunit-7.html) is quite good. For doing this kata, I’ve typically used the global PHPUnit PHAR (archive) file. I prefer this because it stops me from adding to what can otherwise be a two-file operation a whole vendor directory and need for Composer.

But using PHPUnit in projects, I prefer to use the Composer installation method. It prevents the issue where different developers on your team have different PHPUnit’s installed globally and so get different output from your test suite.

Doing a Code Kata

In general, as we’ve discussed, the cycle you’ll use in doing a code kata is the following:

  1. Identify a scenario your code should handle, and write a test you can foresee failing.
  2. Confirm the test does fail and PHPUnit gives you a “red” failure.
  3. Write just enough production code that you expect the test to pass.
  4. Confirm that the test passes (are “green”). (Repeat step three if not.)
  5. If you need, refactor the code that passed the test to better reflect “good code” practices. Keep all the tests passing when you do that.

That’s it. You can obviously break down the steps more or less, but you get the basic idea right there. Write a test, see it fail, write code, see the test(s) pass, and then do any general refactoring if you need to.

A Few Steps Through the Kata

We’ll do a few instructive steps and leave the rest to you when you truly get a chance to practice it.

Getting Set Up

As mentioned above, my preference for using PHPUnit when doing katas is to keep the folder structure simple. I’ll typically have a src.php file, for the production code I’m going to write and a tests.php file for my tests.

PHPUnit comes with an autoloader and if we use this set up we can run our tests using a globally configured PHAR with the following command:

phpunit --colors --bootstrap src.php tests

Strictly speaking, we don’t need the --colors option turned on, but I like my greens to be green and my reds red. Then we’re specifying for it to load up (or bootstrap) both our production code and our tests. This allows us to keep those both as simple as possible, and allows us to have no dependence between the two files.

When you have a larger test suite, --bootstrap can be used to execute a file which executes all the code needed for your code to run. At the very least, this could be vendor/autoload.php, which will be necessary with a Composer-installed PHPUnit. In that circumstance you’ll run your tests (assuming an otherwise similar file layout) with:

./vendor/bin/phpunit --colors --bootstrap vendor/autoload.php tests

Writing Your First Test

Writing tests with PHPUnit will start by extending the PHPUnit Framework TestCase. It’ll look a little like this:

<?php
use PHPUnit\Framework\TestCase;

class TestSrc extends TestCase

PHPUnit provides a base test class. This class is most useful for its assertions (which we’ll cover momentarily) and has setUp and tearDown functionality (which allow you to run specific code before each test, or after it; we’ll not say more about that in this article).

Next, we’ll write our first test. Our tests are methods on the class we extends from PHPUnit. The test runner expects these will start with test, although there are other ways to run them if you insist on not doing so.

Our first method will look a little like:

function test1yieldsI() 
{
    $this->assertEquals('I', arabicToRoman(1));
}

In general, when people describe what you put in a test, they’ll talk about the three “A”s (Arrange-Act-Assert). What they mean is that it’s very helpful, especially in more complex testing conditions, to set up your production code first, then run that code, and finally assert what will be true after the code has run.

For simple tests, I typically skip that structure. But if you want to hew to it, a “best practice” first test might look more like:

function test1yeildsI() 
{
    // arrange
    $startingArabic = 1;
    $expectedRoman = 'I';
    // act
    $receivedRoman = arabicToRoman($startingArabic);
    // assert
    $this->assertEquals($expectedRoman, $receivedRoman);
}

The most important thing here is in the “act” and “assert” phases. What might be confusing about the “act” is that we’re calling a function called arabicToRoman without any knowledge that it exists. This is also done above in the shorter version. When you’re writing tests first, you’ll be doing this constantly: calling new methods or functions you’ve not yet written. Many people advocate TDD for exactly this reason; it makes you think about how you want your code to be invoked before you start writing it.

Our assert is also interesting. As mentioned, PHPUnit is a helpful bucket of assertions you can use. For almost all my tests when doing the Roman numeral kata, I simply use $this->assertEquals. But there are a whole bunch more as well.

Verifying Our Test Fails

To run the test, we’ll use the command from above.

phpunit --colors --bootstrap src.php tests

If you do (with the longer test form from above) you’ll get the following output (trimmed for conciseness):

There was 1 error:

1) TestSrc::test1yieldsI
Error: Call to undefined function arabicToRoman()

tests.php:11

This is what we expected and discussed. There’s no code in our “production” set yet, so the arabicToRoman function we’re calling is never defined. So we need to write that.

Writing the Production Code

My first step will be to simply create the empty function body in src.php.

function arabicToRoman($arabic)
{
}

When I do, the error changes:

1) TestSrc::test1yieldsI
Failed asserting that null matches expected 'I'.

tests.php:13

Great! This is a real failure of the function under test. So we can go on and write the function body. You might already suspect what the form of the final solution for a Roman numeral converter is. If so, you may be unpleasantly surprised that my first “green” production code is this:

function arabicToRoman($arabic)
{
    return 'I';
}

I mentioned this above, but the practice of katas and TDD dictates we write as little production code as possible to get a green. This code gets a green when we have a single test. So while we could refactor after getting here, we really shouldn’t.

You Next Few Tests

After you’ve gotten a pass at 1, you should probably go to 2 or 3. I will usually skip 4 (which is the weird subtractive thing of IV) and instead do 5. Six, and maybe seven starts to require us to generalize a bit. testThat10IsX gets us to code that looks a lot like 5. So we’re starting to find the pain of the duplication that a sheer “slimed out” version has led us to.

It’s around the point of having specific if or while blocks for 1, 5, and 10. Often I’ll go past 20 and to 30. And that point I’ll feel enough pain that I’ll set about refactoring.

The Interesting Twist: Refactoring Time!

At the point I start to want to refactor (depending on how intentionally obtuse I’ve been to that point), my production code will reach this point looking like this:

function arabicToRoman($arabic)
{
    $result = '';

    if ($arabic > 9) {
        while ($arabic > 9) {
            $result = $result.'X';
            $arabic = $arabic - 10;
        }
    }

    if ($arabic > 4) {
        $result = $result.'V';
        $arabic = $arabic - 5;
    }

    for ($i=$arabic; $i > 0; $i--) { 
        $result = $result.'I';
    }

    return $result;
}

Pretty ugly, but it’s passing the seven tests I have: 30, 20, 10, 5, 3, 2, 1. This is where the magic of testing and refactoring comes in.

Because I’m green now, I can freely make changes to this code. In general, my goal right now is that I can sense that there’s a whole lot of duplicated code that doesn’t look uniform. I like to make the similarity very obvious first. So I’ll make a series of changes favoring while loops over both the if and for I’ve got running right now. That’ll lead me to something like this:

function arabicToRoman($arabic)
{
    $result = '';

    while ($arabic >= 10) {
        $result = $result.'X';
        $arabic = $arabic - 10;
    }

    while ($arabic >= 5) {
        $result = $result.'V';
        $arabic = $arabic - 5;
    }

    while ($arabic >= 1) {
        $result = $result.'I';
        $arabic = $arabic - 1;
    }

    return $result;
}

I’m skipping steps here, but I think with your test harness you’ll find it pretty easy to guide yourself here, and give yourself the confidence it’s all still working.

Now the pattern is so clear you’d have to be trying to miss it: we have a set of pairs X and 10, Vand 5. If and when we combine them, we’re set for the big transition where we can write down that lookup table into an associative array (yay PHP!), and then foreach through it with a while loop. Which will lead to:

function arabicToRoman($number)
{
    $arabicToRoman = [
        10 => 'X',
        5 => 'V',
        1 => 'I',
    ];

    $result = '';
    foreach ($arabicToRoman as $arabic => $roman) {
        while ($number >= $arabic) {
            $result .= $roman;
            $number = $number-$arabic;
        }
    }
    return $result;
}

When I run my tests against this code, I see my fourth or fifth straight green during this refactoring. If at any point you get a red while refactoring, it’s a chance to undo to stay green.

With my test and this structure, it’s debatable whether or not I must test-drive the next phase of the code. I suspect if we fill in 4 between 5 and 1, it’d work. I suspect that if we extend upward to the fact that C is 100, it’d work. We should probably test at least a few of them on the way there though.

Finishing it Off: Testing the Edges

After we’ve gotten to point supporting all the ASCII Roman numbers (those which don’t require a bar above the top of a letter), we’ll have something like this lookup table:

$arabicToRoman = [
    1000 => 'M',

    900 => 'CM',
    500 => 'D',
    400 => 'CD',
    100 => 'C',

    90 => 'XC',
    50 => 'L',
    40 => 'XL',
    10 => 'X',

    9 => 'IX',
    5 => 'V',
    4 => 'IV',
    1 => 'I',
];

Now that we’ve filled out the “happy path” through our code, we can go ahead and move on to edge cases. As I just mentioned, the easy-to-write-as-a-string Roman numbers end at 3999. So even if I’m not really test driving it (my code already supports it) I like to write that test to assert that 3999 (my upper bound of might-receive input) gives me the right result:

function test3999()
{
    $this->assertEquals('MMMCMXCIX', arabicToRoman(3999));
}

Testing Zero

I also like to do the low end. For Roman numerals, that’s generally regarded to be zero. And zero is really interesting. What should happen when we try to convert zero into a Roman numeral? I think a reasonable answer is that we should just give back a string with no contents, ''. If we want that result, like 3999, we won’t have to write new code.

There’s also a reasonable case to make that when we see zero, or other out-of-bounds numbers, we should actually throw an exception. After all, if you were trying to represent zero in a string using Roman numerals, you might not find the empty string very satisfying.

If we want to raise an exception, we’ll write the test like this:

function test0ThrowsAnException()
{
    $this->expectException(Exception::class);
    arabicToRoman(0);
}

The customary way in PHPUnit to indicate you want an exception is to say you expectException and then pass through the exception you want. Then, (as you probably know) we’d add to our production code a clause like:

if ($number < 1) {
    throw new Exception('Zero and negative numbers are not allowed');
}

At this point, I have pretty high confidence in our code. It works for the numbers I think it reasonably can work for. And it breaks in a way I like in exceptional cases. And we’re just approaching our time limit too.

Where to Find Other Code Katas

With the Roman numberal kata under your belt, you’ll probably want to do some more. As I’ve said, there are tons of sources. You can even find “legacy code katas” as well as ones you write from scratch as we’ve done with the Roman numerals. There are lots of sources for them online, but here’s a list to get you started on sources to look for katas:

Why We Do Katas and What We Get From It

Hopefully this preview of doing PHP unit testing and TDD using katas has inspired you to try it out on your own. There are many places to find katas, and also many setups you can use. All of them should help you feel more comfortable with your tools—your testing framework, text editor, command line, etc.—and better at TDD.

PHPUnit is great for doing code katas because it’s the most widely-used testing framework in PHP. And so trying it here, and being comfortable writing and testing PHP code on your machine with your favorite tools really makes you better at doing this for real, on code that matters to you or your day job. I wish you great luck!

Note:This article originally appeared in php[architect]‘s December 2018 issue.

Standard

Leave a Reply

Your email address will not be published. Required fields are marked *