designing testable software

79
Designing Testable Software OpKoKo 16.2 Ekerö October 21, 2016 @DanielDeogun

Upload: omegapoint-academy

Post on 18-Jan-2017

18 views

Category:

Software


1 download

TRANSCRIPT

Page 1: Designing Testable Software

Designing Testable SoftwareOpKoKo 16.2

Ekerö October 21, 2016

@DanielDeogun

Page 2: Designing Testable Software

@DanielDeogun

About Me

Daniel Deogun Coder and Quality Defender

- VP Academy, Core 3, Stockholm

- Current assignment Hi3G / Nordic Choice Hotels

- Speaker, Teacher, Lead Developer

- Author of Secure by Design, Manning publ (in progress)

- Interests: DDD, DDSec, TDD, BDD, DbC, …

Page 3: Designing Testable Software

@DanielDeogun

Conclusion

- Put the test hat on when designing a system

- Testing is quite hard and require a lot of good design

- You get pretty far by thinking test first

Page 4: Designing Testable Software

@DanielDeogun

The Spec of the New “System”

Easy to add / remove / update functionality

Have same functionality as the “old” system

Must be resilient

Easy to maintain

Page 5: Designing Testable Software

@DanielDeogun

Where do you begin?

[1]

Page 6: Designing Testable Software

@DanielDeogun

Test Hat On

Let’s put the test hat on

and see what we need…[2]

Page 7: Designing Testable Software

@DanielDeogun

Requirement

Have same functionality as the “old” system

Page 8: Designing Testable Software

@DanielDeogun

Requirement

How do we verify this?

Have same functionality as the “old” system

Page 9: Designing Testable Software

@DanielDeogun

Requirement

How do we verify this?

Is there any documentation?

Have same functionality as the “old” system

Page 10: Designing Testable Software

@DanielDeogun

Requirement

How do we verify this?

Is there any documentation?

How do we report progress?

Have same functionality as the “old” system

Page 11: Designing Testable Software

@DanielDeogun

Requirement

How do we verify this?

Is there any documentation?

Is there a domain expert?How do we report progress?

Have same functionality as the “old” system

Page 12: Designing Testable Software

@DanielDeogun

Requirement

How do we verify this?

Is there any documentation?

Is there a domain expert?

Do we want “exactly” the same behavior?

How do we report progress?

Have same functionality as the “old” system

Page 13: Designing Testable Software

@DanielDeogun

Behavior Driven Development in a Nutshell

BDD tries to capture the behavior of a system, not how it’s implemented

Page 14: Designing Testable Software

@DanielDeogun

BDD in Practice

Given … When … Then …

scenarios

produce

Gherkin notation

Page 15: Designing Testable Software

@DanielDeogun

BDD in Practice Scenarios Act As a Client

Given … When … Then …

public class Scenario {

public void given() {…}

public void when() {…}

public void then() {…}

}

convert to

executable test acting as a client

invokes

application

Page 16: Designing Testable Software

@DanielDeogun

Application Behavior Captured by Scenario Tests

BDD Scenarios

application

Page 17: Designing Testable Software

@DanielDeogun

Application Behavior Captured by Scenario Tests

BDD Scenarios

But what about dependencies to other systems?

invokes

application

Page 18: Designing Testable Software

@DanielDeogun

Application Behavior Captured by Scenario Tests

BDD Scenarios

But what about dependencies to other systems?

invokes

application

Let’s have a look at 3 important principles…

Page 19: Designing Testable Software

@DanielDeogun

Dependency Inversion Principle (DIP)

High-level modules should not depend on low-level modules. Both should depend on abstractions.

Abstractions should not depend on details. Details should depend on abstractions.

- https://en.wikipedia.org/wiki/Dependency_inversion_principle

Page 20: Designing Testable Software

@DanielDeogun

Dependency Inversion Principle (DIP)

Dependency

Dependency implementation

abstraction

Page 21: Designing Testable Software

@DanielDeogun

Dependency Injection (DI) Explained

