test in action week 2
TRANSCRIPT
Test in Action – Week 2Testing Framework
Hubert Chan
Testing Framework
• Why using framework?– Reinventing the wheel?– Frameworks provides• Library / API / Syntactic Sugar• xUnit Pattern• Stub / Mock• Log / Test Coverage• Static Analysis
PHPUnit
• PHPUnit– Unit Testing Framework– Most common for PHP– Open Source– Used in known PHP Frameworks• Zend Framework• Kohana• symfony
PHPUnit Installation
• Using pear– pear channel discover (only once)
pear channel-discover pear.phpunit.depear channel-discover components.ez.nopear channel-discover pear.symfony-project.com
– Install PHPUnitpear install phpunit/PHPUnit
PHPUnit Example
class StackTest extends PHPUnit_Framework_TestCase { public function testPushAndPop() { $stack = array(); $this->assertEquals(0, count($stack));
array_push($stack, 'foo'); $this->assertEquals('foo', $stack[count($stack)-1]); $this->assertEquals(1, count($stack));
$this->assertEquals('foo', array_pop($stack)); $this->assertEquals(0, count($stack)); }}
• Test Code
PHPUnit Test Cases Rule
• General Rule1. The tests for a class Class go into a class ClassTest.2. ClassTest inherits (most of the time) from
PHPUnit_Framework_TestCase.3. The tests are public methods that are named test*.4. Write Assertions
Data Provider
• Data Provider– Return an iterative object (Iterator interface)– Use for data-driven testing• Like ACM Input data
– Use it carefully
Data Provider Sample
class DataTest extends PHPUnit_Framework_TestCase { /** * @dataProvider provider */ public function testAdd($a, $b, $c) { $this->assertEquals($c, $a + $b); }
public function provider() { return array( array(0, 0, 0), array(0, 1, 1), array(1, 0, 1), array(1, 1, 3) ); }}
• Test Code
Testing Exception
• Testing Exception– Testing for failure cases– Error Handling– Examine• Exception Type• Exception Message• Exception Code
Testing Exception Sample
class ExceptionTest extends PHPUnit_Framework_TestCase { /** * @expectedException InvalidArgumentException * @expectedExceptionMessage Right Message */ public function testExceptionHasRightMessage() { throw new InvalidArgumentException('Some Message', 10); }
/** * @expectedException InvalidArgumentException * @expectedExceptionCode 20 */ public function testExceptionHasRightCode() { throw new InvalidArgumentException('Some Message', 10); }}
• Test Code
Testing Exception Sample
class ExceptionTest extends PHPUnit_Framework_TestCase {
public function testExceptionHasRightMessage() { $this->setExpectedException( 'InvalidArgumentException', 'Right Message' ); throw new InvalidArgumentException('Some Message', 10); }
public function testExceptionHasRightCode() { $this->setExpectedException( 'InvalidArgumentException', 'Right Message', 20 ); throw new InvalidArgumentException( 'The Right Message', 10 ); }}
• Test Code
Assertion
• Assertions– assertEquals– assertTrue / assertFalse– assertContains– and etc.
Assertion Guideline
• Assertion Guideline– Using best suitable assertion– Some assertions are more easy to use• assertXmlFileEqualsXmlFile()• assertRegExp()• and etc.
– Make assertion contains useful message
Assertion Compare - Equals
• Test Codeclass EqualTest extends PHPUnit_Framework_TestCase {
public function test_AssertTrue() { $this->assertTrue("123" === "456"); }
public function test_AssertEquals() { $this->assertEquals("123", "456"); }}
Assertion Compare – Equals (cont.)
• Output• Output1) EqualTest::test_AssertTrueFailed asserting that <boolean:false> is true.
/usr/home/hubert/tmp/php/equal_compare.php:6
2) EqualTest::test_AssertEqualsFailed asserting that two strings are equal.--- Expected+++ Actual@@ @@-123+456
/usr/home/hubert/tmp/php/equal_compare.php:10
Assertion Message
• Test Codeclass EqualTest extends PHPUnit_Framework_TestCase { public function test_AssertMessage() { $handler = new LogViewHandler(); $this->assertNotEmpty( $handler->generate_ajax(), "generate_ajax() should not be empty!!" ); }}
assertEmpty(mixed $actual[, string $message = ''])
• Prototype
Assertion Message - Output
• Output1) EqualTest::test_AssertMessagegenerate_ajax() should not be empty!!Failed asserting that a string is not empty.
/usr/home/hubert/tmp/php/equal_compare.php:24
xUnit Architecture
• Test Case• Test fixtures– Pre-action / Post-action needed to run a test
• Test Suites– A set of tests that all share the same fixture– The order of the tests shouldn't matter
Test Fixtures
• Fixtures– Avoid duplicate Arrangement code– Make test cases focus on Action and Assertion
• Test Fixtures– setUp()• Pre-action before each test case
– tearDown()• Post-action after each test case
Test Execution
Fixture Example
• Test Codeclass StackTest extends PHPUnit_Framework_TestCase { protected $stack;
protected function setUp() { $this->stack = array(); }
public function testEmpty() { $this->assertTrue(empty($this->stack)); }
public function testPush() { array_push($this->stack, 'foo'); $this->assertEquals('foo', $this->stack[count($this->stack)-1]); $this->assertFalse(empty($this->stack)); }
public function testPop() { array_push($this->stack, 'foo'); $this->assertEquals('foo', array_pop($this->stack)); $this->assertTrue(empty($this->stack)); }}
Fixture Guideline
• Fixture Guideline– Only use setUp and tearDown to initialize or
destroy objects that are shared throughout the test class in all the tests• Otherwise, readers don’t know which tests use the
logic inside the setup method and which don’t
Sharing Fixture
• Sharing Fixture– Really few reason to share fixtures– Good example
• Reuse database connection
– Sample Test Codeclass DatabaseTest extends PHPUnit_Framework_TestCase { protected static $dbh;
public static function setUpBeforeClass() { self::$dbh = new PDO('sqlite::memory:'); }
public static function tearDownAfterClass() { self::$dbh = NULL; }}
Organize PHPUnit Tests
• Mapping for production code and testtests|── _log|── bootstrap.php|── handlers│ └── _details│ |── TRConverterTest.php│ └── tmql│ └── TMQLEscapeTest.php|── lib└── phpunit.xml
middleware_rev |── handlers│ └── _details│ |── TRConverter.class.php│ └── tmql│ └── TMQLEscape.class.php└── lib
Execute PHPUnit tests
• Execute all tests% phpunit
• Execute all tests in a subdirectory% phpunit handlers
• Execute single test in a subdirectory % phpunit --filter TMQLEscapeTest handlers
Test Naming
• Test Class Name– For class name “Util”– Test class name should be “UtilTest”– Test filename should be “UtilTest.php”
Test Naming
• Function Name– Test function name should be
test_<function>_<scenario>_<expect_behavior>– Example• test_escape_evenBackSlashesData_successEscape
Single Assertion / Concept per Test
• Single Assertion / Concept per Test– If a test failed, the cause may be more obvious– Compare• PHPUnit Example• Fixture Example
– testPushPop test 3 concepts• Test for Empty / Push / Pop
Clean Test
• Clean Test– Keeping Tests Clean– Or you will not maintain them– Test codes are as important code as production– Write Test API / Utility / DSL– Clean TDD cheat sheet• http://www.planetgeek.ch/2011/01/04/clean-code-and
-clean-tdd-cheat-sheets/
F.I.R.S.T
• F.I.R.S.T– Fast– Independent (isolated)– Repeatable– Self-Validating– Timely
References
• Clean Code• The Art of Unit Testing