99% is not enough
TRANSCRIPT
Goods and Bads• fully object-oriented
• easy refactoring thanks to 100% code coverage
• not enough automated acceptance tests
• dependencies to legacy software (database changes, API calls)
public function execute() { $orders = $this->orderService->getOrders($this->orderNumber); $json = $this->orderJsonMapper->map($orders);
return new CommandResult($json); }
Example
public function testExecute() { $service = $this->createMock(OrderService::class); $mapper = $this->createMock(OrderJsonMapper::class);
$order1 = $this->createMock(Order::class); $order2 = $this->createMock(Order::class);
$command = new GetOrdersCommand('123', $service, $mapper);
$service->expects($this->once()) ->method('getOrders') ->with('123') ->willReturn([$order1, $order2]);
$mapper->expects($this->once()) ->method('map') ->with([$order1, $order2]);
$command->execute(); }
public function execute() { $orders = $this->orderService->getOrders($this->orderNumber); $json = $this->orderJsonMapper->map($orders);
return new CommandResult(json_encode('{"foo":"bar"}')); }
Breaking Stuff
Unit Testing leads to SOLID(ish) code• Single Responsibility
• Open/Closed
• Liskov Substitution
• Interface Segregation
• Dependency Inversion
<?php namespace Kartenmacherei\Phpughh;
use PHPUnit_Framework_TestCase;
class SmokeTest extends PHPUnit_Framework_TestCase { /** * @dataProvider furyUrlProvider * * @param Url $url */ public function testFuryUrl(Url $url) { $result = $this->sendGetRequest($url);
$this->assertSame(200, $result->getStatusCode()); $this->assertNotEmpty($result->getBody()); $this->assertLessThanOrEqual(100, $result->getTimeToFirstByteInMilliseconds()); }
Why 100%?• Robustness
• Feeling of security when refactoring
• Fixing bugs is cheaper
• Reducing amount of major bugs found in production
• Broken Windows Theory
Strict PHPUnit Config<?xml version="1.0" encoding="UTF-8"?> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.6/phpunit.xsd" bootstrap="bootstrap.php" backupGlobals="false" backupStaticAttributes="false" beStrictAboutChangesToGlobalState="true" beStrictAboutOutputDuringTests="true" beStrictAboutTestsThatDoNotTestAnything="true" forceCoversAnnotation="true" beStrictAboutCoversAnnotation="true">
Mocking public function testCalculatorMultiplies() { $multiplier = $this->getMultiplierMock(); $calculator = new Calculator($multiplier); // (…) }
/** * @return PHPUnit_Framework_MockObject_MockObject|Multiplier */ private function getMultiplierMock() { return $this->createMock(Multiplier::class); }
@codeCoverageIgnore
• errorHandlers
• shutdownHandlers
Be very strict about excluding code from coverage!
@codeCoverageIgnoreBe very strict about excluding code from coverage!
/** * @param mixed $targetNode * * @throws WrongInstanceException */ private function ensureIsElement($targetNode) { if (!$targetNode instanceof fDOMElement) { // @codeCoverageIgnoreStart throw new WrongInstanceException('target node is not instance of fDOMElement'); // @codeCoverageIgnoreEnd } }
Don't test your privates!
• Uncovered private methods are probably obsolete
• "If covering private methods by only testing public methods is hard, there might be another class in there struggling to get out"
"Who's going to pay for all that?"Re
lative
Cost
of a B
ugfix
40x
80x
120x
160x
Requirements Design Code Developer Tests Acceptance Tests Operations
1x 5x 10x20x
50x
150x
Source: Barry Boehm, Keynote Address, EQUITY 2007.
https://www.instagram.com/kartenmacherei/
Q&A
https://www.facebook.com/kartenmacherei/ +49 40 468996861
http://www.kartenmacherei.de/recruiting