Testing persistence (in PHP) PHPUnit & DbUnit
Our story by unit testing classes Started from inside to the outside
Tools and principles Using PHPUnit with Mock objects  to isolate the SUT <ul><ul><li>Design for testability (SOLID) </li><...
PHPUnit class   TestClass        extends  PHPUnit_Framework_TestCase {      public   function   setUp () {}      public   ...
Problems No integration guarantees Not effective for refactoring legacy code Does not ensure external quality
Need for integration tests To ensure external quality from top to bottom
Unit testing DAOs with DI and mocks $connection   =  createMockExpecting(      &quot; INSERT INTO table SET field = 'value...
Testing DAOs - injected connection $connection   =  createLocalTestConnection(); $dao   =   new  Dao( $connection ); $dao ...
Dependency lookup
Testing DAOs - dependency lookup $dao   =   new  Dao(); $dao -> insert ( 'value' ); assertInsertedValueMatches( 'value' );...
DbUnit + Etsy extensions
Test case init class   RemoverDaoTest   extends  DatabaseTestCaseBase { /**   * @return PHPUnit_Extensions_MultipleDatabas...
Datasets user:  -    id: 1    name: Ingrid <dataset>     <table   name= &quot;user&quot; >         <column> id </column>  ...
Assertions public   function   testRemoveNoProblem () {     $remover   =   new  RemoverDao();     $remover -> removeUser (...
Datasets user:  -    id: 1    name: Ingrid    created_at: 2012-01-16 <dataset>     <table   name= &quot;user&quot; >      ...
Dataset filter $datasetFilter  =   new  PHPUnit_Extensions_Database_DataSet_DataSetFilter(   $dataset ); $datasetFilter ->...
Datasets user:  -    id: 1    name: Ingrid    created_at:  ##TIME## <dataset>     <table   name= &quot;user&quot; >       ...
Replacement dataset $replacementDataset   =   new  PHPUnit_Extensions_Database_DataSet_ReplacementDataSet (    $dataset , ...
Implicit setup Common initial state, extended test-by-test Hard to share
Inline setup <ul><ul><li>Define only the tables being used in common setup </li></ul></ul><ul><ul><li>Insert all the neces...
Inline datasets with PHP Value objects + builders Convert to dataset for: <ul><ul><li>inserting into database </li></ul></...
Setup $user    =  UserBuilder :: create () -> build (); $video   =  VideoBuilder :: create ()    -> owner ( $user )    -> ...
Excercise VideoPublisherDao :: create () -> publish ( $video -> id ); Http service DAO HttpClient :: create () -> call ( '...
Verify <ul><li>assertDataSetsEqual(    $this -> createDataSet ( 'video' ,  $video ),    $this -> getCentralDbConnection ()...
Layer crossing tests - where we use it <ul><ul><li>Http services </li></ul></ul><ul><ul><li>Daemons </li></ul></ul><ul><ul...
The power of layer crossing tests Refactoring (legacy) code Increasing code coverage
There are downsides too We use the back door => risk of  overspecification <ul><li>Empty initial state  </li></ul><ul><ul>...
Concurrent end-to-end black box tests Uses only the front door (public api) Isolation with unique identifiers &  Δ asserti...
[email_address] twitter.com/pepov
Upcoming SlideShare
Loading in …5
×

Testing persistence in PHP with DbUnit

5,337 views

Published on

Published in: Technology
0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
5,337
On SlideShare
0
From Embeds
0
Number of Embeds
9
Actions
Shares
0
Downloads
24
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

Testing persistence in PHP with DbUnit

  1. 1. Testing persistence (in PHP) PHPUnit & DbUnit
  2. 2. Our story by unit testing classes Started from inside to the outside
  3. 3. Tools and principles Using PHPUnit with Mock objects  to isolate the SUT <ul><ul><li>Design for testability (SOLID) </li></ul></ul><ul><ul><li>Use the front door first </li></ul></ul><ul><ul><li>One condition per test </li></ul></ul>http://xunitpatterns.com/Principles of Test Automation.html http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)
  4. 4. PHPUnit class TestClass        extends PHPUnit_Framework_TestCase {     public function setUp () {}     public function testMethod () {}     public function tearDown () {} }
  5. 5. Problems No integration guarantees Not effective for refactoring legacy code Does not ensure external quality
  6. 6. Need for integration tests To ensure external quality from top to bottom
  7. 7. Unit testing DAOs with DI and mocks $connection = createMockExpecting(      &quot; INSERT INTO table SET field = 'value' &quot; ); $dao = new Dao( $connection ); $dao -> insert ( 'value' ); Tells nothing about the database interaction
  8. 8. Testing DAOs - injected connection $connection = createLocalTestConnection(); $dao = new Dao( $connection ); $dao -> insert ( 'value' ); assertInsertedValueMatches( 'value' ); Hard to replace in end to end tests
  9. 9. Dependency lookup
  10. 10. Testing DAOs - dependency lookup $dao = new Dao(); $dao -> insert ( 'value' ); assertInsertedValueMatches( 'value' ); http://xunitpatterns.com/Dependency Lookup.html
  11. 11. DbUnit + Etsy extensions
  12. 12. Test case init class RemoverDaoTest extends DatabaseTestCaseBase { /** * @return PHPUnit_Extensions_MultipleDatabase_Database[] */ protected function getDatabaseConfigs () { return array ( $this -> getDbConfigBuilder () -> connection ( $this -> getCentralDbConnection ()) -> dataSet ( $this -> createXmlDataSet ( 'init.xml' )) -> build () ); }
  13. 13. Datasets user:  -    id: 1    name: Ingrid <dataset>     <table name= &quot;user&quot; >         <column> id </column>         <column> name </column>         <row>             <value> 1 </value>             <value> Ingrid </value>         </row>     </table> </dataset> MySQL XML YAML
  14. 14. Assertions public function testRemoveNoProblem () {     $remover = new RemoverDao();     $remover -> removeUser ( 1 );     $this -> assertDataSetsEqual ( $this -> createYamlDataSet ( 'empty.yml' ), $this -> getCentralDbConnection () -> createDataSet ( array ( 'user' ))    ); }
  15. 15. Datasets user:  -    id: 1    name: Ingrid    created_at: 2012-01-16 <dataset>     <table name= &quot;user&quot; >         <column> id </column>         <column> name </column>         <column> created_at </column>         <row>             <value> 1 </value>             <value> Ingrid </value>             <value> 2012-01-16 </value>         </row>     </table> </dataset> MySQL XML YAML
  16. 16. Dataset filter $datasetFilter = new PHPUnit_Extensions_Database_DataSet_DataSetFilter( $dataset ); $datasetFilter -> addIncludeTables ( array ( 'user' ) ); $datasetFilter -> setExcludeColumnsForTable ( 'user' , array ( 'created_at' ) );
  17. 17. Datasets user:  -    id: 1    name: Ingrid    created_at: ##TIME## <dataset>     <table name= &quot;user&quot; >         <column> id </column>         <column> name </column>         <column> created_at </column>         <row>             <value> 1 </value>             <value> Ingrid </value>             <value> ##TIME## </value>         </row>     </table> </dataset> MySQL XML YAML
  18. 18. Replacement dataset $replacementDataset = new PHPUnit_Extensions_Database_DataSet_ReplacementDataSet (   $dataset , array ( '##TIME##' => TimeService::time ()) );
  19. 19. Implicit setup Common initial state, extended test-by-test Hard to share
  20. 20. Inline setup <ul><ul><li>Define only the tables being used in common setup </li></ul></ul><ul><ul><li>Insert all the necessary data inline in the test method </li></ul></ul>Still messy to share common datasets with xml or yml Minimal fixture! 
  21. 21. Inline datasets with PHP Value objects + builders Convert to dataset for: <ul><ul><li>inserting into database </li></ul></ul><ul><ul><li>using in assertions </li></ul></ul>
  22. 22. Setup $user   = UserBuilder :: create () -> build (); $video = VideoBuilder :: create ()   -> owner ( $user )   -> private ()   -> build (); TestDataInserter :: create (getCentralDbConnection())   -> add ( 'user' , $user )   -> add ( 'video' , $video );
  23. 23. Excercise VideoPublisherDao :: create () -> publish ( $video -> id ); Http service DAO HttpClient :: create () -> call ( 'Video::publish' , $video -> id );
  24. 24. Verify <ul><li>assertDataSetsEqual(   $this -> createDataSet ( 'video' , $video ),   $this -> getCentralDbConnection () </li></ul><ul><li>-> createDataSet ( array ( 'video' )) ); </li></ul>
  25. 25. Layer crossing tests - where we use it <ul><ul><li>Http services </li></ul></ul><ul><ul><li>Daemons </li></ul></ul><ul><ul><li>Cron jobs </li></ul></ul><ul><ul><li>Ajax calls </li></ul></ul>
  26. 26. The power of layer crossing tests Refactoring (legacy) code Increasing code coverage
  27. 27. There are downsides too We use the back door => risk of  overspecification <ul><li>Empty initial state  </li></ul><ul><ul><li>hides concurrency problems </li></ul></ul><ul><ul><li>parallel testing more difficult </li></ul></ul>
  28. 28. Concurrent end-to-end black box tests Uses only the front door (public api) Isolation with unique identifiers &  Δ assertions Tests real concurrency Harder to track bugs & intermittent tests http://engineering.imvu.com/2011/01/19/buildbot-and-intermittent-tests/
  29. 29. [email_address] twitter.com/pepov

×