The Grumpy Programmer's Guide To Building Testable PHP Applications (2015)

6. Like An Onion, Your Application Has Layers

I think it is obvious that, if you are going to be able to test your application with any sort of automated tool, you are going to have to split up your business logic from your display logic.

We’ve all built the application that is one big ball of mud. You know, two or three PHP scripts that mash together code that checks the value of $_GET, then branches off to do a MySQL query, then further loops through the result set outputting things. All in one place, but tremendously difficult when you need to update something. Which you, of course, do live on the server. I’ve been that guy. Don’t be that guy any more. Life is too short and too many tools exist for you to use that excuse any more.

Most web application frameworks accomplish this task by encouraging use of the Model-View-Controller software architectural pattern. Why is this a good idea? Because it forces the decoupling of the different layers of your application. Unless, of course, the framework itself is not built properly. Hey, it happens.

So take your cue from these web application frameworks. Even if you are not going to use one of them because you are using your home-spun Precious Snowflake Framework (PSF) at least be smart and build it in such a way that you have your business logic in one place, your request-response logic in another, and your display logic somewhere else that you can work on them in isolation.

Think of these things as layers for your application. To use a food analogy, your models, code that represents the data your application will use, is the bottom layer of your cake. Your controllers, which are called as the result of a specific request and then determine what the correct response should be, are the second layer of your cake, maybe with a little filling. Finally, your presentation layer is the frosting and delicious toppings for your cake. You want to be able to make changes to any of these layers at any time.

I can already hear you saying “But Chris, how often do you really change things like that?” More often than I would care to admit. Nobody makes the correct choice every single time when they build an application. Now, you might end up getting trapped within a particular framework because it requires a lot of discipline to only use the parts of a framework that you really need.

What you really want is an application that consists of well-crafted, well-tested reusable components that use a framework as simply the glue to get them to speak to each other. I’ve been involved in an open source project known as Phix that is attempting to help build a set of tools to encourage this exact type of architecture for your application. But this is not a guide about Phix. We can use some of the philosophy behind Phix to help you.

If you’re serious about providing useful tests for your application, you have to follow the mantra:

If you can’t automate a test for it, then it’s broken.

Why do I say that? If you don’t have a good set of tests then you really don’t know if the application is going to behave in a way you expect. Of course there are many barriers to making this happen. Almost all of them are because the developer has chosen an incorrect solution for the problem. That might be a harsh thing to say, but it is usually the truth. I am not an exception to this rule either.

I think that testing data models is one area where many beginner to intermediate testers make mistakes. They fail to understand that the goal is to ensure that you are representing your data in a way that you expect and that you are also able to separate the generation of the data from the code that represents it.

I can be honest with you because you’ve read my guide this far: testing does take a lot of discipline and you often find yourself writing as much support and scaffolding code as code that actually works. Personally, I think that most of this work is very useful because it is a down payment on the future. All this extra work is an attempt to protect your code against unintended consequences. In other words, you want your tests to detect if you’ve broken something without knowing it.

Here’s an example of what I call scaffolding code that you need in order to support the types of tests that provide value. In our test application, we have to generate standings. To do this we select a series of Games objects across our desired date range, then do a bunch of work to figure out how many wins and losses, and then return that list sorted by winning percentage.

Now, if I wanted to, how would I test this thing? The correct way to test it would probably involve creating a data fixture file that contains serialized objects that represent all the games we wish to generate standings from.

How do we create that fixture?

 1 <?php

 2 $conn = new PDO(

 3     'pgsql:host=localhost;dbname=ibl_stats',

 4     'stats',

 5     'st@ts=Fun'

 6 );

 7

 8 echo "Collecting all games in our season...\n";

 9 $gameMapper = new \IBL\GameMapper($conn);

10 $allGames = $gameMapper->findAll();

11 echo "Writing games objects into fixture file...\n";

12 file_put_contents('./fixtures/games.txt', serialize($allGames));