public class Monkey {private final Banana banana;

public Monkey() { this.banana = new Banana();}…

}

Monkey depends on Banana but the Monkey creates the banana.

public class Monkey {private final Banana banana;

public Monkey(final Banana banana) { this.banana = notNull(banana);}…

}

Monkey depends on Banana but banana is given (injected) to Monkey

Page 22: Designing Testable Software

@DanielDeogun

Liskov’s Substitution Principle

Let ϕ(x) be a property provable about objects x of type T. Then ϕ(y) should be true for objects y of type S where S is a subtype of T.

- Barbara Liskov and Jeannette Wing [3]

Page 23: Designing Testable Software

@DanielDeogun

LSP - “For Dummies”

If C is a subtype of P, then objects of type P may be replaced by objects of type C without violating behavior or invariants

- Common Sense

class C extends P {…}

final P p = new C();

Page 24: Designing Testable Software

@DanielDeogun

The Beautiful Trinity

DIP + DI + LSP = True

[6]

Page 25: Designing Testable Software

@DanielDeogun

DIP, DI, and LSP Allows Isolated Testing

Let’s use DI to inject dependencies

Let’s use DIP to remove dependencies to low level implementations

Let’s use LSP to ensure invariants and behavior

dependency

abstraction

Page 26: Designing Testable Software

@DanielDeogun

Pros & Cons BDD & Spec by Example

+ Easy to verify business rules and expected behavior + A good way to learn how the system works + Creates confidence

- Deciding what to test may be hard - Feature driven scenario organization may create duplicated tests - Requires deep domain knowledge

Page 27: Designing Testable Software

@DanielDeogun

Evaluation

How do we verify this?

Is there any documentation?

Is there a domain expert?

Do we want “exactly” the same behavior?

How do we report progress?

Have same functionality as the “old” system

Page 28: Designing Testable Software

@DanielDeogun

Evaluation

How do we verify this?

Is there any documentation?

Is there a domain expert?

Do we want “exactly” the same behavior?

How do we report progress?

Have same functionality as the “old” system

Page 29: Designing Testable Software

@DanielDeogun

Evaluation

How do we verify this?

Is there any documentation?

Is there a domain expert?

Do we want “exactly” the same behavior?

How do we report progress?

√?Have same functionality as the “old” system

Page 30: Designing Testable Software

@DanielDeogun

Evaluation

How do we verify this?

Is there any documentation?

Is there a domain expert?

Do we want “exactly” the same behavior?

How do we report progress?

√?Have same functionality as the “old” system

Page 31: Designing Testable Software

@DanielDeogun

Evaluation

How do we verify this?

Is there any documentation?

Is there a domain expert?

Do we want “exactly” the same behavior?

How do we report progress?

√?√

√?Have same functionality as the “old” system

Page 32: Designing Testable Software

@DanielDeogun

Evaluation

How do we verify this?

Is there any documentation?

Is there a domain expert?

Do we want “exactly” the same behavior?

How do we report progress?

?√

√?Have same functionality as the “old” system

Page 33: Designing Testable Software

@DanielDeogun

Requirement

Easy to add / remove / update functionality

Easy to maintain

Page 34: Designing Testable Software

@DanielDeogun

Requirement

Easy to add / remove / update functionality

How do we avoid breaking things?

Easy to maintain

Page 35: Designing Testable Software

@DanielDeogun

Requirement

Easy to add / remove / update functionality

How do we avoid breaking things?

How do we design for change?

Easy to maintain

Page 36: Designing Testable Software

@DanielDeogun

Requirement

Easy to add / remove / update functionality

How do we avoid breaking things?

How do we design for change?

Easy to maintain

What does easy to maintain mean?

Page 37: Designing Testable Software

@DanielDeogun

Test Driven Development

RED

GREEN

REFACTOR

Prove need

Smallest code change

Improve

TDD tend to focus on how something is implemented rather than its behavior

A good practice is to use BDD when doing TDD

Page 38: Designing Testable Software

@DanielDeogun

