Concepts and Patterns for TDD

advertisement
Test Driven
Lasse Koskela
Chapter 4: Concepts and Patterns for
TDD
Paul Ammann
http://cs.gmu.edu/~pammann/
Overview
•
•
•
•
•
•
How to Write Tests and Make Them Pass
Essential Testing Concepts
Closer Look into Test Doubles
Guidelines for Testable Designs
Unit-Testing Patterns
Working With Legacy Code
Lots of Stuff in This Chapter
7/1/2016
2
How to Write Tests And Make Them Pass
• Test Selection Strategies
– Details-First vs. Big-Picture
• Details-First Offers Concrete Progress, but
• Big-Picture Fleshes Out Overall Design
• Judgment Call In Any Given Situation
– Uncertain vs. Familiar
• Attacking Uncertainty Can Reduce Risk
• Familiar Code May Have Large Payoff
– High Value vs. Low Hanging Fruit
• Usually, High Value is Better
– Happy Path vs. Error Situations
• Happy Path First – Mostly for Value
• Sometimes Error Situations Help Define Remainder as Happy Path
Keep Your Options in Mind; Avoid a Rut
7/1/2016
3
How to Write Tests And Make Them Pass(2)
• Implementation Strategies
– Faking It
• Useful Strategy For Handling Big Steps
• Incurs Later Obligations To Squeeze Out Fakes
– Triangulation
• Strategy For Evolving Towards More General Implementation
• Use To Drive Out Hard Coded Solutions
– Obvious Implementation
• Sometimes The Correct Solution Really Is Simple
• Try It And See
– If Tests Fail, Back It Out
Goal Is To Get To Green Fast
7/1/2016
4
How to Write Tests And Make Them Pass(3)
• Prime Guidelines for Test Driving
– Do. Not. Skip. Refactoring.
• The Design Cycle for TDD is Crucial
• Skipping This Step Leads to Code/Test Bloat
• Result Is Unmaintainable Software
– Get To Green Fast
• Write Code That Passes The Tests First
• Then Worry About Refactoring
– Slow Down After a Mistake
• Mistakes Indicate That Your Reach Exceeds Your Grasp
• Back Off and Try Smaller Steps
• The Tests Are The Oracle That Keeps You On Track
The First Rule Is The Most Important
7/1/2016
5
Essential Testing Concepts
• Fixtures are the Context for Tests
– Holistic View of State
– Fixtures Remove Duplication
• Bloated Tests Are Hard to Read and Hard to Maintain
– Fixtures Allow For Focused Tests
• Test Doubles Stand In for Dependencies
– Example: java.sql.ResultSet
– Details Depend on Database Used
– More on Doubles in Later Slides
Real-World JUnit Tests Can’t Avoid Some Complexities
7/1/2016
6
Essential Testing Concepts(2)
• State and Interaction-Based Testing
– State-Based Testing
• Idea Is To Look At Variable State To Verifying Result
@Test
public void notEmpty() {
Collection<String> c = new ArrayList<String>();
assertTrue (c.isEmpty());
c.add(“Bob”);
assertFalse(c.isEmpty());
}
– Testing For Interactions
• Goal: Did The Expected Methods Calls Happen In The Right Order?
• Usually Requires Some Sort of Double
We lean on interaction-based testing to verify how an object talks to its
collaborators; we lean on state-based testing to verify how well the object listens.
7/1/2016
7
Closer Look Into Test Doubles
• Replace Object with Double If Real Object is
–
–
–
–
Too Slow
It’s Not Available
It Depends on Something That’s Not Available
Its Too Difficult To Instantiate Or Configure For a Test
• Examples
– How to test exceptions, such as
» “Dead”code?
» An unplugged network cable?
– How to interact with something that’s nondeterministic?
Doubles Are Key To Unit Testing
7/1/2016
8
Closer Look Into Test Doubles(2)
• Example of a Test Double
– Next Slide
• Stubs, Fakes, and Mocks
– Stub: Simplest Possible Implementation
– Fake: Still Hand Coded, But A Degree More Sophisticated
– Mock: Usually Generated By Tools
• Mock Objects in Action
– Next slide
Checking Correct Behavior is Complicated!
7/1/2016
9
Test Double Example: State Testing
7/1/2016
10
Test Double Example: Interaction Testing
7/1/2016
11
Guidelines for Testable Designs
• Choose Composition Over Inheritance
– General Advice For Normal Coding And Testing
– More Verbose, But Worth It
• Avoid Static and Singleton
– These Are Hard To Double
• Static class names are hardcoded. How to replace at test time?
– This Can Conflict With Normal Coding Advice
• Consider Factories!
Test Requirements Impact Code!
7/1/2016
12
4.4. Code smell: methods obtaining dependencies through static method calls
Dependency Example: Static Method
public class OrderProcessor {
public void process(Order order) {
PricingService service = PricingService.getInstance();
// use the PricingService object for processing the order
}
}
7/1/2016
13
Exploiting a Seam
7/1/2016
14
Injecting Dependencies: Easy Test!
public class OrderProcessorTest {
@Test public void testOrderProcessorWithDependencyInjection() throws Exception {
OrderProcessor p = new OrderProcessor();
p.setPricingService(new FakePricingService());
... }}
7/1/2016
15
Unit Testing Patterns
• Assertion Patterns
– Resulting State Assertion
• Most Common Usage
– Guard Assertion
• Test Both Before and After The Action (precondition testing)
– Delta Assertion
• Verify Part of the State – Eg, List is One Bigger Than Before
– Custom Assertion
• Encodes Complex Verification Rules
– Interaction Assertion
• Verification For Interaction Tests
Choose and Use Standard Patterns
7/1/2016
16
Example Interaction Assertion
7/1/2016
17
Fowler: Conventional JUnit Example
public class OrderStateTester extends TestCase {
private static String TALISKER = "Talisker";
private static String HIGHLAND_PARK = "Highland Park";
private Warehouse warehouse = new WarehouseImpl();
protected void setUp() throws Exception {
warehouse.add(TALISKER, 50);
warehouse.add(HIGHLAND_PARK, 25);
}
public void testOrderIsFilledIfEnoughInWarehouse() {
Order order = new Order(TALISKER, 50);
order.fill(warehouse);
assertTrue(order.isFilled());
assertEquals(0, warehouse.getInventory(TALISKER));
}
public void testOrderDoesNotRemoveIfNotEnough() {
Order order = new Order(TALISKER, 51);
order.fill(warehouse);
assertFalse(order.isFilled());
assertEquals(50, warehouse.getInventory(TALISKER));
}
}
7/1/2016
18
4.4. Code smell: methods obtaining dependencies through static method calls
Fowler: jMock Version
7/1/2016
public class OrderInteractionTester extends MockObjectTestCase {
private static String TALISKER = “Talisker”;
public void testFillingRemovesInventoryIfInStock() {
//setup - data
Order order = new Order(TALISKER, 50);
Mock warehouseMock = new Mock(Warehouse.class); // constructor
//setup - expectations
warehouseMock.expects(once()).method("hasInventory")
.with(eq(TALISKER),eq(50)) .will(returnValue(true));
warehouseMock.expects(once()).method("remove")
.with(eq(TALISKER), eq(50)) .after("hasInventory");
//exercise
order.fill((Warehouse) warehouseMock.proxy());
//verify
warehouseMock.verify(); assertTrue(order.isFilled());
}
public void testFillingDoesNotRemoveIfNotEnoughInStock() {
Order order = new Order(TALISKER, 51);
Mock warehouse = mock(Warehouse.class); // vs. mock call
warehouse.expects(once()).method("hasInventory")
.withAnyArguments() .will(returnValue(false));
order.fill((Warehouse) warehouse.proxy());
assertFalse(order.isFilled());
}}
19
4.4. Code smell: methods obtaining dependencies through static method calls
Fowler: EasyMock Example
public class OrderEasyTester extends TestCase {
private static String TALISKER = "Talisker";
private MockControl warehouseControl;
private Warehouse warehouseMock;
public void setUp() {
warehouseControl = MockControl.createControl(Warehouse.class);
warehouseMock = (Warehouse) warehouseControl.getMock();
}
public void testFillingRemovesInventoryIfInStock() {
//setup - data Order
order = new Order(TALISKER, 50);
//setup - expectations
warehouseMock.hasInventory(TALISKER, 50);
warehouseControl.setReturnValue(true);
warehouseMock.remove(TALISKER, 50);
warehouseControl.replay();
//exercise
order.fill(warehouseMock);
//verify
warehouseControl.verify();
assertTrue(order.isFilled());
}
7/1/2016
20
Fowler/Meszaros “Double” Definitions
• A Test Double is anything that stands in for a real object
– Dummy
• Used to fill parameter lists
– Fake
• Actual working implementations, but take shortcuts
• Example: In Memory Database
– Stub
• Canned answers to calls made during tests, but useless elsewhere
– Mock
• Objects preprogrammed with expectations that form a specification of
the calls they expect to receive
Various Levels of Complexity
7/1/2016
21
4.4. Code smell: methods obtaining dependencies through static method calls
Fowler: Stub Example
public interface MailService {
public void send (Message msg);
}
public class MailServiceStub implements MailService {
private List<Message> messages = new ArrayList<Message>();
public void send (Message msg) {
messages.add(msg);
}
public int numberSent() {
return messages.size();
}}
// Usage
class OrderStateTester...
public void testOrderSendsMailIfUnfilled() {
Order order = new Order(TALISKER, 51);
MailServiceStub mailer = new MailServiceStub();
order.setMailer(mailer);
order.fill(warehouse);
assertEquals(1, mailer.numberSent());
}
7/1/2016
22
4.4. Code smell: methods obtaining dependencies through static method calls
Fowler: Mock Version
class OrderInteractionTester...
public void testOrderSendsMailIfUnfilled() {
Order order = new Order(TALISKER, 51);
Mock warehouse = mock(Warehouse.class);
Mock mailer = mock(MailService.class);
order.setMailer((MailService) mailer.proxy());
mailer.expects(once()).method("send");
warehouse.expects(once()).method("hasInventory")
.withAnyArguments()
.will(returnValue(false));
order.fill((Warehouse) warehouse.proxy());
}
}
7/1/2016
23
Unit Testing Patterns (2)
• Fixture Patterns
– Parameterized Creation Method
• Populating Complex Set Of Objects
– Object Mother
• Aggregate of Creation Methods
– Automated TearDown
• More Important For Integration Testing Than Unit Testing
Fixtures Need Attention Too
7/1/2016
24
Unit Testing Patterns (3)
• Test Patterns
– Parameterized Test (see next slide)
– Self Shunt
• The Double Is The Test Class
– Intimate Inner Class
• Sharing Between Test Class and Test Double Classes
– Privileged Access
• Reflection-Based Injection Approaches For Legacy Code (Careful)
– Extra Constructor
• Compensates For Classes Not Designed For Testing
Tests Need To Accommodate A Variety of Real Code
7/1/2016
25
Parameterized JUnit Example
7/1/2016
26
Working With Legacy Code
• Test-Driven Legacy Development
• Analyzing the Change
– Change Points and Inflection Points
• Change Code at Change Points
• See Effects At Inflection Points
• Hopefully, The Points Are Close Together…
• Preparing for the Change
– Install Tests To Capture Current Behavior of Inflection Point
• Test-Driving the Change
– Add Tests To Capture New Behavior, Also At Inflection Point
Turning Legacy Code Into TDD Code
7/1/2016
27
Download