Showing posts with label php. Show all posts
Showing posts with label php. Show all posts

Thursday, December 20, 2007

Unit testing in PHP

I needed to do some unit testing for some PHP5 utility classes, and wanted some unit test framework. Here are my requirements:

  • Simple. If it's more than one file, something is wrong.
  • Unrestrictive license. I should be able to include the one file in any package I create for distribution, give to clients, whatever.
  • Short namespace. I don't want to type SomeBloatedClass::testIfTrueOnTruesdayOnly(...), when something like assert("foo" == "bar") will do.
  • Easy to run, no need for anything more than a command line

Oddly I couldn't find anything. Lots of links to outdated information. Lots of monster web framework testing things. PHPUnit is baffling. I found out of date versions, links to PEAR that didn't work, etc. And it's huge! Why? Unit testing should be short and sweet.

So screw it: here's my 100-line version. It does just about everything the fat ones do. Here's the "manual" (more samples in source file)

  • Add require_once('UnitTest.php');
  • Create a class that extends UnitTest
  • Make a test by adding a method to the class. The method name must start with test
  • Test values in the test method with assert and assertEquals($val, $expected)
  • Optionally define a public function setup(), public function teardown() functions. These will be called before and after each test.
  • Add at the bottom unittest('classname')
  • Run test with php yourfile
  • Script will return 0, if all tests succeeded, 1 if failures occurred, and 2 if the class couldn't even be loaded

For example:

#!/usr/bin/env php
<?php
require_once('UnitTest.php');

class ATest extends UnitTest
{
 
    public function testPass()
    {
        assert(1 == 1);
    }

    public function testNot()
    {
        assert(1 == 2);
    }

    public function testFail()
    {
        assertEquals("foobar", 10);
        assertEquals("foobar", 10, "optional message goes here";
    }

    public function testError()
    {
        throw new Exception("wtf");
    }
}


// RUN IT
unittest('ATest');
?>

Running this will produce:

$  ./sample.php 
testPass: OK
testNot: Fail : Assertion failed @ line 63 in ATest::testNot
testFail: Fail : foobar != 10 @ line 63 in ATest::testFail
testError: Error : wtf @ line 24 in filename
Total Tests: 4
total = 4, pass = 1, fail = 2, error = 1
$ echo $?
1

Yeah the output isn't so pretty. So... change it! Need more fancy methods? Add 'em! You can modify the base, or subclass it. need "Test Suites" ... use directories!

And go forth and make unit tests!

Oh yeah, improvements and suggestions welcome. One more method might be assertEqualsFloat which would take 2 floats and a tolerance.

Tuesday, December 11, 2007

Tamperproof URLs and PHP

I finally posted my slides for "Tamperproof URLS and PHP" from the NYPHP talk on 27-Nov-2007 at http://modp.com/slides/securestring/. In addition the source code and unit test are posted. This is a temporary spot until code.google.com bumps up my resources and then it will go into SVN.

Thank you all for coming. Based on the questions and feedback, I have completely revamped the slides and added a lot more examples. Enjoy! --nickg

Wednesday, October 17, 2007

php string "startswith" let me count the ways

I bounce around between half a dozen different programming languages, so I forget all the little variations. Recently I needed to test in php if a string starts with another (a prefix). So I typed in php string startswith, expecting to get the php manual with some built-in function.

oh no. It's not built in, that's fine. It's the online advice that frightens me. In order from google:

Take 1: use strstr

if (strstr($source, $prefix) == $source)) {
    echo "'$source starts with '$prefix'\n";
} else {
    echo "'$source' does not start with '$prefix'\n";
}

Uhhh if the source does not contain the prefix, then the entire string is searched. Worse then you have to do a full string compare.

Take 2: use strpos

if (strpos($source, $prefix) === 0)) {
    echo "'$source starts with '$prefix'\n";
} else {
    echo "'$source' does not start with '$prefix'\n";
}

Marginally better. Very fast if the source does start with the prefix, very slow if it does not. Lots of confusion on how to test for false (!!!) online.

Take 4: Use substr

The first example I saw just had hardwired examples, but it was something like this:

if (substr($source, 0, strlen($prefix)) == $prefix)) {
    echo "'$source starts with '$prefix'\n";
} else {
    echo "'$source' does not start with '$prefix'\n";
}

Requires creating a new string and doing a full string comparison, however it does not scan the whole string.

Take 5: Use preg_match

good grief. My favorite was this "tip" to "check if a string starts with a specific character."

   if (preg_match('/^a/', $str)) {
                echo "String starts with an a";
   }

No comment on this one.

PLEASE READ

ok team, here it is.

function str_startswith($source, $prefix)
{
   return strncmp($source, $prefix, strlen($prefix)) == 0;
}

It fails fast, it doesn't create new strings that get thrown away, it works in all cases. Granted strncmp is a bit cryptic to people who have never used "C", but it's a of shocker that I didn't see this online.

Here's my "unit test" for it.

function mytest($source, $prefix) {
    if (str_startswith($source, $prefix)) {
       echo "'$source' starts with '$prefix'\n";
    } else {
       echo "'$source' does not start with '$prefix'\n";
    }
}

mytest("foobar", "foo");
mytest("foobar", "food");
mytest("foobar", "bar");
mytest("foobar", "FOO");
mytest("foobar", "foobar1");
mytest('', '');
mytest('', 'foo');
mytest('foobar', '');

It better output:

'foobar' starts with 'foo'
'foobar' does not start with 'food'
'foobar' does not start with 'bar'
'foobar' does not start with 'FOO'
'foobar' does not start with 'foobar1'
'' starts with ''
'' does not start with 'foo'
'foobar' starts with ''

Interview Question

Since it seems that people have trouble with this, it makes a great interview question.

Here's a list of the php string functions. Write down as many functions as you can that check to see if one string starts with another. What are the pros and cons for each method? What test cases do you need to check?