Download - Testing persistence in PHP with DbUnit
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
• Design for testability (SOLID)
• Use the front door first
• One condition per test
http://xunitpatterns.com/Principles of Test Automation.htmlhttp://en.wikipedia.org/wiki/SOLID_(object-oriented_design)
PHPUnit
class TestClass extends PHPUnit_Framework_TestCase{ public function setUp() {} public function testMethod() {} public function tearDown() {}}
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( " INSERT INTO table SET field = 'value' ");
$dao = new Dao($connection);$dao->insert('value');
Tells nothing about the database interaction
Testing DAOs - injected connection
$connection = createLocalTestConnection();
$dao = new Dao($connection);$dao->insert('value');
assertInsertedValueMatches('value');
Hard to replace in end to end tests
Dependency lookup
Testing DAOs - dependency lookup
$dao = new Dao();$dao->insert('value');
assertInsertedValueMatches('value');
http://xunitpatterns.com/Dependency Lookup.html
DbUnit + Etsy extensions
Test case initclass RemoverDaoTest extends DatabaseTestCaseBase{ /** * @return PHPUnit_Extensions_MultipleDatabase_Database[] */ protected function getDatabaseConfigs() { return array( $this->getDbConfigBuilder() ->connection($this->getCentralDbConnection()) ->dataSet($this->createXmlDataSet('init.xml')) ->build() ); }
Datasets
user: - id: 1 name: Ingrid
<dataset> <table name="user"> <column>id</column> <column>name</column> <row> <value>1</value> <value>Ingrid</value> </row> </table></dataset>
MySQL XML YAML
Assertions
public function testRemoveNoProblem(){ $remover = new RemoverDao(); $remover->removeUser(1);
$this->assertDataSetsEqual( $this->createYamlDataSet('empty.yml'), $this->getCentralDbConnection() ->createDataSet(array('user')) );}
Datasets
user: - id: 1 name: Ingrid created_at: 2012-01-16
<dataset> <table name="user"> <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
Dataset filter
$datasetFilter = new PHPUnit_Extensions_Database_DataSet_DataSetFilter( $dataset);
$datasetFilter->addIncludeTables( array('user'));$datasetFilter->setExcludeColumnsForTable( 'user', array('created_at'));
Datasets
user: - id: 1 name: Ingrid created_at: ##TIME##
<dataset> <table name="user"> <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
Replacement dataset
$replacementDataset = new PHPUnit_Extensions_Database_DataSet_ReplacementDataSet( $dataset, array('##TIME##' => TimeService::time()));
Implicit setup
Common initial state, extended test-by-test
Hard to share
Inline setup
• Define only the tables being used in common setup
• Insert all the necessary data inline in the test method
Still messy to share common datasets with xml or yml
Minimal fixture!
Inline datasets with PHP
Value objects + builders
Convert to dataset for: • inserting into database• using in assertions
Setup
$user = UserBuilder::create()->build();$video = VideoBuilder::create() ->owner($user) ->private() ->build();
TestDataInserter::create(getCentralDbConnection()) ->add('user', $user) ->add('video', $video);
Excercise
VideoPublisherDao::create()->publish($video->id);
Http service
DAO
HttpClient::create()->call('Video::publish', $video->id);
Verify
assertDataSetsEqual( $this->createDataSet('video', $video), $this->getCentralDbConnection() ->createDataSet(array('video')));
Layer crossing tests - where we use it
• Http services
• Daemons
• Cron jobs
• Ajax calls
The power of layer crossing tests
Refactoring (legacy) code
Increasing code coverage
There are downsides too
We use the back door => risk of overspecification
Empty initial state • hides concurrency problems• parallel testing more difficult
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 testshttp://engineering.imvu.com/2011/01/19/buildbot-and-intermittent-tests/
[email protected]/pepov