370 likes | 519 Views
Test-Driven Service Development Developing robust web service frameworks. Status Report Jacob Orshalick. Agenda. Introduction Research overview Status report. Introduction. Test-driven development Test-first development (TFD) Refactoring TDD = TFD + Refactoring Framework Layers.
E N D
Test-Driven Service DevelopmentDeveloping robust web service frameworks Status Report Jacob Orshalick
Agenda • Introduction • Research overview • Status report
Introduction • Test-driven development • Test-first development (TFD) • Refactoring • TDD = TFD + Refactoring • Framework Layers
Introduction • Scott Ambler [2] defines TDD development with the following formula: • TDD = TFD + refactoring. • where TFD is test-first development
Test-first development Output: test suite and code which satisfies test suite [pass, development stops] [pass, development continues] Add a test Run the tests [pass] Run the tests [fail] Make a little change (of test stubs) [fail]
Test-driven development • Code is never written until a test is written that fails. • Develop the code with the intention to pass the test. • Once tests have passed, refactor the code to retain quality and ensure loosely coupled components. • Key is, write a little test, write a little code. • But, what drives the tests?
Test-driven development (cont) • Requirements are the drivers for the tests that are developed. • A requirement must be defined to the point that it can be tested (and through potentially some analysis unit-tested). • This is currently a grey area in TDD as it is difficult in requirements gathering to determine what level of granularity constitutes an acceptable test.
Example: Shipping & Handling • Let us say that we need to develop a system to calculate shipping and handling for an order. • The details of how shipping is calculated are unknown at this time, but we do know the details about an order. • An order consists of line items which each have a value and quantity: • Line Item value = quantity * individual value • Order value = Total value of all Line Items • Based on the total value of the order, the shipping can be calculated.
Order LineItem[] lineItems getTotal() LineItem value : double quantity : int Shipping & Handling 1 1..*
Add a test • So let’s cut to the chase and write a test to check the total value of an Order! • The Order class is stubbed out and then the following test is written, TestOrder.java public void testOrderValue() { // initialize a new LineItem with test values LineItem l1 = new LineItem(); l1.setQuantity(3); l1.setValue(50.00); // initialize another LineItem with test values LineItem l2 = new LineItem(); l2.setQuantity(2); l2.setValue(25.00); Order o = new Order(); // add the LineItems to the Order o.addLineItem(l1); o.addLineItem(l2); // test that the Order total is correct assertEquals(o.getTotal(), 200.00); }
Run the test • The test is run and promptly fails as no logic exists to calculate the value for the assertion.
Make a little change (of test stubs) • The Order.getTotal() method is implemented to handle the test: public double getTotal() { double total = 0; // loop through the LineItems and add the total // value of each LineItem to the total for(LineItem l : this.lineItems) { total += l.getValue() * l.getQuantity(); } return total; }
Run the test • This time the test passes, thus our logic was implemented correctly.
Refactoring • Refactoring is described by Martin Fowler as [4]: • Refactoring is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior. • Its heart is a series of small behavior preserving transformations. Each transformation (called a 'refactoring') does little, but a sequence of transformations can produce a significant restructuring. • Since each refactoring is small, it's less likely to go wrong. The system is also kept fully working after each small refactoring, reducing the chances that a system can get seriously broken during the restructuring.
Refactoring Guidelines • Martin Fowler and SantiagoValdarrama describe the following guiding principals for refactoring: • Backtrack If Refactoring Fails – ensure a backup copy of the code exists • The First Refactoring Step – ensure a suitable unit test suite exists • Refactoring In Very Small Steps – only make a few small changes and apply Test Every Refactoring • Test Every Refactoring – run the unit test suite against the refactored code • Refactoring In Duets – utilize pair programming • Refactoring Using Tools – tools ensure that name changes, structural changes, etc. are kept consistent • Refactoring Hat – do not change the semantics of the code, a good guideline to follow: no unit tests should change during refactoring
Refactoring Techniques • Extract Method from Fowler, • "You have a code fragment that can be grouped together… Turn the fragment into a method whose name explains the purpose of the method.“ • Move Method, • "A method is, or will be, using or used by more features of another class than the class on which it is defined. Create a new method with a similar body in the class it uses most. Either turn the old method into a simple delegation, or remove it altogether.“ • Replace Conditional With Polymorphism • Break the functional differences into method implementations in sub-classes of some abstract base class. The actual implementation is then determined through polymorphism.
Test-driven development [pass, development continues] Add a test [pass] [pass, development stops] Run the tests Run the tests (verify refactoring did not introduce defects) [fail] [fail] Make a little change Make a little structural change (reduce coupling and abstract commonalities) [fail] Run the tests Analyze code
Example (cont.) • Analyzing the code we notice that the Move Method technique is applicable in our implementation of Order.getTotal(). • total += l.getValue() * l.getQuantity() // could be rewritten as • total += l.getTotal(); // where getTotal() performs the multiplication public double getTotal() { double total = 0; // loop through the LineItems and add the total // value of each LineItem to the total for(LineItem l : this.lineItems) { total += l.getValue() * l.getQuantity(); } return total; }
Make a little structural change • The method getTotal() is added to the LineItem object and the functionality is extracted from Order: LineItem.java: public double getTotal() { return this.value * this.quantity; } Order.java: public double getTotal() { … for(LineItem l : this.lineItems) { total += l.getTotal() } …
Run the tests • Validate that after refactoring the code still passes the test suite.
Add a test • Another test can now be added specific to the LineItem.getTotal() method. • This allows the LineItem to be tested in an isolated fashion: TestLineItem.java: public void testLineItemValue() { LineItem lItem = new LineItem(); lItem.setValue(25.00); lItem.setQuantity(2); assertEquals(lItem.getValue(), 50.00); }
Run the tests • This time we run the test suite as there are now 2 objects: TestOrder and TestLineItem
TDD Behavior • Beck [3] explains the following behavior: • You write your own tests because you can't wait 20 times per day for someone else to write them for you. • Your development environment must provide rapid response to small changes (e.g you need a fast compiler and regression test suite). • Your designs must consist of highly cohesive, loosely coupled components (e.g. your design is highly normalized) to make testing easier (this also makes evolution and maintenance of your system easier too).
Example (cont.) • The intended method of calculating shipping is determined by a rate table where the Total Order value indicates cost. • The shipping cost is then prorated among the line items based on the following formula: • Line Item value / Total Order value = Line Item % of Order • Line Item % of Order * Shipping Rate = Prorated Shipping • The Prorated Shipping should always be rounded down and floating pennies should be added back to the most expensive line item.
TestLineItem Order RateTable LineItem[] lineItems Rate[] rates testLineItemTotal() getRate(total : double) getTotal() prorateShipping() getShippingCharge() Rate min : double max : double cost : double TestRate testIsWithinRange() testIsWithinRangeUpperBound() testIsOutsideRangeLowerBound() Shipping and Handling w/ Test Suite determines rate for 1 TestRateTable TestOrder 1 1..* 1..* testGetRate() testGetRateUpperBound() testGetRateLowerBound() testOrderTotal() testProrateShipping() testGetShippingCharge() LineItem value : double quantity : int shipping : double
This presents an issue… • Doubles have been used for handling money which can cause loss of precision (especially during division): • Line Item value / Total Order value = Line Item % of Order • Line Item % of Order * Shipping Rate = Prorated Shipping • Money is only concerned with 2 decimal places of precision. • This could be accomplished by analyzing the code and implementing the rounding policy in all the places where calculations occur… • Or, an aspect could be written that implements the concern throughout the objects at join-points.
Develop the aspect • The following aspect will handle this policy: public aspect MoneyPolicy { pointcut moneyRequests() : execution(public double getTotal()) || execution(public double getValue()); pointcut moneyParams() : execution(public void setTotal(double)) || execution(public void setValue(double)); before() : moneyParams() { double value = (new Double(thisJoinPoint.getArgs()[0])).doubleValue(); BigDecimal policy = new BigDecimal(value, new MathContext(2)); policy.doubleValue(); } double around(double returned) : moneyRequests() { BigDecimal policy = new BigDecimal(value, new MathContext(2)); return policy.doubleValue(); } }
Original Sequence Diagram Order LineItem getTotal() : double total = value * quantity total
Aspect Sequence Diagram moneyRequests() Order MoneyPolicy <<aspect>> LineItem getTotal(): double <<pointcut>> moneyRequests() total = value * quantity total BigDecimal policy = new BigDecimal(value, new MathContext(2)) policy.doubleValue();
How can we test this aspect? • First refactor the code into a helper class for the policy so it can be tested by a unit test. • Create a mock object that catches when the advice is executed. • This requires a slightly different approach to test-driven development.
Research overview • Hypothesis • Test-driven development techniques can be applied to development of an aspect-oriented system. • Assumptions • The strategy will utilize existing unit testing frameworks, e.g. JUnit and JMock • Existing test-driven methodology practices will be utilized and modified according to the needs of an aspect-oriented development endeavor.
Questions to answer • How can tests be written prior to writing aspect code? • What TDD frameworks suit AOP? • How can we ensure an appropriate test suite for an aspect? • What modifications must be made to the existing TDD methodology to accommodate AOP?
HACS Development Status • Development of HACS system • Microwave – complete • Air conditioner – 50% complete give details • Security – 40% complete give details • Home simulator • Backend – Complete • UI – In progress • Show test case diagram as well as system diagram.
Technologies • Maven – build tool • Spring – cross-domain framework (core) • Spring MVC – cross-domain framework (web) • JUnit – cross-domain framework (testing) • JMock – mock-object framework (testing) • Eclipse – integrated development environment (IDE) • Apache Tomcat – web server
Methodology techniques • Test-driven development • Feature-driven development (for requirements purposes – explain this further) • Iterative development
Current tasks • Continuing development of HACS • Test case development then system • Looking at FitNesse as a system-integration test suite that provides the necessary tests to ensure that application-instances will function appropriately with the framework. • Explain relationship between FitNesse and refactoring. • Add slides to explain details. • Provide sample JUnit and FitNesse test for HACS • Show the difference between original code structure and new code structure (revisions) • Reviewing research papers associated with TDD and refactoring. • Information: http://orshalic.wikispaces.com/
Idea: • Case study – compare efficiency of TDD with standard implementation then testing approach