13 echo "Done\n";

Since we have the fixture, what’s our test look like to make sure that our standings model is working the way we want it to work?

 1 <?php

 2

 3 $testGames = unserialize(file_get_contents('./fixtures/games.txt')); 

 4 $conn = new \PDO(

 5     'pgsql:host=localhost;dbname=ibl_stats',

 6     'stats',

 7     'st@ts=Fun'

 8 );

 9 $testFranchiseMapper = new \IBL\FranchiseMapper($conn);

10 $testStandings = new \IBL\Standings($testGames, $testFranchiseMapper);

11 $testResults = $testStandings->generateBasic();

12 $this->assertTrue(count($testResults) > 0);

13 $testResult = $testResults['AC']['East'][0];

14 $this->assertEquals(1, $testResult['teamId'], 'Got expected team ID');

15 $this->assertEquals(97, $testResult['wins'], 'Got expected win total');

16 $this->assertEquals(65, $testResult['losses'], 'Got expected loss total');

17 $this->assertEquals('--', $testResult['gb'], 'Got expected GB total');

Okay, not bad for a first pass, but do you see the problem here? We are using a real Franchise mapper, instead of a mock one. That matters because we shouldn’t have to actually talk to a database to get our information about what Franchises exist. That would be making the test dependant on our database, which we don’t want.

What we should do is create another fixture that passes in all the Franchise objects that we will need, and then alter the Standings model itself internally to rely on those for any info that it wants.

Create a tool that generates the Franchise fixture based on the data you have in your database.

 1 <?php

 2 include 'test_bootstrap.php';

 3

 4 $conn = new PDO(

 5     'pgsql:host=localhost;dbname=ibl_stats',

 6     'stats',

 7     'st@ts=Fun'

 8 );

 9 echo "Collecting all fixtures for our league...\n";

10 $mapper = new \IBL\FranchiseMapper($conn);

11 $allFranchises = $mapper->findAll();

12 echo "Writing franchise objects into fixture file...\n";

13 file_put_contents('./fixtures/franchises.txt', serialize($allFranchises));

14 echo "Done\n";

Now, we rewrite our test to use the new fixture and to test that our Standings model accepts an array of Games and array of Franchises as constructor arguments.

 1 <?php

 2 include './test_bootstrap.php';

 3

 4 class StandingsTest extends \PHPUnit_Framework_TestCase

 5 {

 6     public function testGenerateRegular()

 7     {

 8         $data = file_get_contents('./fixtures/games.txt');

 9         $testGames = unserialize($data);

10         $data = file_get_contents('./fixtures/franchises.txt');

11         $testFranchises = unserialize($data);

12         $testStandings = new \IBL\Standings($testGames, $testFranchises);

13    

14         // Rest of the test is the same

15     }

16 }

I wasn’t kidding that it requires a lot of extra code to support a test suite that does anything other than a very superficial set of tests. At the same time, the gyrations often required to provide this extra support gives you a very good (and often brutal) look at the design of your code.

If you’ve noticed, I’m doing this using what people would normally consider fixtures when doing unit testing. The most common method is to use database fixtures and then pull in some sort of library that handles turning those fixtures into data. You then are able to use some sort of mock object that acts like a database connection as far as your tests are concerned.

There are times when you do need database fixtures. Maybe you cannot decouple things in the way I have been able to. If you want to go the route of using database fixtures then I recommend you take a look at Phactory. It provides a simpler, code-driven way to create database fixtures and is an ideal fit if your application is using PDO.

Either way, you have to be aware of the drawbacks of using fixtures. When you change a test you will find yourself having to change the fixtures as well. It also slows down your tests when it has to load fixtures either the traditional way or my way of creating data objects. The slower your tests, the more likely developers will try and skip running the full test suite.

For this sample application, creating new data fixtures if my tests change is simple because I wrote small scripts to actually create them. Modify the fixture creation script, run it again, and I have a newly updated data fixture. It’s certainly a lot easier than manually editing a large number of XML files if your tests require different data.

