imagine a world without mocks

79
Imagine a world without mocks @KenScambler Scala Developer at

Upload: kenbot

Post on 09-Jan-2017

3.469 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Imagine a world without mocks

Imagine a world without mocks

@KenScambler Scala Developer at

Page 2: Imagine a world without mocks
Page 3: Imagine a world without mocks

NOOOPE

Page 4: Imagine a world without mocks

Me

14 years

5 years

5 years

when possible

when bored

when forced

Page 5: Imagine a world without mocks

http://techblog.realestate.com.au/to-kill-a-mockingtest/

Page 6: Imagine a world without mocks

Q: What’s so bad about mocks & stubs?

A: The problem they solve is “how to test poorly designed code”

Page 7: Imagine a world without mocks

How did we get here?

Page 8: Imagine a world without mocks

UserRepoUserService

User getUser(UserId)

DB

AuthServiceboolean authUser(UserId)

Collaborators galore!

Record selectUser(SQL)

Page 9: Imagine a world without mocks

UserRepoUserService

User getUser(UserId)

DB

AuthServiceboolean authUser(UserId)

Record selectUser(SQL)

How to separate UserService?

Page 10: Imagine a world without mocks

http://martinfowler.com/articles/mocksArentStubs.html

Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test.

Page 11: Imagine a world without mocks

UserServiceUser getUser(UserId)

If you ask me “authUser(1234)”, I’ll say “true”

Stub

UserRepoDB

Record selectUser(SQL)

Page 12: Imagine a world without mocks

UserServiceUser getUser(UserId)

Stub

UserRepoRecord selectUser(SQL)

UserService’s input is now deterministic!

If you ask me “authUser(1234)”, I’ll say “true”

DB

Page 13: Imagine a world without mocks

http://martinfowler.com/articles/mocksArentStubs.html

Mocks are objects pre-programmed with expectations which form a specification of the calls they are expected to receive.

Page 14: Imagine a world without mocks

UserServiceUser getUser(UserId)

I expect “selectUser()” to be called once

Mock

If you ask me “authUser(1234)”, I’ll say “true”

Page 15: Imagine a world without mocks

UserServiceUser getUser(UserId)

I expect “selectUser()” to be called once

Mock

UserService’s output is now deterministic!

If you ask me “authUser(1234)”, I’ll say “true”

Page 16: Imagine a world without mocks

UserServiceUser getUser(UserId)

MockDeterministic output

Deterministic input

= Fairly sane test

Page 17: Imagine a world without mocks

So far so good.

Page 18: Imagine a world without mocks

But wait! There’s a cost…

Page 19: Imagine a world without mocks

1. Coupled to brittle implementation details.

Page 20: Imagine a world without mocks

UserServiceUser getUser(UserId)

Mock

What if an equivalent method is called instead? If you ask me

“authUser(1234)”, I’ll say “true”

authLocalUser(1234)

Stub

Page 21: Imagine a world without mocks

UserServiceUser getUser(UserId)

Ah shit.

authLocalUser(1234)

Page 22: Imagine a world without mocks

UserServiceUser getUser(UserId)

Honestly, no one’s asked me that before.authLocalUser(1234)

Page 23: Imagine a world without mocks

UserServiceUser getUser(UserId)

I’m just a sock.

authLocalUser(1234)

Page 24: Imagine a world without mocks

2.Somewhat misses the point of the test

Page 25: Imagine a world without mocks

UserServiceUser getUser(UserId)

authLocalUser(1234)

I expect “selectUser()” to be called once

Still no idea.

Page 26: Imagine a world without mocks

UserServiceUser getUser(UserId)

IT DIDN’T GET CALLED!!! NOTHING HAPPENED!!!!!

Page 27: Imagine a world without mocks

UserServiceUser getUser(UserId)

IT DIDN’T GET CALLED!!! NOTHING HAPPENED!!!!!

Real problem:The stub configuration was out of date.

Page 28: Imagine a world without mocks

Case study #1Clumsy input

Page 29: Imagine a world without mocks