TDD Á la BDD

public void test_ticker() {Ticker ticker = new Ticker();

ticker.increment();

assertEquals(1, ticker.value());}

TDD

Page 39: Designing Testable Software

@DanielDeogun

TDD Á la BDD

public void test_ticker() {Ticker ticker = new Ticker();

ticker.increment();

assertEquals(1, ticker.value());}

TDDpublic void should_increment_ticker() {

givenTickerWithRandomStart();

int expectedValue = ticker.value() + ticker.incrementStep();

ticker.increment();

thenTickerIsIncrementedTo(expectedValue);}

TDD á la BDD

Page 40: Designing Testable Software

@DanielDeogun

TDD Á la BDD

public void test_ticker() {Ticker ticker = new Ticker();

ticker.increment();

assertEquals(1, ticker.value());}

!- Never verify more than one behavior in each test - Don’t use a common set up method

TDDpublic void should_increment_ticker() {

givenTickerWithRandomStart();

int expectedValue = ticker.value() + ticker.incrementStep();

ticker.increment();

thenTickerIsIncrementedTo(expectedValue);}

TDD á la BDD

Page 41: Designing Testable Software

@DanielDeogun

Mocks

“…mock objects are simulated objects that mimic the behavior of real objects in controlled ways. A programmer typically creates a mock object to test the behavior of some other object, …”

https://en.wikipedia.org/wiki/Mock_object

Page 42: Designing Testable Software

@DanielDeogun

Use DI to Inject Mocksprivate final List<Item> items = mock(List.class); private final ShoppingCart shoppingCart = new ShoppingCart(items);