In other words: be smart about how much data you are importing for testing purposes. Chances are that you do not need a complete data set, just a small slice of data in order to validate functionality. If you can refactor your code to use objects instead of mocking out database connections, that’s even better.

Here’s another mantra you should take to heart:

If it’s not yours, wrap it up.

If you are using a 3rd-party API to do something like user authentication you will quickly discover that it becomes very difficult to test. A typical scenario might be where this 3rd-party API requires a redirect to their site where the user fills out some information and then they are returned to your application with some sort of token that says you are who you claim to be. Now this is good for you because it removes the burden of managing those credentials yourself, but bad because how do you test such a thing?

You might be lucky and the application you are using has a way to capture the request and response cycle. The sample application for this guide does not, so let’s think of how you could use something like Facebook Connect.

The answer is, of course, that you place your use of a 3rd party authentication service inside a wrapper. Stay with me as I explain the logic.

Instead of thinking “how do I create a test where I talk to Facebook” you turn it on it’s head and say “how do I create a test for an authentication service that uses Facebook Connect as it’s data source?”. I hope you can see the difference.

The normal way of using Facebook Connect is that you use their own SDK (or if you are a masochist you write your own OAuth implementation) which basically redirects the user to Facebook, where they enter their login credentials for the site. If their credentials are good, they are redirected back to your application along with a token that you then can use to get information about the user.

In cases where I myself have used Facebook Connect for authentication purposes I tend to store information about the user in the session and then refer to it later. Here’s an example of what I mean:

 1 <?php

 2 // Assume $facebookUser is the object representing info about our user

 3

 4 $_SESSION['email'] = $facebookUser->getEmail();

 5 $_SESSION['name'] =

 6      $facebookUser->getFirstName() .

 7      ' ' .

 8      $facebookUser->getLastName();

 9

10 // In other parts of the app when I need to check if the user is

11 // authenticated properly

12

13 if (isset($_SESSION['email'])) {

14     // Execute the desired functionality

15 } else {

16     // Redirect user to log in with Facebook Connect

17 }

As an aside, I tend to put the code that checks if a user is properly authenticated in whatever structure best suits the framework I am using for the project. Some frameworks make it easier than others to execute code in a controller before it runs your action-specific code.

However, if you look at my little sample above you can see why that would be so difficult to test: we’re relying on having information stored in the session. How could we attempt to fix this?

The first thing we need to do is stop doing that direct check of the contents of the session and create an object that handles the task of telling us if we are authenticated or not.

 1 <?php

 2 // New object that holds info about your Authenticated user

 3

 4 class AuthUser

 5 {

 6

 7     ...

 8

 9     public function check()

10     {

11         return (isset($_SESSION['email']));

12     }

13 }

14

15 // Then in the controller where you check authorization...

16

17 $authUser = new AuthUser();

18

19 if ($authUser->check() === true) {

20     // Execute the desired functionality

21 } else {

22     // Redirect user to log in with Facebook Connect

23 }

That’s better, but we’re still relying on a session being available. How can we reduce that particular dependency?

 1 <?php

 2

 3 class AuthUser

 4 {

 5     protected $_sessionObject;

 6

 7     public function __construct($sessionContents)

 8     {

 9         $this->_sessionContents = $sessionContents;

10     }

11   

12     public function check()

13     {

14         return (isset($this->_sessionContents['email']));

15     }

16 }

Now we’re making even more progress. Now let’s imagine we are wanting to test some controller logic. In our sample application we are aiming to be able to load controller code independently of the environment we are in. This means that we have to make sure that we do not depend on the existence of things like $_SESSION in this code.

However there are also some additional concerns. To run our test we do not want to have to worry about actually connecting to Facebook in order to get info about the user. This is where the concept of mock objects can be added to our toolkit.

