using tests and mocks to drive the design of software

63
Test Driven Design Using tests and mocks to drive the design of software Attila Magyar Microsec Plc 2012

Upload: attila-magyar

Post on 07-May-2015

747 views

Category:

Entertainment & Humor


1 download

TRANSCRIPT

Page 1: Using tests and mocks to drive the design of software

Test Driven DesignUsing tests and mocks to drive the design of software

Attila MagyarMicrosec Plc

2012

Page 2: Using tests and mocks to drive the design of software

● End to End– Real environment

● Integration– Against 3rd party API

● Unit– Isolated

Page 3: Using tests and mocks to drive the design of software

Feedback

Unit test Integration test End to end test0

2

4

6

8

10

12

Feed

back

abo

ut Q

ualit

y

Growing object-oriented software guided by tests: Steve Freeman, Nat Pryce

Page 4: Using tests and mocks to drive the design of software

Feedback

Unit test Integration test End to end test0

2

4

6

8

10

12External Quality

Feed

back

abo

ut Q

ualit

y

Growing object-oriented software guided by tests: Steve Freeman, Nat Pryce

Page 5: Using tests and mocks to drive the design of software

Feedback

Unit test Integration test End to end test0

2

4

6

8

10

12External Quality

Feed

back

abo

ut Q

ualit

y

Growing object-oriented software guided by tests: Steve Freeman, Nat Pryce

Page 6: Using tests and mocks to drive the design of software

Feedback

Unit test Integration test End to end test0

2

4

6

8

10

12External Quality

Feed

back

abo

ut Q

ualit

y

Growing object-oriented software guided by tests: Steve Freeman, Nat Pryce

Page 7: Using tests and mocks to drive the design of software

Feedback

Unit test Integration test End to end test0

2

4

6

8

10

12External Quality

Feed

back

abo

ut Q

ualit

y

Growing object-oriented software guided by tests: Steve Freeman, Nat Pryce

Page 8: Using tests and mocks to drive the design of software

Feedback

Unit test Integration test End to end test0

2

4

6

8

10

12External QualityCode Quality

Feed

back

abo

ut Q

ualit

y

Growing object-oriented software guided by tests: Steve Freeman, Nat Pryce

Bad design: ● Rigidity● Fragility● Immobility

Page 9: Using tests and mocks to drive the design of software

Feedback

Unit test Integration test End to end test0

2

4

6

8

10

12External QualityCode Quality

Feed

back

abo

ut Q

ualit

y

Growing object-oriented software guided by tests: Steve Freeman, Nat Pryce

Bad design: ● Rigidity● Fragility● Immobility

Page 10: Using tests and mocks to drive the design of software

Feedback

Unit test Integration test End to end test0

2

4

6

8

10

12External QualityCode Quality

Feed

back

abo

ut Q

ualit

y

Growing object-oriented software guided by tests: Steve Freeman, Nat Pryce

Bad design: ● Rigidity● Fragility● Immobility

Page 11: Using tests and mocks to drive the design of software

End to End vs Unit tests

● End to End:● Slow execution and feedback● Sometimes unreliable● Difficult and time-consuming to

write● Difficult to localize bugs● Verifies configuration, integration,

environment

● Unit tests:● Fast execution and feedback● Always deterministic● Easy to write and automate● Easy to localize bugs● Verifies basic correctness

Page 12: Using tests and mocks to drive the design of software

Pyramid

Unit

Integration

End to end

Page 13: Using tests and mocks to drive the design of software

Unit tests:state based testing

// Testlight.switchOn();assertTrue(light.isOn());

light.switchOff();assertFalse(light.isOn())

public class Light { private boolean on; public void switchOn(){ on = true; } public void switchOff(){ on = false; } public boolean isOn(){ return on; }}

Page 14: Using tests and mocks to drive the design of software

Ticket Machineclass TrainTicketMachine implements TicketMachine { private List<Integer> pressedButtons = new ArrayList<Integer>(); [...] public void press(int buttonNumber) { pressedButtons.add(buttonNumber); } public void enter() { ticketReserver.reserve(new ArrayList<Integer>(pressedButtons)); }}

Page 15: Using tests and mocks to drive the design of software

Ticket Machine

Ticket Reserver

Message: reserve([1,2,3])

Page 16: Using tests and mocks to drive the design of software

Ticket Machine

Ticket Reserver

