73
Why Your Test Suite Sucks (and what you can do about it) by Ciaran McNulty at PHPCon Poland 2015

Why Your Test Suite Sucks - PHPCon PL 2015

Embed Size (px)

Citation preview

Page 1: Why Your Test Suite Sucks - PHPCon PL 2015

Why Your Test Suite Sucks(and what you can do about it)

by Ciaran McNulty at PHPCon Poland 2015

Page 2: Why Your Test Suite Sucks - PHPCon PL 2015

It’s not working for me

===

TDD must suck1

1 BIT.LY/1LEF0GI & BIT.LY/1SLFGTQ

Page 3: Why Your Test Suite Sucks - PHPCon PL 2015

The problem is not TDD

Page 4: Why Your Test Suite Sucks - PHPCon PL 2015

The problem is your suite

Page 5: Why Your Test Suite Sucks - PHPCon PL 2015

Value = Benefits - Costs

Page 6: Why Your Test Suite Sucks - PHPCon PL 2015

Suites suck when you aren’t ge)ng all the benefits of TDD

Page 7: Why Your Test Suite Sucks - PHPCon PL 2015

Suites suck when the tests are hard to maintain

Page 8: Why Your Test Suite Sucks - PHPCon PL 2015

Reason 1:You don't have a test suite

Page 9: Why Your Test Suite Sucks - PHPCon PL 2015

The Evolu*on of a TDD Team

Page 10: Why Your Test Suite Sucks - PHPCon PL 2015

Maturity model

• Manual tes&ng

• Automated tes&ng

• Test-first development

• Test-driven development

Page 11: Why Your Test Suite Sucks - PHPCon PL 2015

Manual tes*ng

• Design an implementa+on

• Implement that design

• Manually test

Page 12: Why Your Test Suite Sucks - PHPCon PL 2015

Growing from Manual to Automated

• “When we make changes, it breaks things!”

• “We spend a lot of /me on manual tes:ng”

• “Bugs keep making it into produc:on”

Enabled by:- Tutorials, training- Team policies

Page 13: Why Your Test Suite Sucks - PHPCon PL 2015

Automated tes+ng

• Design an implementa+on

• Implement that design

• Write tests to verify it

Page 14: Why Your Test Suite Sucks - PHPCon PL 2015

Growing from Automated to Test-first

• “It’s a pain having to write tests once I’m done”

• “My test suite is bri'le”

• “Maintaining the suite is hard”

• ”TDD Doesn’t Work!”

Supported by:- Coaching, pairing- Leadership

Page 15: Why Your Test Suite Sucks - PHPCon PL 2015

Test-first development

• Design an implementa+on

• Write tests to verify that implementa+on

• Implement that design

Page 16: Why Your Test Suite Sucks - PHPCon PL 2015

Growing from Test-first to Test-driven

• “Test suite is s"ll bri,le”

• “Maintaining the suite is s"ll hard”

• ”TDD Doesn’t Work!”

Supported by:- Prac1ce

Page 17: Why Your Test Suite Sucks - PHPCon PL 2015

Test-driven development

• Write tests that describe behaviour

• Implement a system that has that behaviour

Problems:- “I don’t know what to do with all this spare 'me!”- “Where should we keep this extra money?”

Page 18: Why Your Test Suite Sucks - PHPCon PL 2015

Reason 2:You are tes*ng implementa*on

Page 19: Why Your Test Suite Sucks - PHPCon PL 2015

Problem: Mocking Queries

• A query is a method that returns a value without side effects

• We shouldn’t care how many 4mes (if any) a query is called

• Specifying that detail makes change harder

Page 20: Why Your Test Suite Sucks - PHPCon PL 2015

function testItGreetsPeopleByName(){ $user = $this->getMock('User');

$user->expects($this->once()) ->method('getName') ->willReturn('Ciaran');

$greeting = $this->greeter->greet($user);

$this->assertEquals('Hello, Ciaran!', $greeting);}

Page 21: Why Your Test Suite Sucks - PHPCon PL 2015

function testItGreetsPeopleByName(){ $user = $this->getMock('User');

$user->method('getName') ->willReturn('Ciaran');

$greeting = $this->greeter->greet($user);

$this->assertEquals('Hello, Ciaran!', $greeting);}

