Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Imagine a world
without mocks
@KenScambler
Scala Developer at
Me
14 years
5 years
5 years
when possible
when bored
when forced
http://techblog.realestate.com.au/to-kill-a-mockingtest/
Q: What’s so bad about
mocks & stubs?
A: The problem they solve is
“how to test poorly designed
code”
How did we get here?
UserRepo
UserService
User getUser(UserId)
DB
AuthService
boolean authUser(UserId)
Collaborators galore!
Record selectUser(...
UserRepo
UserService
User getUser(UserId)
DB
AuthService
boolean authUser(UserId)
Record selectUser(SQL)
How to separate U...
http://martinfowler.com/articles/mocksArentStubs.html
Stubs provide canned answers
to calls made during the test,
usually ...
UserService
User getUser(UserId)
If you ask me
“authUser(1234)”,
I’ll say “true”
Stub
UserRepo
DB
Record selectUser(SQL)
UserService
User getUser(UserId)
Stub
UserRepo
Record selectUser(SQL)
UserService’s
input is now
deterministic!
If you ask...
http://martinfowler.com/articles/mocksArentStubs.html
Mocks are objects pre-
programmed with
expectations which form a
spe...
UserService
User getUser(UserId)
I expect
“selectUser()”
to be called once
Mock
If you ask me
“authUser(1234)”,
I’ll say “...
UserService
User getUser(UserId)
I expect
“selectUser()”
to be called once
Mock
UserService’s
output is now
deterministic!...
UserService
User getUser(UserId)
Mock
Deterministic
output
Deterministic
input
= Fairly sane test
So far so good.
But wait!
There’s a cost…
1.
Coupled to brittle
implementation
details.
UserService
User getUser(UserId)
Mock
What if an equivalent
method is called
instead? If you ask me
“authUser(1234)”,
I’ll...
UserService
User getUser(UserId)
Ah shit.
authLocalUser(1234)
UserService
User getUser(UserId)
Honestly, no
one’s asked me
that before.
authLocalUser(1234)
UserService
User getUser(UserId)
I’m just a sock.
authLocalUser(1234)
2.
Somewhat misses the
point of the test
UserService
User getUser(UserId)
authLocalUser(1234)
I expect
“selectUser()”
to be called once
Still no idea.
UserService
User getUser(UserId)
IT DIDN’T GET
CALLED!!! NOTHING
HAPPENED!!!!!
UserService
User getUser(UserId)
IT DIDN’T GET
CALLED!!! NOTHING
HAPPENED!!!!!
Real problem:
The stub configuration was
ou...
Case study #1
Clumsy input
public interface Config {
// Database stuff
String getDatabaseHost();
int getDatabasePort();
int getMaxThreads();
int getC...
public class PotatoService {
public PotatoService(Config config) {
this.potatoVariety = config.getPotatoVariety();
this.ma...
public class PotatoServiceTest {
Config config = mock(Config.class)
@Before
public void before() {
when(config.getDefaultP...
public class PotatoServiceTest {
Config config = mock(Config.class)
@Before
public void before() {
when(config.getDefaultP...
Looks ok. But what is the
stub trying to tell us?
No-ones ever
going to
need all those
things at once.
public interface Config {
// Database stuff
String getDatabaseHost();...
That’s better!
public interface DatabaseConfig {
String getDatabaseHost();
int getDatabasePort();
int getMaxThreads();
int...
public class PotatoService {
public PotatoService(PotatoConfig config) {
this.potatoVariety = config.getPotatoVariety();
t...
public class PotatoService {
public PotatoService(String variety, int max) {
this.potatoVariety = variety;
this.maxPotatoe...
public class PotatoServiceTest {
public testMakeSalad() {
PotatoService service =
new PotatoService(“pontiac”, 33);
Assert...
- More modular
- More reusable
- Simpler
- Less code
- Stubs are gone
Case study #2
Unnecessary mutable
state
public interface Wallet {
int removeCoins(int amount);
int getAmount();
}
public interface VendingMachine {
void insertCoi...
public class CustomerTest {
Wallet wallet = mock(Wallet.class);
VendingMachine machine = mock(VendingMachine.class);
@Befo...
public class CustomerTest {
Wallet wallet = mock(Wallet.class);
VendingMachine machine = mock(VendingMachine.class);
@Befo...
The class under test is
separated now!
But what are the mocks
telling us?
public interface Wallet {
int removeCoins(int amount);
int getAmount();
}
public interface VendingMachine {
void insertCoi...
If the state is just
immutable
values, we don’t
have to force
isolation
public interface Wallet {
int removeCoins(int amou...
public interface Wallet {
int getAmount();
Wallet removeCoins(int amount);
}
public interface VendingMachine {
Optional<Ca...
public interface Wallet {
int getAmount();
Wallet removeCoins(int amount);
}
public interface VendingMachine {
Optional<Ca...
public interface Wallet {
int getAmount();
Wallet removeCoins(int amount);
}
public interface VendingMachine {
Optional<Ca...
public class CustomerTest {
public testBuyDrink() {
Customer c = new Customer(new Wallet(23));
VendingMachine vm = new Ven...
- Less moving parts
- More reusable
- Simpler
- Easier
- Mocks & Stubs are
gone
“But then it’s an
integration test!”
1. Immutable data
structures are just
values.
2. We have no
business peeking at
a method’s tools;
only its results,
effects
3. Pure functions
are already
deterministic
Case study #3
Essential effects
public interface EmailSender {
void sendEmail(String addr, Email email);
}
public class SpecialOffers {
private final Emai...
public class SpecialOffersTest {
EmailSender sender = mock(EmailSender.class)
public testSendEmail() {
SpecialOffers offer...
public class SpecialOffersTest {
EmailSender sender = mock(EmailSender.class)
public testSendEmail() {
SpecialOffers offer...
Ok, so it tests we send an
email.
But what is the mock trying
to tell us?
public interface EmailSender {
void sendEmail(String addr, Email email);
}
public class SpecialOffers {
private final Emai...
public interface SendEmailIntent {
String getAddress();
Email getEmail();
}
public interface Interpreter {
void interpret(...
public class SpecialOffersTest {
public testSendEmail() {
SpecialOffers offers = new SpecialOffers();
SendEmailIntent inte...
- Separated intent
from execution
- More reusable
- Simpler
- Easier
- Mocks are gone
Mocks kill TDD.
TDD = design
methodology
Test-first
encourages you
to design code
well enough to
test…
…and no further.
Mocks & stubs
set a
looooow bar
This totally
guts TDD’s
value for
design.
Conclusion:
Side effects are
the real killer
All I do is make the
input deterministic. If
the input is already
just immutable values,
then you don’t need
me.
Stub Mock
If you’re just using me
because stuff is hard
to create, you need to
get back and design
harder!
Stub Mock
I make output
deterministic, by
recording method
calls instead of
allowing effects.
Stub Mock
Sometimes, this
means that I test a
pointless web of lies,
that doesn’t touch the
code’s reason for
existence.
Stub Mock
Other times, I am really
testing the intent of the
code, which can be pulled
out as its own structure.
This separates the
...
Stub Mock
If you are using
immutable types and
pure functions, then
you’re home and
hosed.
Forget about
• “collaborators”
...
Imagine a world without mocks
Imagine a world without mocks
Imagine a world without mocks
Imagine a world without mocks
Upcoming SlideShare
Loading in …5
×

Imagine a world without mocks

4,542 views

Published on

Mocks and stubs are ubiquitous, yet they are always unnecessary and hurt good design. Here's why, and how to avoid them.

Published in: Software
  • Hello! Get Your Professional Job-Winning Resume Here - Check our website! https://vk.cc/818RFv
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Case study 3 is interesting but 1 and 2 are just make me wonder why the hell you would want to stubb / mock value types in the first place. That's classic mock abuse.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

Imagine a world without mocks

  1. 1. Imagine a world without mocks @KenScambler Scala Developer at
  2. 2. Me 14 years 5 years 5 years when possible when bored when forced
  3. 3. http://techblog.realestate.com.au/to-kill-a-mockingtest/
  4. 4. Q: What’s so bad about mocks & stubs? A: The problem they solve is “how to test poorly designed code”
  5. 5. How did we get here?
  6. 6. UserRepo UserService User getUser(UserId) DB AuthService boolean authUser(UserId) Collaborators galore! Record selectUser(SQL)
  7. 7. UserRepo UserService User getUser(UserId) DB AuthService boolean authUser(UserId) Record selectUser(SQL) How to separate UserService?
  8. 8. 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.
  9. 9. UserService User getUser(UserId) If you ask me “authUser(1234)”, I’ll say “true” Stub UserRepo DB Record selectUser(SQL)
  10. 10. UserService User getUser(UserId) Stub UserRepo Record selectUser(SQL) UserService’s input is now deterministic! If you ask me “authUser(1234)”, I’ll say “true” DB
  11. 11. 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.
  12. 12. UserService User getUser(UserId) I expect “selectUser()” to be called once Mock If you ask me “authUser(1234)”, I’ll say “true”
  13. 13. UserService User 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”
  14. 14. UserService User getUser(UserId) Mock Deterministic output Deterministic input = Fairly sane test
  15. 15. So far so good.
  16. 16. But wait! There’s a cost…
  17. 17. 1. Coupled to brittle implementation details.
  18. 18. UserService User getUser(UserId) Mock What if an equivalent method is called instead? If you ask me “authUser(1234)”, I’ll say “true” authLocalUser(1234) Stub
  19. 19. UserService User getUser(UserId) Ah shit. authLocalUser(1234)
  20. 20. UserService User getUser(UserId) Honestly, no one’s asked me that before. authLocalUser(1234)
  21. 21. UserService User getUser(UserId) I’m just a sock. authLocalUser(1234)
  22. 22. 2. Somewhat misses the point of the test
  23. 23. UserService User getUser(UserId) authLocalUser(1234) I expect “selectUser()” to be called once Still no idea.
  24. 24. UserService User getUser(UserId) IT DIDN’T GET CALLED!!! NOTHING HAPPENED!!!!!
  25. 25. UserService User getUser(UserId) IT DIDN’T GET CALLED!!! NOTHING HAPPENED!!!!! Real problem: The stub configuration was out of date.
  26. 26. Case study #1 Clumsy input
  27. 27. 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(); }
  28. 28. public class PotatoService { public PotatoService(Config config) { this.potatoVariety = config.getPotatoVariety(); this.maxPotatoes = config.getMaxPotatoes(); } public Salad makePotatoSalad() {...} }
  29. 29. 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(), ...); } }
  30. 30. 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
  31. 31. Looks ok. But what is the stub trying to tell us?
  32. 32. 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(); }
  33. 33. 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(); }
  34. 34. 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?
  35. 35. 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.
  36. 36. public class PotatoServiceTest { public testMakeSalad() { PotatoService service = new PotatoService(“pontiac”, 33); Assert.equalTo(service.makeSalad(), ...); } }
  37. 37. - More modular - More reusable - Simpler - Less code - Stubs are gone
  38. 38. Case study #2 Unnecessary mutable state
  39. 39. 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(); }
  40. 40. 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(); } }
  41. 41. 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
  42. 42. The class under test is separated now! But what are the mocks telling us?
  43. 43. 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.
  44. 44. 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(); }
  45. 45. 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); }
  46. 46. 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
  47. 47. 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
  48. 48. 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()); } }
  49. 49. - Less moving parts - More reusable - Simpler - Easier - Mocks & Stubs are gone
  50. 50. “But then it’s an integration test!”
  51. 51. 1. Immutable data structures are just values.
  52. 52. 2. We have no business peeking at a method’s tools; only its results, effects
  53. 53. 3. Pure functions are already deterministic
  54. 54. Case study #3 Essential effects
  55. 55. 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)) } } }
  56. 56. public class SpecialOffersTest { EmailSender sender = mock(EmailSender.class) public testSendEmail() { SpecialOffers offers = new SpecialOffers(sender); offers.sendSpecialOffers( new Customer(false, “Bob”, “foo@foo.com”)); verify(sender).send(“foo@foo.com”, new Email(“Hi, Bob!”)); } }
  57. 57. public class SpecialOffersTest { EmailSender sender = mock(EmailSender.class) public testSendEmail() { SpecialOffers offers = new SpecialOffers(sender); offers.sendSpecialOffers( new Customer(false, “Bob”, “foo@foo.com”)); verify(sender).send(“foo@foo.com”, new Email(“Hi, Bob!”)); } } Mock
  58. 58. Ok, so it tests we send an email. But what is the mock trying to tell us?
  59. 59. 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?
  60. 60. 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.
  61. 61. public class SpecialOffersTest { public testSendEmail() { SpecialOffers offers = new SpecialOffers(); SendEmailIntent intent = offers.sendSpecialOffers( new Customer(false, “Bob”, “foo@foo.com”)).get(); Assert.equals(intent.getAddress(), “foo@foo.com”); Assert.equals(intent.getEmail().getText(), “Hi, Bob!”); } }
  62. 62. - Separated intent from execution - More reusable - Simpler - Easier - Mocks are gone
  63. 63. Mocks kill TDD.
  64. 64. TDD = design methodology
  65. 65. Test-first encourages you to design code well enough to test…
  66. 66. …and no further.
  67. 67. Mocks & stubs set a looooow bar
  68. 68. This totally guts TDD’s value for design.
  69. 69. Conclusion: Side effects are the real killer
  70. 70. All I do is make the input deterministic. If the input is already just immutable values, then you don’t need me. Stub Mock
  71. 71. If you’re just using me because stuff is hard to create, you need to get back and design harder! Stub Mock
  72. 72. I make output deterministic, by recording method calls instead of allowing effects. Stub Mock
  73. 73. Sometimes, this means that I test a pointless web of lies, that doesn’t touch the code’s reason for existence. Stub Mock
  74. 74. 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
  75. 75. 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”.

×