designing testable software
TRANSCRIPT
Designing Testable SoftwareOpKoKo 16.2
Ekerö October 21, 2016
@DanielDeogun
@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, …
@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
@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
@DanielDeogun
Where do you begin?
[1]
@DanielDeogun
Test Hat On
Let’s put the test hat on
and see what we need…[2]
@DanielDeogun
Requirement
Have same functionality as the “old” system
@DanielDeogun
Requirement
How do we verify this?
Have same functionality as the “old” system
@DanielDeogun
Requirement
How do we verify this?
Is there any documentation?
Have same functionality as the “old” system
@DanielDeogun
Requirement
How do we verify this?
Is there any documentation?
How do we report progress?
Have same functionality as the “old” system
@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
@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
@DanielDeogun
Behavior Driven Development in a Nutshell
BDD tries to capture the behavior of a system, not how it’s implemented
@DanielDeogun
BDD in Practice
Given … When … Then …
scenarios
produce
Gherkin notation
@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
@DanielDeogun
Application Behavior Captured by Scenario Tests
BDD Scenarios
application
@DanielDeogun
Application Behavior Captured by Scenario Tests
BDD Scenarios
But what about dependencies to other systems?
invokes
application
@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…
@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
@DanielDeogun
Dependency Inversion Principle (DIP)
Dependency
Dependency implementation
abstraction
@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
@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]
@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();
@DanielDeogun
The Beautiful Trinity
DIP + DI + LSP = True
[6]
@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
@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
@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
@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
@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
@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
@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
@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
@DanielDeogun
Requirement
Easy to add / remove / update functionality
Easy to maintain
@DanielDeogun
Requirement
Easy to add / remove / update functionality
How do we avoid breaking things?
Easy to maintain
@DanielDeogun
Requirement
Easy to add / remove / update functionality
How do we avoid breaking things?
How do we design for change?
Easy to maintain
@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?
@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
@DanielDeogun
TDD Á la BDD
public void test_ticker() {Ticker ticker = new Ticker();
ticker.increment();
assertEquals(1, ticker.value());}
TDD
@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
@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
@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
@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)); } …
@DanielDeogun
The Great Frustration
Incorrect mocking makes you spend more time updating test code than production code
And the reason is…
@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!
@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)); } …
@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)); } …
@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())); }
@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
@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);
@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
@DanielDeogun
Branch By Code Using Feature Toggles
http://martinfowler.com/articles/feature-toggles.html
@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
@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
@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?
@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?
√
@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?
√
√
@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?
√√?
√
@DanielDeogun
Requirement
Must be resilient
@DanielDeogun
Requirement
How do we measure “resilience?”
Must be resilient
@DanielDeogun
Requirement
How do we measure “resilience?”
How do we test resilience?
Must be resilient
@DanielDeogun
Requirement
How do we measure “resilience?”
How do we test resilience?
Can we find the “upper bound?”
Must be resilient
@DanielDeogun
System Integration Resilience
@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
@DanielDeogun
Circuit Breakers Avoid Killing Backend
circuit breakers
@DanielDeogun
Schematic view of a Circuit Breaker
- Release It! Michael Nygard, The Pragmatic Bookshelf
@DanielDeogun
Bulkhead Pattern
https://github.com/Netflix/Hystrix/wiki/How-it-Works#Threads
[9]
@DanielDeogun
Separate Thread Pools Avoid Cascading Failures
circuit breakers
Separate thread pools
@DanielDeogun
Request Collapsing Pattern
https://github.com/Netflix/Hystrix/wiki/How-it-Works#RequestCollapsing
@DanielDeogun
“Internal” Resilience
[5]
Bad input data
Corrupt responses
Design by Contract
Message driven
Domain Driven DesignNull values
Default values
ImmutabilityConcurrency
@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
@DanielDeogun
Evaluation
How do we measure “resilience?”
How do we test resilience?
Must be resilient
Can we find the “upper bound?”
@DanielDeogun
Evaluation
How do we measure “resilience?”
How do we test resilience?
Must be resilient
Can we find the “upper bound?”
√?
@DanielDeogun
Evaluation
How do we measure “resilience?”
How do we test resilience?
Must be resilient
√
Can we find the “upper bound?”
√?
@DanielDeogun
Evaluation
How do we measure “resilience?”
How do we test resilience?
Must be resilient
√
√Can we find the “upper bound?”
√?
@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
@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
@DanielDeogun
Q & A
[7]
@DanielDeogun
Thanks@DanielDeogun
@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/