public interface Config { // Database stuff String getDatabaseHost(); int getDatabasePort(); int getMaxThreads(); int getConnectionTimeout(); // Potato settings String getDefaultPotatoVariety(); int getMaxPotatoes(); double getPotatoShininess();

// Sacrificial settings int getBloodSacrificeGoatCount(); int getBloodSacrificeChickenCount(); int getBloodSacrificeSheepCount(); }

Page 30: Imagine a world without mocks

public class PotatoService {

public PotatoService(Config config) { this.potatoVariety = config.getPotatoVariety(); this.maxPotatoes = config.getMaxPotatoes(); }

public Salad makePotatoSalad() {...} }

Page 31: Imagine a world without mocks

public class PotatoServiceTest {

Config config = mock(Config.class)

@Before public void before() {

when(config.getDefaultPotatoVariety()) .thenReturn(“pontiac”);

when(config.getMaxPotatoes()) .thenReturn(33); }

public testMakeSalad() { PotatoService service = new PotatoService(); Assert.equalTo(service.makeSalad(), ...); } }

Page 32: Imagine a world without mocks

public class PotatoServiceTest {

Config config = mock(Config.class)

@Before public void before() {

when(config.getDefaultPotatoVariety()) .thenReturn(“pontiac”);

when(config.getMaxPotatoes()) .thenReturn(33); }

public testMakeSalad() { PotatoService service = new PotatoService(); Assert.equalTo(service.makeSalad(), ...); } }

Stub

Page 33: Imagine a world without mocks

Looks ok. But what is the stub trying to tell us?

Page 34: Imagine a world without mocks

No-ones ever going to need all those things at once.

public interface Config { // Database stuff String getDatabaseHost(); int getDatabasePort(); int getMaxThreads(); int getConnectionTimeout(); // Potato settings String getDefaultPotatoVariety(); int getMaxPotatoes(); double getPotatoShininess();

// Sacrificial settings int getBloodSacrificeGoatCount(); int getBloodSacrificeChickenCount(); int getBloodSacrificeSheepCount(); }

Page 35: Imagine a world without mocks

That’s better!

public interface DatabaseConfig { String getDatabaseHost(); int getDatabasePort(); int getMaxThreads(); int getConnectionTimeout();}

public interface PotatoConfig { String getDefaultPotatoVariety(); int getMaxPotatoes(); double getPotatoShininess();}

public interface SacrificialConfig { int getBloodSacrificeGoatCount(); int getBloodSacrificeChickenCount(); int getBloodSacrificeSheepCount();}

Page 36: Imagine a world without mocks

public class PotatoService {

public PotatoService(PotatoConfig config) { this.potatoVariety = config.getPotatoVariety(); this.maxPotatoes = config.getMaxPotatoes(); }

public Salad makePotatoSalad() {...} }

Don’t you just need the two fields? Does it matter where they come from?

Page 37: Imagine a world without mocks

public class PotatoService {

public PotatoService(String variety, int max) { this.potatoVariety = variety; this.maxPotatoes = max; }

public Salad makePotatoSalad() {...} }

The application wiring can be someone else’s business.

Page 38: Imagine a world without mocks

public class PotatoServiceTest {

public testMakeSalad() { PotatoService service = new PotatoService(“pontiac”, 33);

Assert.equalTo(service.makeSalad(), ...); } }

Page 39: Imagine a world without mocks

- More modular- More reusable- Simpler- Less code- Stubs are gone

Page 40: Imagine a world without mocks

Case study #2Unnecessary mutable

state

Page 41: Imagine a world without mocks

removeCoins()

insertCoins()

collectCan()

Page 42: Imagine a world without mocks

public interface Wallet { int removeCoins(int amount); int getAmount(); }

public interface VendingMachine { void insertCoins(int amount); Can collectCan(); int getStoredCash();}

public interface Customer { void buyDrink();}

Page 43: Imagine a world without mocks

public class CustomerTest {

Wallet wallet = mock(Wallet.class); VendingMachine machine = mock(VendingMachine.class);

@Before public void before() { when(wallet.removeCoins(3)).thenReturn(3); when(vendingMachine.collectCan()) .thenReturn(new CokeCan()); }

public testBuyDrink() { Customer c = new Customer(); c.buyDrink();

verify(wallet).removeCoins(3); verify(vendingMachine).insertCoins(3); verify(vendingMachine).collectCan(); }}

Page 44: Imagine a world without mocks

public class CustomerTest {

Wallet wallet = mock(Wallet.class); VendingMachine machine = mock(VendingMachine.class);

@Before public void before() { when(wallet.removeCoins(3)).thenReturn(3); when(vendingMachine.collectCan()) .thenReturn(new CokeCan()); }

public testBuyDrink() { Customer c = new Customer(); c.buyDrink();

verify(wallet).removeCoins(3); verify(vendingMachine).insertCoins(3); verify(vendingMachine).collectCan(); }}

Stub

Mock

Page 45: Imagine a world without mocks

The class under test is separated now!

But what are the mocks telling us?

Page 46: Imagine a world without mocks

public interface Wallet { int removeCoins(int amount); int getAmount(); }

public interface VendingMachine { void insertCoins(int amount); Can collectCan(); int getStoredCash();}

public interface Customer { void buyDrink();}

Surely we care about the resulting state, not the in-betweeny verbs.

Page 47: Imagine a world without mocks

If the state is just immutable values, we don’t have to force isolation

public interface Wallet { int removeCoins(int amount); int getAmount(); }

public interface VendingMachine { void insertCoins(int amount); Can collectCan(); int getStoredCash();}

public interface Customer { void buyDrink();}

Page 48: Imagine a world without mocks

public interface Wallet { int getAmount(); Wallet removeCoins(int amount);}

public interface VendingMachine { Optional<Can> getCanInTray(); int getStoredCash(); List<Can> getCansInMachine(); VendingMachine insertCoins(int amount); VendingMachine collectCan();}

public interface Customer { Wallet getWallet(); List<Can> getCansHeld(); Pair<VendingMachine, Customer> buyDrink(VendingMachine vm);}

Page 49: Imagine a world without mocks

public interface Wallet { int getAmount(); Wallet removeCoins(int amount);}

public interface VendingMachine { Optional<Can> getCanInTray(); int getStoredCash(); List<Can> getCansInMachine(); VendingMachine insertCoins(int amount); VendingMachine collectCan();}

public interface Customer { Wallet getWallet(); List<Can> getCansHeld(); Pair<VendingMachine, Customer> buyDrink(VendingMachine vm);}

Immutable state

Page 50: Imagine a world without mocks

public interface Wallet { int getAmount(); Wallet removeCoins(int amount);}

public interface VendingMachine { Optional<Can> getCanInTray(); int getStoredCash(); List<Can> getCansInMachine(); VendingMachine insertCoins(int amount); VendingMachine collectCan();}

public interface Customer { Wallet getWallet(); List<Can> getCansHeld(); Pair<VendingMachine, Customer> buyDrink(VendingMachine vm);}

“Actions” just return new copies

Page 51: Imagine a world without mocks

public class CustomerTest {

public testBuyDrink() { Customer c = new Customer(new Wallet(23)); VendingMachine vm = new VendingMachine(10,30);

Pair<VendingMachine, Customer> result = c.buyDrink(vm); Customer c2 = result.second(); VendingMachine vm2 = result.first();

Assert.equals(20, c2.getWallet().getAmount()); Assert.equals(9, vm2.getCansInMachine().size()); Assert.equals(33, vm2.getStoredCash()); }}

Page 52: Imagine a world without mocks

- Less moving parts- More reusable- Simpler- Easier - Mocks & Stubs are

gone

Page 53: Imagine a world without mocks

“But then it’s an integration test!”

Page 54: Imagine a world without mocks

1. Immutable data structures are just

values.

Page 55: Imagine a world without mocks

2. We have no business peeking at

a method’s tools; only its results,

effects

Page 56: Imagine a world without mocks

3. Pure functions are already

deterministic

Page 57: Imagine a world without mocks

Case study #3Essential effects

Page 58: Imagine a world without mocks

public interface EmailSender { void sendEmail(String addr, Email email);}

public class SpecialOffers { private final EmailSender sender;

void sendSpecialOffers(Customer c) { if (!c.isUnsubscribed()) { String content = "Hi " + c.getName() + "!"; sender.sendEmail(c.getEmailAddr(), new Email(content)) } }}

Page 59: Imagine a world without mocks

public class SpecialOffersTest {

EmailSender sender = mock(EmailSender.class)

public testSendEmail() { SpecialOffers offers = new SpecialOffers(sender);

offers.sendSpecialOffers( new Customer(false, “Bob”, “[email protected]”));

verify(sender).send(“[email protected]”, new Email(“Hi, Bob!”)); }}

Page 60: Imagine a world without mocks

public class SpecialOffersTest {

EmailSender sender = mock(EmailSender.class)

public testSendEmail() { SpecialOffers offers = new SpecialOffers(sender);

offers.sendSpecialOffers( new Customer(false, “Bob”, “[email protected]”));

verify(sender).send(“[email protected]”, new Email(“Hi, Bob!”)); }}

Mock

Page 61: Imagine a world without mocks

Ok, so it tests we send an email.

But what is the mock trying to tell us?

Page 62: Imagine a world without mocks

public interface EmailSender { void sendEmail(String addr, Email email);}

public class SpecialOffers { private final EmailSender sender;

void sendSpecialOffers(Customer c) { if (!c.isUnsubscribed()) { String content = "Hi " + c.getName() + "!"; sender.sendEmail(c.getEmailAddr(), new Email(content)) } }} I only care about the intent to

send an email, not the actual sending. Can the intent be its own thing?

Page 63: Imagine a world without mocks

public interface SendEmailIntent { String getAddress(); Email getEmail();}

public interface Interpreter { void interpret(SendEmailIntent intent);}

public class SpecialOffers {

Optional<SendEmailIntent> sendSpecialOffers( Customer c) { if (!c.isUnsubscribed()) { String content = "Hi " + c.getName() + "!"; return Optional.of(new SendEmailIntent( c.getEmailAddr(), new Email(content))); } else { return Optional.empty(); } }}

We can have an interpreter elsewhere.

Page 64: Imagine a world without mocks

public class SpecialOffersTest {

public testSendEmail() { SpecialOffers offers = new SpecialOffers();

SendEmailIntent intent = offers.sendSpecialOffers( new Customer(false, “Bob”, “[email protected]”)).get();

Assert.equals(intent.getAddress(), “[email protected]”); Assert.equals(intent.getEmail().getText(), “Hi, Bob!”); }}

Page 65: Imagine a world without mocks

- Separated intent from execution

- More reusable- Simpler- Easier - Mocks are gone

Page 66: Imagine a world without mocks

Mocks kill TDD.

Page 67: Imagine a world without mocks

TDD = design methodology

Page 68: Imagine a world without mocks

Test-first encourages you to design code well enough to

test…

Page 69: Imagine a world without mocks

…and no further.

Page 70: Imagine a world without mocks

Mocks & stubs set a

looooow bar

Page 71: Imagine a world without mocks

This totally guts TDD’s value for design.

Page 72: Imagine a world without mocks

Conclusion:Side effects are the real killer

Page 73: Imagine a world without mocks

All I do is make the input deterministic. If the input is already just immutable values, then you don’t need me.

Stub Mock

Page 74: Imagine a world without mocks

If you’re just using me because stuff is hard to create, you need to get back and design harder!

Stub Mock

Page 75: Imagine a world without mocks

I make output deterministic, by recording method calls instead of allowing effects.

Stub Mock

Page 76: Imagine a world without mocks

Sometimes, this means that I test a pointless web of lies, that doesn’t touch the code’s reason for existence.

Stub Mock

Page 77: Imagine a world without mocks

Other times, I am really testing the intent of the code, which can be pulled out as its own structure. This separates the concern of choosing the next thing.

Stub Mock

Page 78: Imagine a world without mocks

Stub Mock

If you are using immutable types and pure functions, then you’re home and hosed.

Forget about • “collaborators”• “Tell don’t ask”• Avoiding static

methods• Avoiding “new”.

Page 79: Imagine a world without mocks