Sunday, October 23, 2011

Approval Tests in PHP

Today, after hearing a talk by Llewellyn Falco, I decided to play with the Approval Tests library. There seems to be plenty of articles on using it with Java, .NET and Ruby but none on PHP, so I figured I'd write about it here.

Approval Tests are about simplifying the testing of complex objects, large strings, et al, where traditional Assert methods might fall short. The Library works with your existing testing framework, be it PHPUnit, Rspec, JUnit, NUnit, what have you. If you've never heard of Approval Tests before please read about them first and come back when you understand the basic concepts. This article focuses on how to use the Approval Tests library in PHP.

To install the Library I downloaded the tarball from the sourceforge download page and then unpacked it into my project's directory. It complained about me not having Zend_PDF so I installed the Zend Framework "minimal" addition. This required adding the Zend Framework's "Library" directory to my PHP include_path. You'll also need PHPUnit installed with PHPUnit_CodeCoverage.

Once installed, getting the approval tests library to work with PHP on my Linux box was tricky because (as of this writing at least) the PHP library seems to have no documentation and is rather incomplete. So I pulled up my sleeves and dug into the source code.

The automatic diff tool and the functionality to display a message for how to move the "received" file to the "approved" file didn't work. The reason is approvals/Approvals.php only had a case for 'html' and 'pdf' and defaulted to the PHPUnitReporter, which itself throws a runtime exception and thus short-cicuits the functionality that takes place in the approve() method.

To remedy this, I added a case for "txt" in the getReporter() method in approvals/Approvals.php. This got it to actually try to call the diff tool but it seems to assume you're using Mac OS X because approvals/reporters/OpenReceivedFileReporter.php does a system() call to the "open" command -- which in OS X will open files as if you double-clicked the file's icon but in Linux it runs 'openvt' -- which in this case will cause an error like "Couldn't get a file descriptor referring to the console." So I edited approvals/reporters/OpenReceivedFileReporter.php and changed:

 system(escapeshellcmd('open') . ' ' . 
    escapeshellarg($receivedFilename));

to:

 system("echo '#!/bin/sh' > /tmp/reporter.command; echo 'diff -u " . 
 escapeshellarg($approvedFilename) . " " .
 escapeshellarg($receivedFilename) .
 "' > /tmp/reporter.command; chmod +x /tmp/reporter.command; 
 /tmp/reporter.command");

That got everything working for me. I'm going to email the author about this and maybe submit a patch to get it working with vimdiff. For now "diff -u" was enough, as I was following the Do The Simplest Thing That Could Possible Work rule.

Now onto the PHP source code.

 <?php
 require_once 'approvals/Approvals.php';

 class Receipt {

   private $items;

   public function __construct() {
     $items = array();
   }

   public function addItem($quantity, $name, $price) {
     $this->items[] = array('name'=>$name, 'quantity'=>$quantity, 'price'=>$price);
   }

   public function __toString() {
     return $this->implode_assoc('=>', '|', $this->items);
   }

   private function implode_assoc($glue, $delimiter, $pieces) {
     $temp = array();

     foreach ($pieces as $k => $v) {
       if (is_array($v)) {
         $v = implode(',', $v);
       }
       $temp[] = "{$k}{$glue}{$v}";

     }

     return implode($delimiter, $temp);
   }
 }


 class ReceiptTest extends PHPUnit_Framework_TestCase {

   /**
    * @test
    **/
   public function Should_create_a_new_receipt() {
     $r = new Receipt();
     $r->addItem(1, 'Candy Bar', 0.50);
     $r->addItem(2, 'Soda', 0.50);
     Approvals::approveString($r);
   }
 }

Running the test the first time creates the file:

    ReceiptTest.Should_create_a_new_receipt.received.txt

in the current directory and fails the test. It's your job to view the generated txt file and decide if it looks right. If it does, rename it to:

    ReceiptTest.Should_create_a_new_receipt.approved.txt

Now when you rerun the test it will diff the current test's output against the approved.txt file's. If they're equivalent, then the test passes, if they're not it doesn't and it's up to you to diff the received and approved files for what the differences are and figure out how to get your test to pass again.

For example, the first time you run the test, it will generate the "received.txt" with the string:

    0=>Candy Bar,1,0.5|1=>Soda,2,0.5

If you rename the file to its approved.txt equivalent, change the quantity of Sodas to 3 and rerun the test, it will fail because it would create another received.txt to diff against the approved.txt but this time the received file will contain:

    0=>Candy Bar,1,0.5|1=>Soda,3,0.5

I hope you approve this article.

5 comments:

  1. Thanks Ryan! This is the only documentation I could find for using this testing framework/method in PHP. I wouldn't have guessed that you had to go and rename the file by hand.

    ReplyDelete
  2. I know this is really old and probably nobody is gonna answer me, but: where did you find the sourcecode for PHP? I can't find it in the creator's GitHub and there is no page for the project in Sourceforge either (other than the frontpage with info).
    Thank you in advance!

    ReplyDelete
    Replies
    1. The Approval Tests page has a Download Here link at the top, then a php section that takes you to the tarball: https://sourceforge.net/projects/approvaltests/files/ApprovalTests-Php/

      Delete
    2. Wow... It was literally in front of me at the top of the page all the time. @_@
      Thank you!

      Delete

Followers