Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Tests doubles: the motion picture
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Fran Iglesias
November 02, 2018
Programming
540
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Tests doubles: the motion picture
Test doubles explained
Fran Iglesias
November 02, 2018
More Decks by Fran Iglesias
See All by Fran Iglesias
Tips for daily refactoring
franiglesias
0
360
Introduction to TDD: Red-Green-Refactor
franiglesias
1
320
Testing value objects
franiglesias
0
280
testing the unpredictable
franiglesias
0
250
Low cost techniques for test doubles
franiglesias
0
240
Other Decks in Programming
See All in Programming
脅威をエンジニアリングの糧にして――現場編 / Turning Threats into Engineering Fuel — Field Edition
nrslib
0
280
Lessons from Spec-Driven Development
simas
PRO
0
210
「なぜそう決めたのか」を残し続ける仕組み ― Notion AI カスタムエージェント × Slack連携による設計判断の自動記録 - NIKKEI Tech Talk #47
niftycorp
PRO
0
200
Oxlintのカスタムルールの現況
syumai
6
1.1k
Webフレームワークの ベンチマークについて
yusukebe
0
170
Even G2とAWSで推しのエージェントを召喚しよう!
har1101
1
120
3Dシーンの圧縮
fadis
1
780
Performance Engineering for Everyone
elenatanasoiu
0
140
気づいたらRubyで100作品 ー クリエイティブコーディングが生活の一部になるまで / 100 Ruby Sketches Later: How Creative Coding Became Part of My Life
chobishiba
3
580
IBM Bobを活用したレガシーアプリの最新化
oniak3ibm
PRO
1
200
ローカルLLMでどこまでコードが書けるか -拡張版 / How much code can be written on a local LLM Extended
kishida
11
4.2k
キャリア迷子上等 ─ "ない道"は自分で作ればいい
16bitidol
3
2.1k
Featured
See All Featured
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
200
Building a Modern Day E-commerce SEO Strategy
aleyda
45
9.1k
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
3.5k
Ecommerce SEO: The Keys for Success Now & Beyond - #SERPConf2024
aleyda
1
2k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
2k
It's Worth the Effort
3n
188
29k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1.2k
Deep Space Network (abreviated)
tonyrice
0
210
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
370
<Decoding/> the Language of Devs - We Love SEO 2024
nikkihalliwell
1
250
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
200
Transcript
TDD 101 6. Test doubles: the motion picture
Test doubles: the motion picture
The plot
You want to test an object that uses collaborators…
You need to isolate its behaviour from that of its
collaborators
Then, you’ll need… test doubles
The argument
A unit of software Can be a query: returns a
response Can be a command: produces a change in the state of the system You should test the results of the behavior
When you test it You want to prove that the
behavior: • Is produced by the code of the unit of software • Is not influenced by any other factor
So, you need to control the behavior of collaborators Cancelling:
no behavior at all Controlling: knowing exactly what they are doing (by means of programming it)
The cast
Dummy No behavior Only interface
namespace Dojo\HappyBirthday\Logger; interface SimpleLogger { public function log(string $channel, string
$message): void; }
namespace Tests\Dojo\HappyBirthday\Double; use Dojo\HappyBirthday\Logger\SimpleLogger; class LoggerDummy implements SimpleLogger { public
function log(string $channel, string $message): void { } }
namespace Tests\Dojo\HappyBirthday; use Dojo\HappyBirthday\SendGreetings; use PHPUnit\Framework\TestCase; class SendGreetingsTest extends TestCase
{ public function testSendGreetingToCustomersWhenIsTheirBirthday() { $clockService = $this->getClockServiceStub(); $logger = $this->getLoggerDummy(); $customerRepository = $this->getCustomerRepositoryStub(); $mailer = $this->getMailerSpy([ new Customer('
[email protected]
'), new Customer('
[email protected]
'), new Customer('
[email protected]
') ]); $sendGreetings = new SendGreetings( $clockService, $customerRepository, $mailer, $logger ); $sendGreetings->execute(); $this->assertEquals(3. $mailer->getMessagesSent()); } }
Stub Programmed behavior
namespace Dojo\HappyBirthday\Clock; use DateTimeImmutable; interface ClockService { public function currentDate():
DateTimeImmutable; }
namespace Tests\Dojo\HappyBirthday\Double; use DateTimeImmutable; use Dojo\HappyBirthday\Clock\ClockService; class ClockServiceStub implements ClockService
{ /** @var DateTimeImmutable */ private $dateTime; public function __construct(DateTimeImmutable $dateTime) { $this->dateTime = $dateTime; } public function currentDate() : DateTimeImmutable { return $this->dateTime; } }
namespace Tests\Dojo\HappyBirthday; use Dojo\HappyBirthday\SendGreetings; use PHPUnit\Framework\TestCase; class SendGreetingsTest extends TestCase
{ public function testSendGreetingToCustomersWhenIsTheirBirthday() { $clockService = $this->getClockServiceStub(); $logger = $this->getLoggerDummy(); $customerRepository = $this->getCustomerRepositoryStub(); $mailer = $this->getMailerSpy([ new Customer('
[email protected]
'), new Customer('
[email protected]
'), new Customer('
[email protected]
') ]); $sendGreetings = new SendGreetings( $clockService, $customerRepository, $mailer, $logger ); $sendGreetings->execute(); $this->assertEquals(3. $mailer->getMessagesSent()); } }
Fake Behavior implementation… but best suited for testing Needs its
own tests
namespace Dojo\HappyBirthday\Contacts; interface ContactRepositoryInterface { public function retrieveById(UUid $uuid): Contact;
}
namespace Dojo\HappyBirthday\Contacts; class InMemoryContactRepository implements ContactRepository { public function retrieveById(UUid
$uuid): Contact { } }
Spy Registers how is used Fragility (coupling)
interface Mailer { public function send(Message $message) : void; }
class MailerSpy implements Mailer { private $calls = 0; public
function send(Message $message) : void { $this->calls++; } public function getCalls() { return $this->calls; } }
Mock Has expectations about how is used Takes assertions away
from the test Fragility (coupling)
Taxonomy
Behavior Knowledge
test doubles by behavior dummy stub fake no behavior fixed
complete Applies to any kind of double by knowledge
test doubles by knowledge passive spy mock Applies to any
kind of double by behavior knows nothing registers use has expectations
How to
i
Design principles Dem SRP OCP DIP ISP LSP DRY Yagni
The “real” object Should have: • No behavior • No
side effects • Immutability
Self-shunt Exploratory and sparse use Simple interface
class ServiceTest extends TestCase implements Mailer { private $mailerCalls =
0; public function testMailer() { $sut = new Service($this); $sut->execute(); $this->assertEquals(2, $this->getCalls()); } public function send(Message $message) : void { $this->mailerCalls++; } }
Anonymous class Exploratory Single use Simple interface No side effects
class ServiceTest extends TestCase { public function testMailer() { $mailer
= new class implements Mailer { private $calls = 0; public function send(Message $message) : void { $this->calls++; } puablic function getCalls() { return $this->calls; } }; $sut = new Service($mailer); $sut->execute(); $this->assertEquals(2, $mailer->getCalls()); } }
Hand made double Simple interfaces Simple behaviour Could need its
own tests
Mocking library Large interfaces Complex behaviour Multiple scenarios
class ServiceTest extends TestCase { public function testMailer() { $mailer
= $this->createMock(Mailer::class); $mailer->expects($this->once()) ->method(‘send’) ->with($this->isInstanceOf(Message::class)) ->willReturn(true); $sut = new Service($mailer); $sut->execute(); } }
None