Test Driven Development Primer LJC Open Conference

Post on 21-Jun-2015

3.462 views 0 download

Tags:

description

Sometimes getting started with Test Driven Development is a difficult change for people to pick up. This presentation gives a brief guide to how to get started and suggests a few examples and practical points. The examples are a bit heavy in terms of they include some Spring - something I'll be looking to improve in the future

Transcript of Test Driven Development Primer LJC Open Conference

Test Driven Development Primer

!Jim Gough

!twitter: @JavaJimLondon email: jpgough@gmail.com

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: jpgough@gmail.com

aptood: http://aptood.com/6f1a22