Mock objects are created specifically for the purpose of making testing easy. At their most basic level you use them to replace an object that might need to communicate with the “outside world” such as a database or a 3rd party API. There are many different tools that can create mock objects for you (PHPUnit has built-in support for it) but others like to use Padraic Brady’s excellent Mockery mock object framework.

In our hypothetical case we want to create a mock object for our Facebook object. Here’s how you might do it:

 1 <?php

 2 // Inside your unit test

 3 $facebookMock = \Mockery::mock(

 4     '\FacebookAPI\Facebook',

 5     array(

 6         'getEmail' => 'test@littlehart.net',

 7         'getName' => 'Testy McTesterson'

 8     )

 9 );

10

11 $sessionContents['email'] = $facebookMock->getEmail();

12 $auth = new AuthUser($sessionContents);

13 $this->assertTrue($auth->check());

Notice how effective the mock object is here. You get all the benefits of having an object that matches one that your code is dependent on without having to actually speak to Facebook in order to make a test. I believe that is a win-win situation when it comes to testing.

Testing your display logic via automated tests is something that often generates blank stares when I talk about it. “Why don’t you just test that stuff in a browser.” My usual answer is along the lines of “because I’m a human and humans make mistakes and skip things when they are testing a web page for the 100th time.” Luckily it doesn’t take much work to capture the output of your PHP application for testing purposes.

Here’s a snippet from a controller in our sample application

 1 <?php

 2 include 'bootstrap.php';                                                       

 3

 4 ...

 5

 6 echo $twig->render(

 7     'index.html',

 8     array(

 9         'currentWeek' => $currentWeek,

10         'currentResults' => $currentResults,

11         'currentRotations' => $currentRotations,

12         'currentSchedules' => $currentSchedules,

13         'franchises' => $franchises,

14         'rotationWeek' => $rotationWeek,

15         'scheduleWeek' => $scheduleWeek,

16         'standings' => $regularStandings,

17     )

18 );

Using that same structure we could very easily write a test that looks something like this

 1 <?php

 2 include './test_bootstrap.php';

 3

 4 class MainPageViewTest extends \PHPUnit_Framework_TestCase

 5 {

 6     protected $_twig;

 7

 8     public function setUp()

 9     {

10         // also include our libraries installed using Composer

11         include APP_ROOT . 'vendor/.composer/autoload.php';

12

13         // We are using Twig for templating

14         $loader = new \Twig_Loader_Filesystem(APP_ROOT . 'templates');

15         $this->_twig = new \Twig_Environment($loader, array('debug' => true));

16         $this->_twig->addExtension(new \Twig_Extensions_Extension_Debug());

17     }

18

19     public function tearDown()

20     {

21         unset($this->_twig);

22     }

23

24     public function testMainPage()

25     {

26         $dbConn = new PDO(

27             'pgsql:host=localhost;dbname=ibl_stats',

28             'stats',

29             'st@ts=Fun'

30         );

31         // Load data that we will need for the front page

32         $gameMapper = new \IBL\GameMapper($dbConn);

33         $franchiseMapper = new \IBL\FranchiseMapper($dbConn);

34         $rotationMapper = new \IBL\RotationMapper($dbConn);

35         $scheduleMapper = new \IBL\ScheduleMapper($dbConn);

36

37         $games = unserialize(file_get_contents('./fixtures/games.txt'));

38         $franchises = unserialize(

39             file_get_contents('./fixtures/franchises.txt')

40         );

41         $standings = new \IBL\Standings($games, $franchises);

42         $regularStandings = $standings->generateRegular();

43         $currentWeek = 27;

44         $currentGames = unserialize(

45             file_get_contents('./fixtures/games-27.txt')

46         );

47         $currentResults = $gameMapper->generateResults(

48             $currentGames,

49             $franchises

50         );

51         $rotations = unserialize(

52             file_get_contents('./fixtures/rotations-27.txt')

53         );

54         $currentRotations = $rotationMapper->generateRotations(

55             $rotations,

56             $franchises

57         );

58         $rawSchedules = unserialize(

59             file_get_contents('./fixtures/raw-schedules-27.txt')

60         );

61         $franchiseMap = unserialize(

62             file_get_contents('./fixtures/franchise-mappings.txt')

63         );

64         $currentSchedules = $scheduleMapper->generate(

65             $rawSchedules,

66             $franchiseMap

67         );

68

69         // Display the data

70         $response = $this->_twig->render(

71             'index.html',

72             array(

73                 'currentWeek' => $currentWeek,

74                 'currentResults' => $currentResults,

75                 'currentRotations' => $currentRotations,

76                 'currentSchedules' => $currentSchedules,

77                 'franchises' => $franchises,

78                 'rotationWeek' => $currentWeek,

79                 'scheduleWeek' => $currentWeek,

80                 'standings' => $regularStandings,

81             )

82         );

83         $standingsHeader = "Standings through week 27";

84         $resultsHeader = "Results for week 27";

85         $rotationsHeader = "Rotations for Week 27";

86         $scheduleHeader = "Schedule for Week 27";

87         $rotation = "KC Greinke, CHN Lilly -2, CHN Wells -2";

88         $this->assertTrue(stripos($response, $standingsHeader) !== false);

89         $this->assertTrue(stripos($response, $resultsHeader) !== false);

90         $this->assertTrue(stripos($response, $rotationsHeader) !== false);

91         $this->assertTrue(stripos($response, $scheduleHeader) !== false);

92

93         // Look for a known team abbreviation

94         $this->assertTrue(stripos($response, "MAD") !== false);

95

96         // Look for a specific rotation to appear

97         $this->assertTrue(stripos($response, $rotation) !== false);

98     }

99 }