Page 22: Why Your Test Suite Sucks - PHPCon PL 2015

Don’t mock queries, just use stubsDo you really care how many /mes it's called?

Page 23: Why Your Test Suite Sucks - PHPCon PL 2015

Problem: Tes,ng the sequence of calls

• Most of the *me we don’t care what order calls are made in

• Tes*ng the sequence makes it hard to change

Page 24: Why Your Test Suite Sucks - PHPCon PL 2015

public function testBasketTotals(){ $priceList = $this->getMock('PriceList');

$priceList->expect($this->at(0)) ->method('getPrices') ->willReturn(120);

$priceList->expect($this->at(1)) ->method('getPrices') ->willReturn(200);

$this->basket->add('Milk', 'Bread'); $total = $this->basket->calculateTotal();

$this->assertEqual(320, $total);}

Page 25: Why Your Test Suite Sucks - PHPCon PL 2015

public function testBasketTotals(){ $priceList = $this->getMock('PriceList');

$priceList->method('getPrices') ->will($this->returnValueMap([ ['Milk', 120], ['Bread', 200] ]));

$this->basket->add('Milk', 'Bread'); $total = $this->basket->calculateTotal();

$this->assertEqual(320, $total);}

Page 26: Why Your Test Suite Sucks - PHPCon PL 2015

Test behaviour you care about, don’t test implementa/on

PROTIP: The best way to do this is to not have an implementa3on yet

Page 27: Why Your Test Suite Sucks - PHPCon PL 2015

Reason 3:Because your design sucks

Page 28: Why Your Test Suite Sucks - PHPCon PL 2015

Problem: Too many Doubles

• It is painful to have to double lots of objects in your test

• It is a signal your object has too many dependencies

Page 29: Why Your Test Suite Sucks - PHPCon PL 2015

public function setUp(){ $chargerules = $this->getMock('ChargeRules'); $chargetypes = $this->getMock('ChargeTypes'); $notifier = $this->getMockBuilder('Notifier') ->disableOriginalConstructor()->getMock();

$this->processor = new InvoiceProcessor( $chargerules, $chargetypes, $notifier ); }

Page 30: Why Your Test Suite Sucks - PHPCon PL 2015

