More Related Content Similar to Best Practice Testing with Lime 2 Similar to Best Practice Testing with Lime 2 (20) Best Practice Testing with Lime 236. Testing Strategies – 4 Phase Test // @Test: resize() resizes the thumbnail copy( 'data/fixtures/test.png' , 'web/uploads/test.png' ); $thumbnail = new CukeetThumbnail( 'web/uploads/test.png' ); $thumbnail ->resize( 100 , 100 ); $size = getimagesize( $thumbnail ->getPath()); $t ->is( $size , array ( 100 , 100 ), 'The image has been resized' ); unlink( 'web/uploads/test.png' ); unset( $thumbnail ); Our Tested Class 37. Testing Strategies – 4 Phase Test // @Test: resize() resizes the thumbnail copy( 'data/fixtures/test.png' , 'web/uploads/test.png' ); $thumbnail = new CukeetThumbnail( 'web/uploads/test.png' ); $thumbnail ->resize( 100 , 100 ); $size = getimagesize( $thumbnail ->getPath()); $t ->is( $size , array ( 100 , 100 ), 'The image has been resized' ); unlink( 'web/uploads/test.png' ); unset( $thumbnail ); Fixture Setup 38. Testing Strategies – 4 Phase Test // @Test: resize() resizes the thumbnail copy( 'data/fixtures/test.png' , 'web/uploads/test.png' ); $thumbnail = new CukeetThumbnail( 'web/uploads/test.png' ); $thumbnail ->resize( 100 , 100 ); $size = getimagesize( $thumbnail ->getPath()); $t ->is( $size , array ( 100 , 100 ), 'The image has been resized' ); unlink( 'web/uploads/test.png' ); unset( $thumbnail ); Test Execution 39. Testing Strategies – 4 Phase Test // @Test: resize() resizes the thumbnail copy( 'data/fixtures/test.png' , 'web/uploads/test.png' ); $thumbnail = new CukeetThumbnail( 'web/uploads/test.png' ); $thumbnail ->resize( 100 , 100 ); $size = getimagesize( $thumbnail ->getPath()); $t ->is( $size , array ( 100 , 100 ), 'The image has been resized' ); unlink( 'web/uploads/test.png' ); unset( $thumbnail ); Result Verification 40. Testing Strategies – 4 Phase Test // @Test: resize() resizes the thumbnail copy( 'data/fixtures/test.png' , 'web/uploads/test.png' ); $thumbnail = new CukeetThumbnail( 'web/uploads/test.png' ); $thumbnail ->resize( 100 , 100 ); $size = getimagesize( $thumbnail ->getPath()); $t ->is( $size , array ( 100 , 100 ), 'The image has been resized' ); unlink( 'web/uploads/test.png' ); unset( $thumbnail ); Fixture Teardown 46. Testing Strategies – 4 Phase Test // @Test: resize() resizes the thumbnail // setup copy( 'data/fixtures/test.png' , 'web/uploads/test.png' ); // test $thumbnail = new CukeetThumbnail( 'web/uploads/test.png' ); $thumbnail ->resize( 100 , 100 ); // assertions $size = getimagesize( $thumbnail ->getPath()); $t ->is( $size , array ( 100 , 100 ), 'The image has been resized' ); // teardown unlink( 'web/uploads/test.png' ); unset( $thumbnail ); Use Comments! 50. Testing Strategies – The Test Fixture Global Fixture Fresh Fixture Fixture Setup test.png, new CukeetThumbnail() Test 1 Test 2 Test 3 Test 4 Fixture Setup test.png, new CukeetThumbnail() Test 1 Test 2 Fixture Setup test.png, new CukeetThumbnail() Fixture Setup 57. Testing Strategies – Annotations // @Before copy( 'data/fixtures/test.png' , 'web/uploads/test.png' ); $thumbnail = new CukeetThumbnail( 'web/uploads/test.png' ); // @After unlink( 'web/uploads/test.png' ); unset( $thumbnail ); // @Test: resize() resizes the thumbnail $thumbnail ->resize( 100 , 100 ); $size = getimagesize( $thumbnail ->getPath()); $t ->is( $size , array ( 100 , 100 ), 'The image has been resized' ); 61. Testing Strategies – xUnit Style Test Class class CukeetThumbnailTest extends LimeTestCase { private $thumbnail ; public function setup() { copy( 'data/fixtures/test.png' , 'web/uploads/test.png' ); $this ->thumbnail = new CukeetThumbnail( 'web/uploads/test.png' ); } public function teardown() { unlink( 'web/uploads/test.png' ); unset( $this ->thumbnail); } public function testTheThumbnailCanBeResized() {} } Same As With Annotations 63. Testing Strategies – How to Write Testable Code class CukeetRecipe { public function save() { $user = sfContext::getInstance()->getUser(); if (! $user ->hasCredential( 'admin' )) { throw new RuntimeException( 'No permission!' ); } } } 64. Testing Strategies – How to Write Testable Code class CukeetRecipe { public function save() { $user = sfContext::getInstance()->getUser(); if (! $user ->hasCredential( 'admin' )) { throw new RuntimeException( 'No permission!' ); } } } Uh oh... 71. Testing Strategies – Constructor Injection class CukeetRecipe { private $user ; public function __construct(sfBasicSecurityUser $user ) { $this ->user = $user ; } public function save() { if (! $this ->user->hasCredential( 'admin' )) { throw new RuntimeException( 'No permission!' ); } } 72. Testing Strategies – Dependency Injection // @Before $user = new sfBasicSecurityUser(); $recipe = new CukeetRecipe( $user ); // @Test: Users with credential 'admin' can save $user ->addCredential( 'admin' ); $recipe ->save( $user ); // @Test: Normal users cannot save $t ->expect( 'RuntimeException' ); $recipe ->save( $user ); 74. Testing Strategies – How to Write Testable Code? sloooowwww sfContext, Database, ... Test Tested Class Dependency 78. Testing Strategies – Slow Dependencies class CukeetRecipe { public function save(CukeetCategoryTable $categoryTable ) { if (is_null( $this ->Category)) { $this ->Category = $categoryTable ->findDefault(); } } } 79. Testing Strategies – Fake Objects // @Test: The default category is assigned, if empty // setup $defaultCategory = new CukeetCategory(); $categoryTable = new FakeCategoryTable( $defaultCategory ); // test $recipe = new CukeetRecipe(); $recipe ->Category = null ; $recipe ->save( $categoryTable ); // assertions $t ->is( $recipe ->Category, $defaultCategory , 'The default...' ); 80. Testing Strategies – Fake Objects class FakeCategoryTable extends CukeetCategoryTable { protected $defaultCategory ; public function __construct(CukeetCategory $defaultCategory ) { $this ->defaultCategory = $defaultCategory ; } public function findDefault() { return $this ->defaultCategory; } } 84. Testing Strategies – Stub Objects // @Test: The default category is assigned, if empty // setup $defaultCategory = new CukeetCategory(); $categoryTable = $t ->stub( 'CukeetCategoryTable' ); $categoryTable ->findDefault()->returns( $defaultCategory ); $categoryTable ->replay(); // test $recipe = new CukeetRecipe(); $recipe ->Category = null ; $recipe ->save( $categoryTable ); // assertions $t ->is( $recipe ->Category, $defaultCategory , 'The default...' ); Stub Generation in Lime 2 85. Testing Strategies – Stub Objects // @Test: The default category is assigned, if empty // setup $defaultCategory = new CukeetCategory(); $categoryTable = $t ->stub( 'CukeetCategoryTable' ); $categoryTable ->findDefault()->returns( $defaultCategory ); $categoryTable ->replay(); // test $recipe = new CukeetRecipe(); $recipe ->Category = null ; $recipe ->save( $categoryTable ); // assertions $t ->is( $recipe ->Category, $defaultCategory , 'The default...' ); Stub Configuration 93. Testing Strategies – Mock Objects // @Test: Upon destruction all data is saved to the session // setup $storage = $t ->mock( 'CukeetSessionStorage' ); $storage ->write( 'Foo' , 'Bar' ); $storage ->replay(); $user = new CukeetUser( $storage ); // test $user ->setAttribute( 'Foo' , 'Bar' ); $user ->__destruct(); // assertions $storage ->verify(); 97. Testing Strategies – Functional Tests $browser ->info( '1 - Recipes are displayed in each category' ) ->info( ' 1.1 - The two most recent recipes are displayed' ) ->click( 'Desserts' ) ->with( 'response' )->begin() ->checkElement( '#recipes li:first' , '/Deluxe Apples/' ) ->checkElement( '#recipes li:last' , '/Quatre Quarts/' ) ->checkElement( '#recipes li' , 2 ) ->end() ->click( 'Burgers' ) ->with( 'response' )->begin() ->checkElement( '#recipes li:first' , '/Kamikaze Burgers/' ) ->checkElement( '#recipes li:last' , '/Surprise Burgers/' ) ->checkElement( '#recipes li' , 2 ) ->end() 107. Testing Strategies – Functional vs. Unit Tests $browser ->info( '1 - Recipes are displayed in each category' ) ->info( ' 1.1 - The two most recent recipes are displayed' ) ->click( 'Desserts' ) ->with( 'response' )->begin() ->checkElement( '#recipes li:first' , '/Deluxe Apples/' ) ->checkElement( '#recipes li:last' , '/Quatre Quarts/' ) ->checkElement( '#recipes li' , 2 ) ->end() ->click( 'Burgers' ) ->with( 'response' )->begin() ->checkElement( '#recipes li:first' , '/Kamikaze Burgers/' ) ->checkElement( '#recipes li:last' , '/Surprise Burgers/' ) ->checkElement( '#recipes li' , 2 ) ->end() 108. Testing Strategies – Test Edge Cases in Unit Tests // CukeetRecipeTableTest.php require_once dirname(__FILE__). '/../bootstrap/doctrine.php' ; $t ->comment( 'findRecent() returns recent recipes of a category' ); // setup ... // test $actual = $table ->findRecent( 2 , $category ); // assertions ... $t ->is( $actual , $expected , 'The correct recipes were returned' ); 109. Testing Strategies – Test Edge Cases in Unit Tests // CukeetRecipeTableTest.php require_once dirname(__FILE__). '/../bootstrap/doctrine.php' ; $t ->comment( 'findRecent() returns recent recipes of a category' ); // setup ... // test $actual = $table ->findRecent( 2 , $category ); // assertions ... $t ->is( $actual , $expected , 'The correct recipes were returned' ); Bootstrap File 110. Testing Strategies – Test Edge Cases in Unit Tests // bootstrap/doctrine.php require_once dirname(__FILE__). '/unit.php' ; $database = new sfDoctrineDatabase( array ( 'name' => 'doctrine' , 'dsn' => 'sqlite::memory:' , )); // load missing model files and create tables Doctrine::createTablesFromModels(ROOT_DIR. '/lib/model' ); // load fixture data Doctrine::loadData( 'data/fixtures/fixtures.yml' ); Load YAML Fixtures 111. Testing Strategies – The Test Fixture Global Fixture Fixture Setup test.png, new CukeetThumbnail() Test 1 Test 2 Test 3 Test 4 112. Testing Strategies – Use Inline Fixtures // @Before reload(); $table = Doctrine::getTable( 'CukeetRecipe' ); // @Test: findRecent() returns recent recipes of a category // setup $category = new CukeetCategory(); $category ->fromArray( array ( 'name' => 'Desserts' , ...)); $recipe1 = new CukeetRecipe(); $recipe1 ->fromArray( array ( 'name' => 'Quatre Quarts' , ...)); $recipe1 ->Category = $category ; $recipe1 ->save(); ... 114. Testing Strategies – Creation and Helper Functions // @Test: findRecent() returns recent recipes of a category // setup $category = createCategory(); $recipe1 = createRecipeInCategory( $category ); $recipe2 = createRecipeInCategory( $category ); $recipe3 = createRecipe(); $recipe4 = createRecipeInCategory( $category ); save( $recipe1 , $recipe2 , $recipe3 , $recipe4 ); // test $actual = $table ->findRecent( 2 , $category ); // assertions $expected = createCollection( $recipe4 , $recipe2 ); $t ->is( $actual , $expected , 'The correct recipes were returned' ); 115. Testing Strategies – Creation and Helper Functions // bootstrap/doctrine.php function createRecipe( array $properties = array ()) { static $i = 0 ; $recipe = new CukeetRecipe(); $recipe ->fromArray(array_merge( array ( 'name' => 'Recipe ' . ++ $i , 'created_at' => date( 'Y-m-d H:m:i' , $i ), ), $properties )); return $recipe ; } 116. Testing Strategies – Functional Test Revisited (1) $browser ->info( '1 - Recipes are displayed in each category' ) ->info( ' 1.1 - The two most recent recipes are displayed' ) ->click( 'Desserts' ) ->with( 'response' )->begin() ->checkElement( '#recipes li:first' , '/Deluxe Apples/' ) ->checkElement( '#recipes li:last' , '/Quatre Quarts/' ) ->checkElement( '#recipes li' , 2 ) ->end() ->click( 'Burgers' ) ->with( 'response' )->begin() ->checkElement( '#recipes li:first' , '/Kamikaze Burgers/' ) ->checkElement( '#recipes li:last' , '/Surprise Burgers/' ) ->checkElement( '#recipes li' , 2 ) ->end( ) 117. Testing Strategies – Functional Test Revisited (2) $browser ->info( '1 - Recipes are displayed in each category' ) ->info( ' 1.1 - The two most recent recipes are displayed' ) ->click( 'Desserts' ) ->with( 'response' )->begin() ->checkElement( '#recipes li:first' , '/Deluxe Apples/' ) ->checkElement( '#recipes li:last' , '/Quatre Quarts/' ) ->checkElement( '#recipes li' , 2 ) ->end() ->click( 'Burgers' ) ->with( 'response' )->begin() ->checkElement( '#recipes li:first' , '/Kamikaze Burgers/' ) ->checkElement( '#recipes li:last' , '/Surprise Burgers/' ) ->checkElement( '#recipes li' , 2 ) ->end() 129. What Nu in Lime 2 – Annotation Support // @Before copy( 'data/fixtures/test.png' , 'web/uploads/test.png' ); $thumbnail = new CukeetThumbnail( 'web/uploads/test.png' ); // @After unlink( 'web/uploads/test.png' ); unset( $thumbnail ); // @Test: resize() resizes the thumbnail $thumbnail ->resize( 100 , 100 ); $size = getimagesize( $thumbnail ->getPath()); $t ->is( $size , array ( 100 , 100 ), 'The image has been resized' ); 131. Whatz Nu in Lime 2 – Mock Objects // @Test: Upon destruction all data is saved to the session // setup $storage = $t ->mock( 'SessionStorage' ); $storage ->write( 'Foo' , 'Bar' )->returns( true )->once(); $storage ->flush()->atLeastOnce(); $storage ->any( 'read' )->throws( 'BadMethodCallException' ); $storage ->replay(); // test ... 133. What Nu in Lime 2 – Improved Error Messages # got: object(CukeetRecipe) ( # ... # '_data' => array ( # ... # 'name' => 'Quatre Quarts', # ... # ), # ) # expected: object(CukeetRecipe) ( # ... # '_data' => array ( # ... # 'name' => 'Kamikaze Burger', # ... # ), # )