Getting to Grips with SilverStripe Testing Mark Rickerby May, 2008
Continuous Integration Project code stored in a central SVN repository Repository synchronized with a build server Code builds on the server on every commit All project tests are run at every build Immediate notification of integration failures Project code is always live, always tested Go  buildbot !
Sapphire Tools DevelopmentAdmin  and  TestRunner  linked into all projects by default Execute via command line or web browser CLI using  sake  (Sapphire Make) /path/to/project$> sake test Browse and run all the tests in the build path http://project/dev/ http://project/dev/tests
Creating New Tests Each  SilverStripe  module has a test folder /htdocs/project/tests /htdocs/module/tests /htdocs/sapphire/tests Test cases use standard PHP files /sapphire/tests/DataObjectTest.php DataObject  fixtures use YAML files /sapphire/tests/DataObjectTest.yml
Running Tests Run a single test http://project/dev/tests/only/MyTest Run all module tests http://project/dev/tests/module/sapphire Run all tests in the project path http://project/dev/tests/all Green if Pass  or  Red if Fail
Writing Unit Tests Extend  SapphireTest  for unit tests class MyTest extends SapphireTest {} Attach a  DataObject  fixture for the test class MyTest extends SapphireTest { static $fixture_file = 'MyTest.yml' }
Writing Unit Tests All methods prefixed with  test  are run as tests class MyTest extends SapphireTest  function testGetAndSet() { $obj = new MyObj; $obj->set('key', 'val'); $this->assertEquals( “ val”, $obj->get('key') ); }}
Assertions Assert methods generate a pass/fail statement assertTrue($value) assertFalse($value) assertEquals($expected, $actual) assertContains($needle, $haystack) assertNotContains($needle, $haystack) Type checking and object identity assertType($expected, $actual) assertSame($expected, $actual)
Testing Objects Testing a single subsystem (unit test) $params = array('Amount' => 9.95, ... ); $service = new PaymentProcessor(); $service->connect(); $rsp = $service->processPayment($params); $this->assertTrue($rsp['success']); $this->assertEquals(9.95, $rsp['amount']);
Testing Boundaries Testing an integration boundar y (smoke test) $params = array('Amount' => 9.95, ... ); $payment = Payment::processPayment($params); $this->assertTrue($payment->isSuccess()); $this->assertEquals(9.95, $payment->Amount); Here,  PaymentProcess  is wrapped by  Payment “ Where there's smoke, there's fire”
Functional Tests Use  FunctionalTest  to browse web controllers class PageTest extends FunctionalTest {} Execute a fake web browser in memory $this->get('pages/name-of-page'); $this->assertExactMatchBySelector( '#page h1', 'Name of Page' );
Functional Tests Navigate a website via URL requests get($path) post($path, $data) submitForm($formID, $data, $button)  Match HTML content against CSS selectors assertExactMatchBySelector($css, $txt) assertExactHTMLMatchBySelector($css, $htm) assertPartialMatchBySelector($css, $txt) assertPartialHTMLMatchBySelector($css, $htm)
When? Test Driven Development –  test first, then code Behavior Driven Development  –  write to specification of what the API should do Characterisation Testing –  write tests to discover the behavior of a system or bring functionality under control
Where? What? Individual object behavior –  validate pre- and post-conditions - discover API as the code evolves System boundaries and flex points –  confirmation that subsystems work together Web application control flow –  validate HTTP and HTML responses –  form submission and session interactions
Rules of Thumb Be aware of breaking existing behavior –  run full suite of tests at every commit –  update all test code when the API changes Not everything is permanent –  if a test is no longer relevant, delete it from the repository
Questions? http://doc.silverstripe.com  - see  Testing Guide http://www.phpunit.de http://simpletest.org http://maetl.coretxt.net.nz/testing Happy coding!