Ticket Persister

public interface TicketReserver { void reserve(List<Integer> ticketCode);}

DB

Page 17: Using tests and mocks to drive the design of software

Ticket Machine

Ticket Reserver

Messages=[reserve(1,2,3), reserve(4,5,6)]

Has received 123?

Page 18: Using tests and mocks to drive the design of software

@Testpublic void reservesTicketUsingEnteredCodes() { // Given Spy spy = new Spy(); ticketMachine = new TrainTicketMachine(spy); // When ticketMachine.press(1); ticketMachine.press(2); ticketMachine.enter(); // Then spy.assertReceived(Arrays.asList(1, 2));}

public class Spy implements TicketReserver { private List<Integer> received = new ArrayList<Integer>(); @Override public void reserve(List<Integer> codes) { received.addAll(codes); } public void assertReceived(List<Integer> codes) { assertThat(received, equalTo(codes)); }}

Page 19: Using tests and mocks to drive the design of software

@RunWith(MockitoJUnitRunner.class)public class TrainTickerReserverTest { TrainTicketMachine ticketMachine; @Mock TicketReserver reserver; @Test public void reservesTicketUsingEnteredCodes() { // Given ticketMachine = new TrainTicketMachine(reserver); // When ticketMachine.press(1); ticketMachine.press(2); ticketMachine.enter(); // Then verify(reserver).reserve(Arrays.asList(1, 2)); }}@RunWith(JMock.class)public class TrainTickerReserverTest { @Mock TicketReserver reserver; Mockery context = new JUnit4Mockery(); TrainTicketMachine ticketMachine; @Test public void reservesTicketUsingEnteredCodes() { context.checking(new Expectations() {{ oneOf(reserver).reserve(Arrays.asList(1, 2)); }}); ticketMachine = new TrainTicketMachine(reserver); ticketMachine.press(1); ticketMachine.press(2); ticketMachine.enter(); }}

(spyito)

Page 20: Using tests and mocks to drive the design of software

Test smells

Page 21: Using tests and mocks to drive the design of software

class TrainTicketMachine implements TicketMachine { private List<Integer> pressedButtons = new ArrayList<Integer>(); public void press(int buttonNumber) { pressedButtons.add(buttonNumber); } public void enter() { TicketReserver.reserve(new ArrayList<Integer>(pressedButtons)); }}

Test smells

Page 22: Using tests and mocks to drive the design of software

class TrainTicketMachine implements TicketMachine { private List<Integer> pressedButtons = new ArrayList<Integer>(); public void press(int buttonNumber) { pressedButtons.add(buttonNumber); } public void enter() { TicketReserver.reserve(new ArrayList<Integer>(pressedButtons)); }}

class TrainTicketMachine implements TicketMachine { private List<Integer> pressedButtons = new ArrayList<Integer>(); public void press(int buttonNumber) { pressedButtons.add(buttonNumber); } public void enter() { TicketReserver.getInstance().reserve(new ArrayList<Integer>(pressedButtons)); }}

Test smells

Page 23: Using tests and mocks to drive the design of software

class TrainTicketMachine implements TicketMachine { private List<Integer> pressedButtons = new ArrayList<Integer>(); public void press(int buttonNumber) { pressedButtons.add(buttonNumber); } public void enter() { TicketReserver.reserve(new ArrayList<Integer>(pressedButtons)); }}

class TrainTicketMachine implements TicketMachine { private List<Integer> pressedButtons = new ArrayList<Integer>(); public void press(int buttonNumber) { pressedButtons.add(buttonNumber); } public void enter() { TicketReserver.getInstance().reserve(new ArrayList<Integer>(pressedButtons)); }}

class TrainTicketMachine implements TicketMachine { private List<Integer> pressedButtons = new ArrayList<Integer>(); private TicketReserver ticketReserver = new TicketReserver(); public void press(int buttonNumber) { pressedButtons.add(buttonNumber); } public void enter() { ticketReserver.reserve(new ArrayList<Integer>(pressedButtons)); }}

Test smells

Page 24: Using tests and mocks to drive the design of software

class TrainTicketMachine implements TicketMachine { private List<Integer> pressedButtons = new ArrayList<Integer>(); public void press(int buttonNumber) { pressedButtons.add(buttonNumber); } public void enter() { TicketReserver.reserve(new ArrayList<Integer>(pressedButtons)); }}

class TrainTicketMachine implements TicketMachine { private List<Integer> pressedButtons = new ArrayList<Integer>(); public void press(int buttonNumber) { pressedButtons.add(buttonNumber); } public void enter() { TicketReserver.getInstance().reserve(new ArrayList<Integer>(pressedButtons)); }}

class TrainTicketMachine implements TicketMachine { private List<Integer> pressedButtons = new ArrayList<Integer>(); private TicketReserver ticketReserver = new TicketReserver(); public void press(int buttonNumber) { pressedButtons.add(buttonNumber); } public void enter() { ticketReserver.reserve(new ArrayList<Integer>(pressedButtons)); }}

Test smells

Page 25: Using tests and mocks to drive the design of software

class TrainTicketMachine implements TicketMachine { private List<Integer> pressedButtons = new ArrayList<Integer>(); private TicketReserver ticketReserver; public TrainTicketMachine(TicketReserver ticketReserver) { this.ticketReserver = ticketReserver; } public void press(int buttonNumber) { pressedButtons.add(buttonNumber); } public void enter() { ticketReserver.reserve(new ArrayList<Integer>(pressedButtons)); }}

Solution

Page 26: Using tests and mocks to drive the design of software

Copy component

Page 27: Using tests and mocks to drive the design of software

Dependency inversion

Page 28: Using tests and mocks to drive the design of software

The Dependency Inversion Principle, Robert C. Martin, C++ Report, May 1996

Page 29: Using tests and mocks to drive the design of software

Test smellsPrivate method access

Page 30: Using tests and mocks to drive the design of software

Test smellsPrivate method access

Page 31: Using tests and mocks to drive the design of software

Test smellsPrivate method access

Principles of Object Oriented Design, Robert C. Martin, 2003

Page 32: Using tests and mocks to drive the design of software

Test smellsExcessive test setup

Page 33: Using tests and mocks to drive the design of software

• SRP violation• Too much coupling

• (No isolation)

Test smellsExcessive test setup

Page 34: Using tests and mocks to drive the design of software

Test smellsDifficult mocking

(deep stubbing)

Page 35: Using tests and mocks to drive the design of software

• Law of demeter violation

Test smellsDifficult mocking

(deep stubbing)

Northeastern University, 1987

Page 36: Using tests and mocks to drive the design of software

dog.getTail().wag()

Test smellsDifficult mocking

Page 37: Using tests and mocks to drive the design of software

dog.expressHappiness()

Test smellsDifficult mocking

Page 38: Using tests and mocks to drive the design of software

Test smellsAccessing local variable

Page 39: Using tests and mocks to drive the design of software

• SRP violation• Method is too long

Test smellsAccessing local variable

Page 40: Using tests and mocks to drive the design of software

Test smellsDupplication in test and production

Page 41: Using tests and mocks to drive the design of software

• Missing abstraction• Mocking 3rd party components

Test smellsDupplication in test and production

Page 42: Using tests and mocks to drive the design of software

3rd party API mockingpublic class Greetings { [...] public void greetUsers() throws SQLException { Statement stmt = connection.createStatement(); sayHelloTo(stmt.executeQuery("select name from users where type=1 or type=2")); }}

@Test public void saysHelloToUsers() throws SQLException { when(conn.createStatement()).thenReturn(stmt); when(stmt.executeQuery("select name from users where type=1 or type=2")).thenReturn(users); movies.greetUsers(); [...]}

Page 43: Using tests and mocks to drive the design of software

public class Greetings { [...] public void greetUsers() throws SQLException { Statement stmt = connection.createStatement(); sayHelloTo(stmt.executeQuery("select name from users where type=1 or type=2")); }}

@Test public void saysHelloToUsers() throws SQLException { when(conn.createStatement()).thenReturn(stmt); when(stmt.executeQuery("select name from users where type=1 or type=2")).thenReturn(users); movies.greetUsers(); [...]}

Dup

licat

ion

3rd party API mocking

Page 44: Using tests and mocks to drive the design of software

Test smellsToo many changes in test code

Page 45: Using tests and mocks to drive the design of software

Test smellsToo many changes in test code

Meyer, Bertrand (1988). Object-Oriented Software Construction.

Page 46: Using tests and mocks to drive the design of software

Test smellsToo many dependencies

Page 47: Using tests and mocks to drive the design of software

● SRP violation● Missing abstraction

Test smellsToo many dependencies

Page 48: Using tests and mocks to drive the design of software

Test smellsDifficult to instantiate SUT

Page 49: Using tests and mocks to drive the design of software

● Hidden dependency (e.g.: Singleton)

● Insufficient domain separation

Test smellsDifficult to instantiate SUT

Page 50: Using tests and mocks to drive the design of software

Adapter

BL

Adapter

BL

Adapter

BL

msg

3rd party

3rd party3rd party

Application domain

Domain of the outside worldObject must send messages to it peers in terms of its domain language.

Page 51: Using tests and mocks to drive the design of software

Adapter

BL

Adapter

BL

Adapter

BL

msg

3rd party

3rd party3rd party

Application domain

Domain of the outside world

Application wiring- „new” and „set”

- Main method- Spring/Guice- XML/Annotations

Page 52: Using tests and mocks to drive the design of software

MOCK

SUT

Adapter

MOCK

MOCK

BL

3rd party

3rd party3rd party

Application domain

Domain of the outside world

Application wiring- „new” and „set”

Unit test

- Main method- Spring/Guice- XML/Annotations

Page 53: Using tests and mocks to drive the design of software

SUT

BL

Adapter

BL

Adapter

BL

3rd party

Real3rd party

Application domain

Domain of the outside world

Application wiring- „new” and „set”

Integration

- Main method- Spring/Guice- XML/Annotations

Page 54: Using tests and mocks to drive the design of software

Adapter

BL

Adapter

BL

Adapter

BL

3rd party

3rd party3rd party

Application domain

Domain of the outside world

Application wiring- „new” and „set”End to end test

- Main method- Spring/Guice- XML/Annotations

Page 55: Using tests and mocks to drive the design of software

TDD

Page 56: Using tests and mocks to drive the design of software

How does it work?

● Write a failing test for A

A

Page 57: Using tests and mocks to drive the design of software

How does it work?

● Write a failing test for A

● Introduce the interface of a collaborator B

A BInterface discovery

Page 58: Using tests and mocks to drive the design of software

How does it work?

● Write a failing test for A

● Introduce the interface of a collaborator B

● Mock the interface● Use the mock to

Finish A

A BInterface discoveryInterface discoveryInterface discovery

Page 59: Using tests and mocks to drive the design of software

How does it work?

● Write a failing test for A

● Introduce the interface of a B

● Mock the interface● Use the mock to

finish A

A B

● Write a failing test for B

● Introduce the interface of C

● Mock the interface● "Ensure contract

compliance" between A and B

● Use the mock to

finish B

Interface discovery CInterface discovery

● C is an adapter● Use integration

test for this

Page 60: Using tests and mocks to drive the design of software

Benefits● Early design feedback (SRP, DIP, OCP, etc..)● Mocks encourage the use of „Tell Don't Ask” principle

→ Well encapsulated code● Outside-In approach

● Simpler interface, (and implementation)● No dead code

● Less debugging● More coverage →

● Higher confidence in code and refactoring● Less post-release bugs

Page 61: Using tests and mocks to drive the design of software

DEMO

Cashier service:● Reads barcodes● Queries prices (REST)● Prints receipts

● Commands:– „Command: NewSale”– „Command: EndSale”– „Input: barcode=100008888559”

Page 62: Using tests and mocks to drive the design of software

Command Translator

Sale S

tarted

CashRegister

Product Entered

Sale E

nded

PrinterpriceCalculated

productPrice

RESTCatalog

Money

ReceiptReceiver

ProductCatalog

SaleEventListener

Product

Barcode

Jersey/ApacheHttp client

java.awt.print

CommandListener

Command

Page 63: Using tests and mocks to drive the design of software

References● Growing Object-Oriented Software Guided by Tests

● Steve Freeman, Nat Pryce ● Why You Don't Get Mock Objects

● Gregory Moeck, rubyconf 2011● Using Mocks And Tests To Design Role-Based Objects

● Isaiah Perumalla ● Mock Roles, not Objects

● Steve Freeman, Nat Pryce, Tim Mackinnon, Joe Walnes, High Holborn ● The Deep Synergy Between Testability and Good Design

● Michael Feathers● Surely the Mars Rover Needed Integrated Tests! (Maybe Not?)

● J. B. Rainsberger● Joey Devilla SOLID principle posters