class InvoiceProcessor{ public function __construct( ChargeRules $chargeRules, ChargeTypes $chargeTypes, Notifier $notifier ) { $this->chargeRules = $chargeRules; $this->chargeTypes = $chargeTypes; $this->notifier = $notifier; }

// …

Page 31: Why Your Test Suite Sucks - PHPCon PL 2015

Too many doubles in your test mean your class has too many

dependencies

Page 32: Why Your Test Suite Sucks - PHPCon PL 2015

Problem: Stubs returning stubs

• A related painful issue is stubs returning a stubs

• It is a signal our object does not have the right dependencies

Page 33: Why Your Test Suite Sucks - PHPCon PL 2015

public function testItNotifiesTheUser(){ $user = $this->getMock(User::class); $contact = $this->getMock(Contact::class); $email = $this->getMock(Email::class); $emailer = $this->getMock(Emailer::class);

$user->method('getContact')->willReturn($contact); $contact->method('getEmail')->willReturn($email); $emailer->expects($this->once) ->method('sendTo') ->with($this->equalTo($email));

(new Notifier($emailer))->notify($user);}

Page 34: Why Your Test Suite Sucks - PHPCon PL 2015

class Notifier{ public function __construct(Emailer $emailer) { // … }

public function notify(User $user) { $email = $user->getContact()->getEmail()

$this->emailer->sendTo($email); }}

Page 35: Why Your Test Suite Sucks - PHPCon PL 2015

class Notifier{ public function __construct(Emailer $emailer) { // … }

public function notify(User $user) { $this->emailer->sendTo($user); }}

Page 36: Why Your Test Suite Sucks - PHPCon PL 2015

class Notifier{ public function __construct(Emailer $emailer) { // … }

public function notify(Email $email) { $this->emailer->sendTo($email); }}

Page 37: Why Your Test Suite Sucks - PHPCon PL 2015

public function testItNotifiesTheUser(){ $email = $this->getMock(Email::class); $emailer = $this->getMock(Emailer::class);

$emailer->expects($this->once) ->method('sendTo') ->with($this->equalTo($email));

(new Notifier($emailer))->notify($user);}

Page 38: Why Your Test Suite Sucks - PHPCon PL 2015

Stubs returning stubs indicate dependencies are not correctly

defined

Page 39: Why Your Test Suite Sucks - PHPCon PL 2015

Problem: Par+ally doubling the object under test

• A common ques*on is “how can I mock methods on the SUT?”

• Easy to do in some tools, impossible in others

• It is a signal our object has too many responsibili*es

Page 40: Why Your Test Suite Sucks - PHPCon PL 2015

class Form{ public function handle($data) { if ($this->validate($data)) { return 'Valid'; }

return 'Invalid'; }

protected function validate($data) { // do something complex }}

Page 41: Why Your Test Suite Sucks - PHPCon PL 2015

function testItOutputsMessageOnValidData(){ $form = $this->getMock('Form', ['validate']); $form->method('validate')->willReturn(true);

$result = $form->handle([]);

$this->assertSame('Valid', $result);}

Page 42: Why Your Test Suite Sucks - PHPCon PL 2015

class Form{ protected function __construct(Validator $validator) { // … }

public function handle($data) { if ($this->validator->validate($data)) { return 'Valid'; }

return 'Invalid'; }}

Page 43: Why Your Test Suite Sucks - PHPCon PL 2015

function testItOutputsMessageOnValidData(){ $validator = $this->getMock(‘Validator’); $validator->method('validate')->willReturn(true);

$form = new Form($validator);

$result = $form->handle([]);

$this->assertSame('Valid', $result);}

Page 44: Why Your Test Suite Sucks - PHPCon PL 2015

If you want to mock part of the SUT it means you should really have

more than one object

Page 45: Why Your Test Suite Sucks - PHPCon PL 2015

How to be a be)er so,ware designer:

• Stage 1: Think about your code before you write it

• Stage 2: Write down those thoughts

• Stage 3: Write down those thoughts as code

Page 46: Why Your Test Suite Sucks - PHPCon PL 2015

How long will it take without TDD?

• 1 hour thinking about how it should work

• 30 mins Implemen6ng it

• 30 mins Checking it works

Page 47: Why Your Test Suite Sucks - PHPCon PL 2015

How long will it take with TDD?

• 1 hour Thinking in code about how it should work

• 30 mins Implemen8ng it

• 1 min Checking it works

Page 48: Why Your Test Suite Sucks - PHPCon PL 2015

Use your test suite as a way to reason about the code

Page 49: Why Your Test Suite Sucks - PHPCon PL 2015

Customers should not dictate your design process

Page 50: Why Your Test Suite Sucks - PHPCon PL 2015

Reason 4:You are unit tes,ng across domain

boundaries

Page 51: Why Your Test Suite Sucks - PHPCon PL 2015

Problem: Tes,ng 3rd party code

• When we extend a library or framework we use other people’s code

• To test our code we end up tes.ng their code too

• This :me is wasted – we should trust their code works

Page 52: Why Your Test Suite Sucks - PHPCon PL 2015

class UserRepository extends \Framework\Repository{ public function findByEmail($email) { $this->find(['email' => $email]); }}

Page 53: Why Your Test Suite Sucks - PHPCon PL 2015

public function testItFindsTheUserByEmail(){ $db = $this->getMock(\Framework\DatabaseConnection::class); $schemaCreator = $this->getMock(\Framework\SchemaCreator::class); $schema = $this->getMock(\Framework\Schema::class);

$schemaCreator->method('getSchema')->willReturn($schema); $schema->method('getMappings')->willReturn(['email'=>'email']); $db->method('query')->willReturn(['email'=>'[email protected]');

$repo = new UserRepository($db, $schemaCreator);

$user = $repo->findByEmail('[email protected]');

$this->assertType(User::class, $user);}

Page 54: Why Your Test Suite Sucks - PHPCon PL 2015

class UserRepository{ private $repository;

public function __construct(\Framework\Repository $repository) { $this->repository = $repository; }

public function findByEmail($email) { return $this->repository->find(['email' => $email]); }}

Page 55: Why Your Test Suite Sucks - PHPCon PL 2015

public function testItFindsTheUserByEmail(){ $fwRepo = $this->getMock(\Framework\Repository::class); $user = $this->getMock(User::class);

$repository = new UserRepository($fwRepo);

$fwRepo->method('find') ->with(['email' => '[email protected]') ->willReturn($user);

$actualUser = $repository->findByEmail('[email protected]');

$this->assertEqual($user, $actualUser);}

Page 56: Why Your Test Suite Sucks - PHPCon PL 2015

When you extend third party code, you take responsibility for its

design decisions

Page 57: Why Your Test Suite Sucks - PHPCon PL 2015

Favour composi'on over inheritance

Page 58: Why Your Test Suite Sucks - PHPCon PL 2015

Problem: Doubling 3rd party code• Test Doubles (Mocks, Stubs, Fakes) are used to test

communica(on between objects

• Tes:ng against a Double of a 3rd party object does not give us confidence that we have described the communica:on correctly

Page 59: Why Your Test Suite Sucks - PHPCon PL 2015

public function testItUploadsValidDataToTheCloud(){ $validator = $this->getMock(FileValidator::class); $client = $this->getMock(\Cloud\Api::class); $file = $this->getMock(File::class);

$validator->method('validate') ->with($this->equalTo($file)) ->willReturn(true);

$validator->expects($this->once())->method('startUpload'); $client->expects($this->once())->method('uploadData'); $client->expects($this->once()) ->method('uploadSuccessful') ->willReturn(true);

(new FileHandler($client, $validator))->process($file);}

Page 60: Why Your Test Suite Sucks - PHPCon PL 2015

Coupled architecture

Page 61: Why Your Test Suite Sucks - PHPCon PL 2015

Coupled architecture

Page 62: Why Your Test Suite Sucks - PHPCon PL 2015

Layered architecture

Page 63: Why Your Test Suite Sucks - PHPCon PL 2015

Tes$ng layered architecture

Page 64: Why Your Test Suite Sucks - PHPCon PL 2015

public function testItUploadsValidDataToTheCloud(){ $fileStore = $this->getMock(FileStore::class); $file = $this->getMock(File::class);

$validator->method('validate') ->with($this->equalTo($file)) ->willReturn(true);

(new FileHandler($fileStore, $validator))->process($file);}

Page 65: Why Your Test Suite Sucks - PHPCon PL 2015

Tes$ng layered architecture

Page 66: Why Your Test Suite Sucks - PHPCon PL 2015

class CloudFilestoreTest extends PHPUnit_Framework_TestCase{ /** @group integration */ function testItStoresFiles() { $testCredentials = … $file = new File(…);

$apiClient = new \Cloud\Api($testCredentials); $filestore = new CloudFileStore($apiClient);

$filestore->store($file);

$this->assertTrue($apiClient->fileExists(…)); }}

Page 67: Why Your Test Suite Sucks - PHPCon PL 2015

Tes$ng layered architecture

Page 68: Why Your Test Suite Sucks - PHPCon PL 2015

Don’t test other people’s codeYou don't know how it's supposed to behave

Page 69: Why Your Test Suite Sucks - PHPCon PL 2015

Don’t test how you talk to other people’s code

You don't know how it's supposed to respond

Page 70: Why Your Test Suite Sucks - PHPCon PL 2015

Pu#ng it all together

• Use TDD to drive development

• Use TDD to replace exis1ng prac1ces, not as a new extra task

Page 71: Why Your Test Suite Sucks - PHPCon PL 2015

Pu#ng it all together

Don’t ignore tests that suck, improve them:

• Is the test too !ed to implementa!on?

• Does the design of the code need to improve?

• Are you tes!ng across domain boundaries?

Page 72: Why Your Test Suite Sucks - PHPCon PL 2015

Image credits

• Human Evolu+on by Tkgd2007h-p://bit.ly/1DdLvy1 (CC BY-SA 3.0)

• Oops by Steve and Sara Emry h-ps://flic.kr/p/9Z1EEd (CC BY-NC-SA 2.0)

• Blueprints for Willborough Home by Brian Tobin h-ps://flic.kr/p/7MP9RU (CC BY-NC-ND 2.0)

• Border by Giulia van Pelt h-ps://flic.kr/p/9YT1c5 (CC BY-NC-ND 2.0)

Page 73: Why Your Test Suite Sucks - PHPCon PL 2015

Thank you!

h"ps://joind.in/16248

@ciaranmcnulty