Finding the Right Testing Tool for the Job

48
Finding the Right Testing Tool for the Job Sebastian Bergmann and Ciaran McNulty

Transcript of Finding the Right Testing Tool for the Job

Page 1: Finding the Right Testing Tool for the Job

Finding the Right Testing Tool for the Job

Sebastian Bergmann and Ciaran McNulty

Page 2: Finding the Right Testing Tool for the Job

Who are these guys?

Page 3: Finding the Right Testing Tool for the Job

3 Dimensions of Testing

Goal - why we are writing the testScope - how much of the system is involved in the testForm - how we express the test

Page 4: Finding the Right Testing Tool for the Job
Page 5: Finding the Right Testing Tool for the Job

3 4 Dimensions of Testing

Goal - why we are writing the testScope - how much of the system is involved in the testForm - how we express the testTime - when we write the test

Page 6: Finding the Right Testing Tool for the Job

What we will talk about

— Characterisation Tests

— Acceptance Tests

— Integration Tests

— Unit Tests

Page 7: Finding the Right Testing Tool for the Job

Characterisation Tests

Page 8: Finding the Right Testing Tool for the Job

Characterisation TestsGoals:

— Capture existing behaviour

— Find out when behaviour changes

Page 9: Finding the Right Testing Tool for the Job

Characterisation TestsScopes:

— Often at UI level

— Sometimes at object/service level

Page 10: Finding the Right Testing Tool for the Job

Characterisation TestsTimes:

— Always after implementation

Page 11: Finding the Right Testing Tool for the Job

Characterisation TestsBest practice:

— Treat these tests as a temporary measure

— Use a tool that makes it easy to create tests

— Expect pain and suffering

Page 12: Finding the Right Testing Tool for the Job

Characterisation TestsForm: Behat + MinkExtension builtin steps

Scenario: Product search returns results Given I am on "/" And I fill in "search" with "Blue jeans" When I press "go" Then I should see "100 Results Found"

Page 13: Finding the Right Testing Tool for the Job

Characterisation TestsForm: PHPUnit + phpunit-mink-trait

class SearchTest extends PHPUnit_Framework_TestCase{ use phpunit\mink\TestCaseTrait;

public function testProductSearchReturnsResult() { $page = $this->visit('http://example.com/');

$page->fillField('search', 'Blue Jeans'); $page->pressButton('go');

$this->assertContains('100 Results Found', $page->getText()); }}

Page 14: Finding the Right Testing Tool for the Job

Characterisation TestsForm: Selenium IDE

Page 15: Finding the Right Testing Tool for the Job

Characterisation TestsForm: Ghost Inspector

Page 16: Finding the Right Testing Tool for the Job

Characterisation TestsForm: PHPUnit + de-legacy-fy

See docs at on GitHub at sebastianbergmann/de-legacy-fy

Page 17: Finding the Right Testing Tool for the Job

Acceptance Tests

Page 18: Finding the Right Testing Tool for the Job

Acceptance TestsGoals:

— Match system behaviour to business requirements

— Get feedback on proposed implementations

— Understand business better

— Document behaviour for the future

Page 19: Finding the Right Testing Tool for the Job

Acceptance TestsScopes:

— At a UI layer

— At an API layer

— At the service layer

— Lower level may be too disconnected

Page 20: Finding the Right Testing Tool for the Job

Acceptance TestsTimes:

— Before implementation