public class ShoppingCart { private final List<Item> items; public ShoppingCart(final List<Item> items) { this.items = notNull(items); } public boolean add(final Item item) { return items.add(notNull(item)); } public boolean remove(final Item item) { return items.remove(notNull(item)); } …

Page 43: Designing Testable Software

@DanielDeogun

The Great Frustration

Incorrect mocking makes you spend more time updating test code than production code

And the reason is…

Page 44: Designing Testable Software

@DanielDeogun

The Great Frustration

Incorrect mocking makes you spend more time updating test code than production code

And the reason is…

Nobody puts Barbra in the corner!

Page 45: Designing Testable Software

@DanielDeogun

Violating LSP Yield Brittle Tests

private final List<Item> items = mock(List.class); private final ShoppingCart shoppingCart = new ShoppingCart(items);

public class ShoppingCart { private final List<Item> items; public ShoppingCart(final List<Item> items) { this.items = notNull(items); } public boolean add(final Item item) { return items.add(notNull(item)); } public boolean remove(final Item item) { return items.remove(notNull(item)); } …

Page 46: Designing Testable Software

@DanielDeogun

Violating LSP Yield Brittle Tests

@Test public void should_remove_item() { final Item coke = new Coke(); BDDMockito.given(items.remove(coke)).willReturn(true); final boolean result = shoppingCart.remove(coke); assertTrue(result); }

public class ShoppingCart { private final List<Item> items; public ShoppingCart(final List<Item> items) { this.items = notNull(items); } public boolean remove(final Item item) {

return items.remove(notNull(item)); } …

Page 47: Designing Testable Software

@DanielDeogun

Violating LSP Yield Brittle Tests

@Test public void should_remove_item() { final Item coke = new Coke(); BDDMockito.given(items.remove(coke)).willReturn(true); final boolean result = shoppingCart.remove(coke); assertTrue(result); }

public class ShoppingCart { private final List<Item> items; public ShoppingCart(final List<Item> items) { this.items = notNull(items); } public boolean remove(final Item item) {

return items.remove(notNull(item)); } …

public class ShoppingCart { private final List<Item> items; public ShoppingCart(final List<Item> items) { this.items = notNull(items); } public boolean remove(final Item item) { return items.removeAll(items.stream() .filter(i -> i.equals(item)) .collect(toList())); }

Page 48: Designing Testable Software

@DanielDeogun

Violating LSP Yield Brittle Tests

@Test public void should_remove_item() { final Item coke = new Coke(); BDDMockito.given(items.remove(coke)).willReturn(true); final boolean result = shoppingCart.remove(coke); assertTrue(result); }

public class ShoppingCart { private final List<Item> items; public ShoppingCart(final List<Item> items) { this.items = notNull(items); } public boolean remove(final Item item) {

return items.remove(notNull(item)); } …

public class ShoppingCart { private final List<Item> items; public ShoppingCart(final List<Item> items) { this.items = notNull(items); } public boolean remove(final Item item) { return items.removeAll(items.stream() .filter(i -> i.equals(item)) .collect(toList())); }

java.lang.NullPointerException

Page 49: Designing Testable Software

@DanielDeogun

Analysis

The “items” mock doesn’t satisfy LSP

Invoking an unmocked method on the items object causes a failure

Beware: Mockito uses default mocking behavior on some types (e.g. List, int, boolean, etc)

private final List<Item> items = mock(List.class); private final ShoppingCart shoppingCart = new ShoppingCart(items);

Page 50: Designing Testable Software

@DanielDeogun

Tip of the Day

Make domain classes final to “prevent“ mocking![8]

org.mockito.exceptions.base.MockitoException: Cannot mock/spy class se.omegapoint.opkoko.MyDomainClassMockito cannot mock/spy following: - final classes - anonymous classes - primitive types

Page 51: Designing Testable Software

@DanielDeogun

Branch By Code Using Feature Toggles

http://martinfowler.com/articles/feature-toggles.html

Page 52: Designing Testable Software

@DanielDeogun

Branch By Code Using Feature Toggles

Release toggles • Compile time dependency • Ex: use DI and start app with different configuration

Ops toggles • Runtime dependency • Ex: special API only available for operations

Experiment toggles • Runtime dependency used for A/B testing • Ex: toggle is based on user information

Permission toggles • Runtime dependency • Ex: Toggle is based on payment information

Page 53: Designing Testable Software

@DanielDeogun

How Do Feature Toggles Affect Testing?

Release toggles • test what’s enabled (in production)

Ops toggles • make sure they only work for operations

Experiment toggles • test the selection algorithm

Permission toggles • test applicability

We cannot test every possible combination

Make an educated choice

Page 54: Designing Testable Software

@DanielDeogun

Evaluation

Easy to add / remove / update functionality

How do we avoid breaking things?

How do we design for change?

Easy to maintain

What does easy to maintain mean?

Page 55: Designing Testable Software

@DanielDeogun

Evaluation

Easy to add / remove / update functionality

How do we avoid breaking things?

How do we design for change?

Easy to maintain

What does easy to maintain mean?

Page 56: Designing Testable Software

@DanielDeogun

Evaluation

Easy to add / remove / update functionality

How do we avoid breaking things?

How do we design for change?

Easy to maintain

What does easy to maintain mean?

Page 57: Designing Testable Software

@DanielDeogun

Evaluation

Easy to add / remove / update functionality

How do we avoid breaking things?

How do we design for change?

Easy to maintain

What does easy to maintain mean?

√√?

Page 58: Designing Testable Software

@DanielDeogun

Requirement

Must be resilient

Page 59: Designing Testable Software

@DanielDeogun

Requirement

How do we measure “resilience?”

Must be resilient

Page 60: Designing Testable Software

@DanielDeogun

Requirement

How do we measure “resilience?”

How do we test resilience?

Must be resilient

Page 61: Designing Testable Software

@DanielDeogun

Requirement

How do we measure “resilience?”

How do we test resilience?

Can we find the “upper bound?”

Must be resilient

Page 62: Designing Testable Software

@DanielDeogun

System Integration Resilience

Page 63: Designing Testable Software

@DanielDeogun

Circuit Breaker

“A circuit breaker is an automatically operated electrical switch designed to protect an electrical circuit from damage caused by overcurrent or overload or short circuit.”

- https://en.wikipedia.org/wiki/Circuit_breaker

Page 64: Designing Testable Software

@DanielDeogun

Circuit Breakers Avoid Killing Backend

circuit breakers

Page 65: Designing Testable Software

@DanielDeogun

Schematic view of a Circuit Breaker

- Release It! Michael Nygard, The Pragmatic Bookshelf

Page 66: Designing Testable Software

@DanielDeogun

Bulkhead Pattern

https://github.com/Netflix/Hystrix/wiki/How-it-Works#Threads

[9]

Page 67: Designing Testable Software

@DanielDeogun

Separate Thread Pools Avoid Cascading Failures

circuit breakers

Separate thread pools

Page 68: Designing Testable Software

@DanielDeogun

Request Collapsing Pattern

https://github.com/Netflix/Hystrix/wiki/How-it-Works#RequestCollapsing

Page 69: Designing Testable Software

@DanielDeogun

“Internal” Resilience

[5]

Bad input data

Corrupt responses

Design by Contract

Message driven

Domain Driven DesignNull values

Default values

ImmutabilityConcurrency

Page 70: Designing Testable Software

@DanielDeogun

Hostile Environment

Imagine an environment whose only purpose is to bring your application to its knees

Each endpoint is a potential “threat”

Put your application under heavy load, inject bad input, return corrupt data, etc

Make this a stage in your delivery pipeline

Monitor memory consumption, response times, etc

Page 71: Designing Testable Software

@DanielDeogun

Evaluation

How do we measure “resilience?”

How do we test resilience?

Must be resilient

Can we find the “upper bound?”

Page 72: Designing Testable Software

@DanielDeogun

Evaluation

How do we measure “resilience?”

How do we test resilience?

Must be resilient

Can we find the “upper bound?”

√?

Page 73: Designing Testable Software

@DanielDeogun

Evaluation

How do we measure “resilience?”

How do we test resilience?

Must be resilient

Can we find the “upper bound?”

√?

Page 74: Designing Testable Software

@DanielDeogun

Evaluation

How do we measure “resilience?”

How do we test resilience?

Must be resilient

√Can we find the “upper bound?”

√?

Page 75: Designing Testable Software

@DanielDeogun

The Spec of the New “System”

Easy to add / remove / update functionality

Have same functionality as the “old” system

Must be resilient

Easy to maintain

Page 76: Designing Testable Software

@DanielDeogun

Conclusion

- Put the test hat on when designing a system

- Testing is quite hard and require a lot of good design

- You get pretty far by thinking test first

Page 77: Designing Testable Software

@DanielDeogun

Q & A

[7]

Page 78: Designing Testable Software

@DanielDeogun

Thanks@DanielDeogun

Page 79: Designing Testable Software

@DanielDeogun

References[1] Torsten, math teacher, https://flic.kr/p/ndFN4Q License: https://creativecommons.org/licenses/by/2.0/

[2] Emily Moe, IMG_7788, https://flic.kr/p/aH4rwk, License: https://creativecommons.org/licenses/by-nd/2.0/

[3] Liskov’s Substitution Principle, Wikipedia, https://en.wikipedia.org/wiki/Liskov_substitution_principle

[4] Tsutomu Takasu, Horse racing event, https://flic.kr/p/6Yy3NZ, License: https://creativecommons.org/licenses/by/2.0/

[5] http://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Emblem-evil-computer.svg/500px-Emblem-evil-computer.svg.png

[6] Huey, Dewey, and Louie Duck, drawn by cartoonist Carl Barks, https://en.wikipedia.org/wiki/File:Louie_Dewey_and_Huey.png

[7] Questions, https://flic.kr/p/9ksxQa] by Damián Navas, License: https://creativecommons.org/licenses/by-nc-nd/2.0/

[8] Chuck Coker, Light Bulb No. 1, https://flic.kr/p/66KLFn, License: https://creativecommons.org/licenses/by-nd/2.0/

[9] DRVMX, Titantic, https://flic.kr/p/gNLK84, License: https://creativecommons.org/licenses/by-nd/2.0/