So what is the best way to look for the data inside our HTML output? You have two options in my opinion.

If you’re looking for a specific string to appear in the HTML output, like say a message that you successfully created a new widget, then you are probably better off using PHP’s built-in string searching functions.

 1 <?php

 2

 3 $standingsHeader = "Standings through week 27";

 4 $resultsHeader = "Results for week 27";

 5 $rotationsHeader = "Rotations for Week 27";

 6 $scheduleHeader = "Schedule for Week 27";

 7 $rotation = "KC Greinke, CHN Lilly -2, CHN Wells -2";

 8 $this->assertTrue(stripos($response, $standingsHeader) !== false);

 9 $this->assertTrue(stripos($response, $resultsHeader) !== false);

10 $this->assertTrue(stripos($response, $rotationsHeader) !== false);

11 $this->assertTrue(stripos($response, $scheduleHeader) !== false);

12

13 // Look for a known team abbreviation

14 $this->assertTrue(stripos($response, "MAD") !== false);

15

16 // Look for a specific rotation to appear

17 $this->assertTrue(stripos($response, $rotation) !== false);

If you are trying to make sure that your output is formatted in a specific way, you should probably be looking to use a tool like PHP’s bulit-in SimpleXML functions to load your output, create XML out of it (after all, HTML is really a subset of XML) and then use the xpath() method. Learning XPath is not the easiest thing to do. I did a lot of work with it when I used to work for a sports data integration company that provided XML data feeds. But if you want to write a test to make sure that your error messages are being correctly wrapped in the proper CSS class, well, there really isn’t any other way to make it happen.

So if you wanted to look for, say, a specific <div> tag in the output being generated, I think the test would look a little like this with a little help from a third-party library called Zend_Dom_Query.

1 <?php

2

3 // $response contains the output of our test page...

4

5 $domQuery = new \Zend_Dom_Query($response);

6

7 // Find the div for our standings display

8 $node = $domQuery->queryXpath('//div[@id="standings"]');

9 $this->assertTrue($node != "");

I think the important thing here to remember is that our goal for building a testable view is that we need to be able to capture the HTML output before it is sent to the browser. That way we can search the output for strings (or DOM elements if you prefer that method) to match our expectations.