Test Driven Development Primer
!Jim Gough
!twitter: @JavaJimLondon email: [email protected]
aptood: http://aptood.com/6f1a22
Schedule
Introduction
Cognitive implications
Mocking
Legacy code
Who am I?
6 years developing in finance.
London Java Community/JCP Panel.
Strong belief in craftsmanship.
Disclaimer
Based on personal experience.
Based on what others found difficult.
I probably take some shortcuts.
I can definitely improve.
What is Test Driven Development?
Write a test first.
Make sure it compiles, runs and fails.
Write the simplest code to pass the test.
Refactor the code and if necessary the test.
http://www.flickr.com/photos/nocallerid_man/3638360458/
Cognitive Implications
http://www.flickr.com/photos/aloha75/4571410233
Cognitive Implications
http://www.flickr.com/photos/eepaul/3946701733/
Cognitive ImplicationsCompose.
Comprehend.
Vary depending on maintenance vs new development.
Rapid increase in complexity from the offset.
Jigsaws
Get the tech right
One Approach
Controller/ UI End Point
Mock
Handler Logic
Mock
External Dependencies
Order Controller
Bartender Database
Difficult ChangeReverse the way the way that you think.
Start from the interactions and the spec.
Asking questions.
Exploring the domain.
Answer the question with solutions.
Take a step further in.
Mocking
Ensure we only test one unit of code at a time. !
!
Helps us keep our goal of thinking about each part in stages.
http://www.flickr.com/photos/ick9s/3950547398/
Verifying Interaction@RunWith(MockitoJUnitRunner.class)public class TestOrderController {
@Mock private BartenderService bartender;
private OrderController orderController;
@Before public void before() { orderController = new OrderController(); orderController. setOrderService(bartender);
Verifying Interaction@Testpublic void testOrderBeer() {
orderController.order(Drink.BEER);
verify(bartender).obtain(Drink.BEER);!}
Verifying Interaction@Testpublic void testOrderTwoDrinks() {
Drink[] drinks = new Drink[] { Drink.BEER, Drink.WINE };! orderController.order(drinks);
verify(bartender, times(2)) .obtain(any(Drink.class));!}
Verifying Interaction@Testpublic void testOrderTwoDrinks() {
Drink[] drinks = new Drink[] { Drink.BEER, Drink.WINE };! orderController.order(drinks);
verify(bartender).obtain(Drink.BEER);verify(bartender).obtain(Drink.WINE);
verifyNoMoreInteractions(bartender);!}
Stubbing
when(databaseBean.inStock(Drink.BEER)) .thenReturn(true);
when(databaseBean.inStock(Drink.BEER)) .thenReturn(true)
.thenReturn(false);
when(databaseBean.inStock(any(Drink.class)) .thenReturn(true)
.thenReturn(false);
StubbingdoThrow(new RuntimeException()).when(databaseBean).removeStock();when(namedParameterJdbcTemplate.update(eq(Employer.INSERT_EMPLOYER), any(SqlParameterSource.class), any(GeneratedKeyHolder.class), any(String[].class))).then(new Answer<Integer>() {! @Override public Integer answer(InvocationOnMock invocation) throws Throwable { //Do things with arguments
// Return }});
Customer wants to order an ale.
@Test public voidcustomer_orders_an_ale() { //Initialise handlerAdapter
private AnnotationMethodHandlerAdapter handlerAdapter;protected MockHttpServletResponse response;
MockHttpServletRequest request = new MockHttpServletRequest();request.setURI(“/order/ale”);request.setMethod(RequestMethod.POST);
handlerAdapter.handle(request, response, orderController);verify(bartender).orderAle(ale);
Ale ale = new Ale(“IPA”);request.setContent(mapper.writeValueAsBytes( ale));
private Bartender bartender;
@RequestMapping(value="order/ale", method=RequestMethod.POST)
@ResponseStatus(value=HttpStatus.OK)
public Drink orderAle(@RequestBody Ale ale) { return bartender.order(ale);!!!}
@RequestBody
@Test public voidpour_ale_that_is_available() {}
@Test public voidale_not_available() {}
@Mockprivate JdbcTemplate jdbcTemplate;
@Test public voidpour_ale_that_is_available() { Object[] params = new Object[1]; params[0] = “IPA”; when(jdbcTemplate.query(“SELECT * FROM Stock WHERE NAME = ?”, params, any(BeerMapper.class)).thenReturn(new Drink(“IPA”));
@InjectMocksprivate Bartender bartender;
Drink drink = bartender.order(ale);verify(jdbcTemplate).query(anyString(), any(Object[].class), any(BeerMapper.class));verify(jdbcTemplate).update(REMOVE_DRINK, params);assertThat(drink.getName(), isEqualTo(“IPA”));
@Mockprivate JdbcTemplate jdbcTemplate;
@Test public voidale_not_available() { when(jdbcTemplate.query(“SELECT * FROM Stock WHERE NAME = ?”, any(Object[].class), any(BeerMapper.class)) .thenReturn(null);
@InjectMocksprivate Bartender bartender;
Drink drink = bartender.order(ale);verify(jdbcTemplate).query(anyString(), any(Object[].class), any(BeerMapper.class));
assertNull(drink);
public Drink order(Ale ale) {
private JdbcTemplate jdbcTemplate;
Object[] params = new Object[] { ale.getName() };List<Drink> drinks = jdbcTemplate.query(SELECT_DRINK, params, beerMapper);if(drinks.size() > 0) { jdbcTemplate.update(REMOVE_DRINK, params); return drinks.get(0);} else { return null;}
Refactoring Legacy Code
Legacy Code
Often following push and pray mentality.
Lots of regression or hands on testing.
Developer time is often wasted.
Highly coupled code, changes become risker over time.
Team culture can be of a fixed way.
Can I Use TDD?
Absolutely, but there are a few new techniques to learn.
Don’t change production code without a test.
One Approach
Write tests to cover production code.
Only refactor allowed are automated IDE steps.
Break statics using intermediate step by identifying seams.
Dealing with Seams
public class MyLegacyClass {
public String getTimesLoggedIn() { User user = UserSession.getInstance() .getLoggedInUser(); return user.timesLoggedIn(); }}
Dealing with Seams
public class MyLegacyClass {
public String getTimesLoggedIn() { User user = UserSession.getInstance() .getLoggedInUser(); return user.timesLoggedIn(); }
protected User getLoggedInUser() { return UserSession.getInstance() .getLoggedInUser();!}
Dealing with Seams
public class MyLegacyClass {
public String getTimesLoggedIn() { User user = getLoggedInUser(); return user.timesLoggedIn(); }
protected User getLoggedInUser() { return UserSession.getInstance() .getLoggedInUser();!}
Dealing with Seams
public class MyLegacyClassTest {
private class TestableMyLegacyClass extends MyLegacyClass { !!!!}
@Overrideprotected User getLoggedInUser() { return loggedInUser;!}
private User loggedInUser;
Recommended Tutorial
http://www.youtube.com/watch?v=_NnElPO5BU0
BenefitsExplore the domain, comprehend the problem.
Start with meaningful requirements.
Framework to solve complicated problems.
High confidence in changes.
Eventually, quicker more productive and accurate.
Sanity.
ChallengesInitially you have to reverse way of thinking.
You sometimes have to sell it to team members.
Sell the benefits to management.
Less risk changes:
One project, 5-6 issues per week - now 5 issues per year.
Developer productivity goes up.
The End Get ready to test!
!Jim Gough
!twitter: @JavaJimLondon email: [email protected]
aptood: http://aptood.com/6f1a22