Getting to Grips with SilverStripe Testing

  • 1.
    Getting to Gripswith SilverStripe Testing Mark Rickerby May, 2008
  • 2.
    Continuous Integration Projectcode stored in a central SVN repository Repository synchronized with a build server Code builds on the server on every commit All project tests are run at every build Immediate notification of integration failures Project code is always live, always tested Go buildbot !
  • 3.
    Sapphire Tools DevelopmentAdmin and TestRunner linked into all projects by default Execute via command line or web browser CLI using sake (Sapphire Make) /path/to/project$> sake test Browse and run all the tests in the build path http://project/dev/ http://project/dev/tests
  • 4.
    Creating New TestsEach SilverStripe module has a test folder /htdocs/project/tests /htdocs/module/tests /htdocs/sapphire/tests Test cases use standard PHP files /sapphire/tests/DataObjectTest.php DataObject fixtures use YAML files /sapphire/tests/DataObjectTest.yml
  • 5.
    Running Tests Runa single test http://project/dev/tests/only/MyTest Run all module tests http://project/dev/tests/module/sapphire Run all tests in the project path http://project/dev/tests/all Green if Pass or Red if Fail
  • 6.
    Writing Unit TestsExtend SapphireTest for unit tests class MyTest extends SapphireTest {} Attach a DataObject fixture for the test class MyTest extends SapphireTest { static $fixture_file = 'MyTest.yml' }
  • 7.
    Writing Unit TestsAll methods prefixed with test are run as tests class MyTest extends SapphireTest function testGetAndSet() { $obj = new MyObj; $obj->set('key', 'val'); $this->assertEquals( “ val”, $obj->get('key') ); }}
  • 8.
    Assertions Assert methodsgenerate a pass/fail statement assertTrue($value) assertFalse($value) assertEquals($expected, $actual) assertContains($needle, $haystack) assertNotContains($needle, $haystack) Type checking and object identity assertType($expected, $actual) assertSame($expected, $actual)
  • 9.
    Testing Objects Testinga single subsystem (unit test) $params = array('Amount' => 9.95, ... ); $service = new PaymentProcessor(); $service->connect(); $rsp = $service->processPayment($params); $this->assertTrue($rsp['success']); $this->assertEquals(9.95, $rsp['amount']);
  • 10.
    Testing Boundaries Testingan integration boundar y (smoke test) $params = array('Amount' => 9.95, ... ); $payment = Payment::processPayment($params); $this->assertTrue($payment->isSuccess()); $this->assertEquals(9.95, $payment->Amount); Here, PaymentProcess is wrapped by Payment “ Where there's smoke, there's fire”
  • 11.
    Functional Tests Use FunctionalTest to browse web controllers class PageTest extends FunctionalTest {} Execute a fake web browser in memory $this->get('pages/name-of-page'); $this->assertExactMatchBySelector( '#page h1', 'Name of Page' );
  • 12.
    Functional Tests Navigatea website via URL requests get($path) post($path, $data) submitForm($formID, $data, $button) Match HTML content against CSS selectors assertExactMatchBySelector($css, $txt) assertExactHTMLMatchBySelector($css, $htm) assertPartialMatchBySelector($css, $txt) assertPartialHTMLMatchBySelector($css, $htm)
  • 13.
    When? Test DrivenDevelopment – test first, then code Behavior Driven Development – write to specification of what the API should do Characterisation Testing – write tests to discover the behavior of a system or bring functionality under control
  • 14.
    Where? What? Individualobject behavior – validate pre- and post-conditions - discover API as the code evolves System boundaries and flex points – confirmation that subsystems work together Web application control flow – validate HTTP and HTML responses – form submission and session interactions
  • 15.
    Rules of ThumbBe aware of breaking existing behavior – run full suite of tests at every commit – update all test code when the API changes Not everything is permanent – if a test is no longer relevant, delete it from the repository
  • 16.
    Questions? http://doc.silverstripe.com - see Testing Guide http://www.phpunit.de http://simpletest.org http://maetl.coretxt.net.nz/testing Happy coding!