— Before commitment (as long as it's not expensive?)

— Hard to write in retrospect

Page 21: Finding the Right Testing Tool for the Job

Acceptance TestsBest practices:

— Get feedback on tests early

— Make them readable, or have readable output, for the intended audience

— Apply for the smallest scope first

— Apply to core domain model first

— Minimise end-to-end tests

Page 22: Finding the Right Testing Tool for the Job

Acceptance TestsForm: Behat at service level

Scenario: Sales tax is applied to basket Given "Blue Jeans" are priced as €100 in the catalogue When I add "Blue Jeans" to my shopping basket Then the basket total should be €120

Page 23: Finding the Right Testing Tool for the Job

class BasketContext implements Context{ public function __construct() { $this->catalogue = new InMemory\Catalogue(); $this->basket = new Basket($catalogue); }

/** * @Given :productName is/are priced as :cost in the catalogue */ public function priceProduct(ProductName $productName, Cost $cost) { $this->catalogue->price($productName, $cost); } //...}

Page 24: Finding the Right Testing Tool for the Job

class BasketContext implements Context{ //... /** * @When I add :productName to my shopping basket */ public function addProductToBasket(ProductName $productName) { $this->basket->add($productName); }

/** * @Then the basket total should be :cost */ public function checkBasketTotal(Cost $cost) { assert($this->basket->total == $cost->asInt()); }}

Page 25: Finding the Right Testing Tool for the Job

Acceptance TestsForm: PHPUnit at service level

class BasketTest extends PHPUnit_Framework_TestCase{ public function testSalesTaxIsApplied() { $catalogue = new InMemory\Catalogue(); $basket = new Basket($catalogue);

$productName = new ProductName('Blue Jeans'); $catalogue->price($productName, new Cost('100')); $basket->add($productName);

$this->assertEquals(new Cost('120'), $basket->calculateTotal()); }}

Page 26: Finding the Right Testing Tool for the Job

Acceptance TestsForm: PHPUnit at service level

Page 27: Finding the Right Testing Tool for the Job

Acceptance TestsForm: Behat at UI level

Scenario: Sales tax is applied to basket Given "Blue Jeans" are priced as €100 in the catalogue When I add "Blue Jeans" to my shopping basket Then the basket total should be €120

Page 28: Finding the Right Testing Tool for the Job

class BasketUiContext extends MinkContext{ public function __construct() { $this->catalogue = new Catalogue(/* ... */); }

/** * @Given :productName is/are priced as :cost in the catalogue */ public function priceProduct(ProductName $productName, Cost $cost) { $this->catalogue->price($productName, $cost); } //...}

Page 29: Finding the Right Testing Tool for the Job

class BasketUiContext extends MinkContext{ //... /** * @When I add :productName to my shopping basket */ public function addProductToBasket(ProductName $productName) { $this->visitPath('/products/'.urlencode($productName)); $this->getSession()->getPage()->pressButton('Add to Basket'); }

/** * @Then the basket total should be :cost */ public function checkBasketTotal(Cost $cost) { $this->assertElementContains('#basket .total', '€120'); }}

Page 30: Finding the Right Testing Tool for the Job

Acceptance TestsForm: PHPUnit at UI level

class BasketUiTest extends PHPUnit_Framework_TestCase{ use phpunit\mink\TestCaseTrait;

public function testSalesTaxIsApplied() { $catalogue = new Catalogue(/* ... */); $catalogue->price(new ProductName('Blue Jeans'), new Cost(120));

$page = $this->visit('http://example.com/products/'.urlencode($productName)); $this->getSession()->getPage()->pressButton('Add to Basket');

$this->assertContains( '€120', $page->find('css', '#basket .total')->getText() ); }}

Page 31: Finding the Right Testing Tool for the Job

Integration Tests

Page 32: Finding the Right Testing Tool for the Job

Integration TestsGoals:

— Test cross-boundary communication

— Test integration with concrete infrastructure

Page 33: Finding the Right Testing Tool for the Job

Integration TestsScopes:

— Large parts of the system

— Focus on the edges (not core domain)

— Areas where your code interacts with third-party code

Page 34: Finding the Right Testing Tool for the Job

Integration TestsTimes:

— After the feature is implemented in core / contracts are established

— During integration with real infrastructure

— When you want to get more confidence in integration

— When cases are not covered by End-to-End acceptance test

Page 35: Finding the Right Testing Tool for the Job

Integration TestsBest Practices:

— Use tools with existing convenient integrations

— Focus on testing through your API

— Make sure your core domain has an interface

Page 36: Finding the Right Testing Tool for the Job

Integration TestsForm: PHPUnit + DbUnit

class CatalogueTest extends PHPUnit_Extensions_Database_TestCase{ public function getConnection() { $pdo = new PDO(/* ... */); return $this->createDefaultDBConnection($pdo, 'myDatabase'); }

public function getDataSet() { return $this->createFlatXMLDataSet(dirname(__FILE__) . '/_files/catalogue-seed.xml'); } // ...}

Page 37: Finding the Right Testing Tool for the Job

class CatalogueTest extends PHPUnit_Extensions_Database_TestCase{ // ...

public function testProductCanBePriced() { $catalogue = new Catalogue(/* ... */);

$catalogue->price( new ProductName('Blue Jeans'), new Cost('100') );

$this->assertEquals( new Cost('100'), $catalogue->lookUp(new ProductName('Blue Jeans') ); }}

Page 38: Finding the Right Testing Tool for the Job

Unit Tests

Page 39: Finding the Right Testing Tool for the Job

Unit TestsGoals:

— Test components individually

— Catch errors earlier

— Drive internal design quality

— Document units for other developers

Page 40: Finding the Right Testing Tool for the Job

Unit Tests:Scopes:

— Single classes

— Single classes + value objects?

— Extremely small units of code

Page 41: Finding the Right Testing Tool for the Job

Unit Tests:Times:

— Just before you implement

Page 42: Finding the Right Testing Tool for the Job

Unit Tests:Times:

— Just before you implement

OK, maybe...

— Just after you implement

— If you want to learn more about a class

— But always before you share the code!

Page 43: Finding the Right Testing Tool for the Job

Unit TestsBest Practices

— Write in a descriptive style

— Describe interactions using Test Doubles

— Don't get too hung up on isolation

— Don't touch infrastructure

— Don't test other people's code

— Don't double other people's code?

Page 44: Finding the Right Testing Tool for the Job

Unit TestsForm: PHPUnit

class BasketTest extends PHPUnit_Framework_TestCase{ public function testSalesTaxIsApplied() { $catalogue = $this->getMock(Catalogue::class); $catalogue->method('lookUp')->with(new ProductName('Blue Jeans')) ->willReturn(new Cost('100'));

$basket = new Basket($catalogue); $basket->add(new ProductName('Blue Jeans'));

$this->assertSame(new Cost('120'), $basket->calculateTotal()); }

}

Page 45: Finding the Right Testing Tool for the Job

Unit TestsForm: PhpSpec

class BasketSpec extends ObjectBehavior{ function it_applies_sales_tax(Catalogue $catalogue) { $catalogue->lookUp(new ProductName('Blue Jeans'))->willReturn(new Cost('100')); $this->beConstructedWith($catalogue);

$this->add(new ProductName('Blue Jeans'));

$basket->calculateTotal()->shouldBeLike(new Cost('120')); }

}

Page 46: Finding the Right Testing Tool for the Job

5th Dimension - Who?

— Choose the right approaches for your context

— What mix of languages can the team use?

— What styles of testing will add the most value?

— What formats make the most sense to the team?

— How will tests fit into the development process?

There is no right answer, there are many right answers!

Page 47: Finding the Right Testing Tool for the Job

Photo Credits

— "tools" by velacreations (CC) - https://flic.kr/p/8ZSb3r

— "Components" by Jeff Keyzer (CC) - https://flic.kr/p/4ZNZp1

— Doctor Who stolen from BBC.co.uk

— Other images used under license

Page 48: Finding the Right Testing Tool for the Job

Thank You & Questions?

@s_bergmann@ciaranmcnulty

https://joind.in/talk/80dbd