Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TDD

turanct
February 23, 2016

 TDD

An introduction to the situation of TDD at our company

turanct

February 23, 2016
Tweet

More Decks by turanct

Other Decks in Programming

Transcript

  1. unit tests • test one unit in isolation (SUT) •

    smallest unit in OOP is the class • use test doubles (mocks, stubs, spies, …) to replace dependencies note: • interfaces can’t be unit tested • abstract classes are difficult to test
  2. integration tests • let multiple classes talk to each other

    in a controlled environment • test side effects in filesystem, database, http calls, … note: These tests will be much slower than unit tests!
  3. advantages • confidence that you can change things without breaking

    something • test suite is a living behavior specification of the code • test suite is a living example of the usage of the code • the red-green-refactor flow tends to take you into the zone
  4. example shopping cart: • add products • remove products •

    checkout: returns price • after checking out, nothing can be added/ removed
  5. class ShoppingCartTest extends PHPUnit_Framework_TestCase { public function test_it_has_a_price_of_zero_without_products() { $cart

    = new ShoppingCart(); $price = $cart->checkout(); $this->assertEquals(0, $price); } }
  6. red

  7. red

  8. class ShoppingCartTest extends PHPUnit_Framework_TestCase { // ... public function test_its_price_equals_the_price_of_a_contained_product()

    { $cart = new ShoppingCart(); $bread = new Product('Bread', 2.20); $cart->add($bread); $price = $cart->checkout(); $this->assertEquals(2.20, $price); } }
  9. red

  10. final class Product { private $name; private $price; public function

    __construct($name, $price) { $this->name = (string) $name; $this->price = (float) $price; } }
  11. red

  12. red

  13. final class ShoppingCart { private $total = 0; public function

    checkout() { return $this->total; } public function add(Product $product) { $this->total = $product->getPrice(); } }
  14. red

  15. final class Product { private $name; private $price; public function

    __construct($name, $price) { $this->name = (string) $name; $this->price = (float) $price; } public function getPrice() { return $this->price; } }
  16. public function test_its_price_equals_the_sum_of_prices_of_all_contained_products() { $cart = new ShoppingCart(); $bread =

    new Product('Bread', 2.20); $cart->add($bread); $cheese = new Product('Cheese', 4.50); $cart->add($cheese); $butter = new Product('Butter', 1.00); $cart->add($butter); $price = $cart->checkout(); $this->assertEquals(7.70, $price); }
  17. red

  18. final class ShoppingCart { private $total = 0; public function

    checkout() { return $this->total; } public function add(Product $product) { $this->total += $product->getPrice(); } }
  19. tests can be cleaned up: sum of all contained products

    test also does the sum of one contained product
  20. public function test_it_does_not_count_removed_products_in_the_total_price() { $cart = new ShoppingCart(); $bread =

    new Product('Bread', 2.20); $cart->add($bread); $butter = new Product('Butter', 1.00); $cart->add($butter); $cart->remove($bread); $price = $cart->checkout(); $this->assertEquals(1, $price); }
  21. red

  22. /** * @expectedException CanNotRemoveProduct */ public function test_it_throws_when_trying_to_remove_product_that_it_does_not_have() { $cart

    = new ShoppingCart(); $bread = new Product('Bread', 2.20); $cart->add($bread); $butter = new Product('Butter', 1.00); $cart->remove($butter); }
  23. red

  24. problem: our current implementation doesn’t know what’s in the cart,

    only prices. remove the last test to go back to green
  25. final class ShoppingCart { private $products = []; public function

    checkout() { return array_reduce( $this->products, function($carry, Product $product) { return $carry + $product->getPrice(); } ); } // ...
  26. // ... public function add(Product $product) { $this->products[] = $product;

    } public function remove(Product $product) { array_splice( $this->products, array_search($product, $this->products), 1 ); } }
  27. /** * @expectedException CanNotRemoveProduct */ public function test_it_throws_when_trying_to_remove_product_that_it_does_not_have() { $cart

    = new ShoppingCart(); $bread = new Product('Bread', 2.20); $cart->add($bread); $butter = new Product('Butter', 1.00); $cart->remove($butter); }
  28. red

  29. final class ShoppingCart { // ... public function remove(Product $product)

    { if (!in_array($product, $this->products)) { throw new CanNotRemoveProduct(); } array_splice( $this->products, array_search($product, $this->products), 1 ); } }
  30. red

  31. /** * @expectedException AlreadyCheckedOut */ public function test_it_throws_when_trying_to_add_products_after_checkout() { $cart

    = new ShoppingCart(); $cart->checkout(); $bread = new Product('Bread', 2.20); $cart->add($bread); }
  32. red

  33. final class ShoppingCart { private $checkedOut = false; // ...

    public function checkout() { $this->checkedOut = true; // ... } // ... }
  34. final class ShoppingCart { // ... public function add(Product $product)

    { if ($this->checkedOut === true) { throw new AlreadyCheckedOut(); } // ... } // ... }
  35. /** * @expectedException AlreadyCheckedOut */ public function test_it_throws_when_trying_to_remove_products_after_checkout() { $cart

    = new ShoppingCart(); $bread = new Product('Bread', 2.20); $cart->add($bread); $cart->checkout(); $cart->remove($bread); }
  36. red

  37. final class ShoppingCart { // ... public function remove(Product $product)

    { if ($this->checkedOut === true) { throw new AlreadyCheckedOut(); } // ... } }
  38. final class ShoppingCart { // ... public function add(Product $product)

    { $this->assertNotCheckedOutYet(); // ... } public function remove(Product $product) { $this->assertNotCheckedOutYet(); // ... } }
  39. final class ShoppingCart { // ... private function assertNotCheckedOutYet() {

    if ($this->checkedOut === true) { throw new AlreadyCheckedOut(); } } }
  40. /** * @expectedException CanNotRemoveProduct */ public function test_it_throws_when_trying_to_remove_product_that_it_does_not_have() { $cart

    = new ShoppingCart(); $bread = new Product('Bread', 2.20); $cart->add($bread); $butter = new Product('Butter', 1.00); $cart->remove($butter); }
  41. class AddToCartControllerTest extends PHPUnit_Framework_TestCase { public function test_it_adds_a_product_and_persists_the_cart() {
 $controller

    = new AddToCartController(); $request = new Request( 'GET', '/addToCart', array('cartId' => 13, 'productId' => 37) ); $response = $controller->handle($request); $expectedResponse = new Response(200, …); $this->assertEquals($expectedResponse, $response); } }
  42. class AddToCartControllerTest extends PHPUnit_Framework_TestCase { public function test_it_adds_a_product_and_persists_the_cart() {
 $controller

    = new AddToCartController(); $request = new Request( 'GET', '/addToCart', array('cartId' => 13, 'productId' => 37) ); $response = $controller->handle($request); $expectedResponse = new Response(200, …); $this->assertEquals($expectedResponse, $response); } }
  43. final class AddToCartController { public function handle(Request $request) { //

    Get product by id (from $request) // Get cart by id (from $request) // Add product to cart // Persist cart // Return a Response } }
  44. red

  45. final class AddToCartController { private $productRepository; private $cartRepository; public function

    __construct( ProductRepository $productRepository, CartRepository $cartRepository ) { $this->productRepository = $productRepository; $this->cartRepository = $cartRepository; } public function handle(Request $request) { // … } }
  46. final class AddToCartController { // … public function handle(Request $request)

    { $cartId = $request->param('cartId'); $cart = $this->cartRepository->getById($cartId); } }
  47. interface CartRepository { /** * Get a cart by its

    id * * @param int $cartId * * @return Cart */ public function getById($cartId); }
  48. class AddToCartControllerTest extends PHPUnit_Framework_TestCase { public function test_it_adds_a_product_and_persists_the_cart() {
 $controller

    = new AddToCartController(); $request = new Request( 'GET', '/addToCart', array('cartId' => 13, 'productId' => 37) ); $response = $controller->handle($request); $expectedResponse = new Response(200, …); $this->assertEquals($expectedResponse, $response); } }
  49. class AddToCartControllerTest extends PHPUnit_Framework_TestCase { public function test_it_adds_a_product_and_persists_the_cart() { //

    Same for ProductRepository $cart = new Cart(…);
 $cartRepository = $this->getMock('CartRepository'); $cartRepository->method('getById')->willReturn($cart); $controller = new AddToCartController( $productRepository, $cartRepository ); // … } }
  50. final class AddToCartController { // … public function handle(Request $request)

    { // Same for $product $cartId = $request->param('cartId'); $cart = $this->cartRepository->getById($cartId); $cart->add($product); $this->cartRepository->persist($cart); return new Response(200, …); } }
  51. red

  52. interface CartRepository { /** * Get a cart by its

    id * * @param int $cartId * * @return Cart */ public function getById($cartId); /** * Persist a cart * * @param Cart $cart */ public function persist(Cart $cart); }
  53. class AddToCartControllerTest extends PHPUnit_Framework_TestCase { public function test_it_adds_a_product_and_persists_the_cart() { //

    … 
 $cartRepository = $this->getMock('CartRepository'); // … $newCart = new Cart(…); $cartRepository ->expects($this->once()) ->method('persist') ->with($this->equalTo($newCart)); $controller = new AddToCartController( $productRepository, $cartRepository ); // … } }
  54. our setup ci-tests • mixture of unit tests and integration

    tests • access to the whole engagor setup • ran by Jenkins tests • mixture of unit tests and integration tests • access to the whole engagor setup • ran manually
  55. $ phpunit --color PHPUnit 4.7.6 by Sebastian Bergmann and contributors.

    ..................................................... 63 / 107 ( 58%) ............................................ Time: 20.86 seconds, Memory: 20.00Mb OK (107 tests, 633 assertions)
  56. $ phpunit --testsuite=ci --color PHPUnit 4.7.6 by Sebastian Bergmann and

    contributors. ....................................................... 65 / 85 ( 76%) .................... Time: 162 ms, Memory: 16.75Mb OK (85 tests, 107 assertions)