58
When TDD Goes Awry Clueless tests, infesting mocks and other horrors... A voyage into today Java Enterprise worse practices. Uberto Barbini @ramtop h0ps://github.com/uberto Reggio Emilia - IAD 2013

When Tdd Goes Awry (IAD 2013)

Embed Size (px)

Citation preview

Page 1: When Tdd Goes Awry (IAD 2013)

When TDD Goes Awry

Clueless tests, infesting mocks and other horrors... A voyage into today Java

Enterprise worse practices.

Uberto  Barbini @ramtop

h0ps://github.com/uberto

Reggio Emilia - IAD 2013

Page 2: When Tdd Goes Awry (IAD 2013)

SHO to  start

SHIN  Heart,  mind

In the beginner's mind there are many possibilities, in the expert's mind there are few.

Page 3: When Tdd Goes Awry (IAD 2013)

a·wry (-r) adv. 1. In a position that is turned or twisted toward one side; askew. 2. Away from the correct course; amiss.

Page 4: When Tdd Goes Awry (IAD 2013)

a·wry (-r) adv. 1. In a position that is turned or twisted toward one side; askew. 2. Away from the correct course; amiss.

@Testpublic void testGetSwapType_SPOTFWD() { when(tradeBean.getField(TradeBeanFields.SWAP_TYPE)).thenReturn("SPOTFWD"); setUpTrade("SWAP"); assertEquals(TradeType.SPOTFWD, trade.getType()); when(tradeBean.getField(TradeBeanFields.SWAP_TYPE)).thenReturn("SPOTFWD"); setUpTrade("FWDFWDSWAP"); assertEquals(TradeType.SPOTFWD, trade.getType());} private void setUpTrade(final String tradingType) { when(tradeBean.getField(TradeBeanFields.ACCOUNT)).thenReturn(ACCOUNT_VAL); when(tradeBean.getField(TradeBeanFields.PRICE)).thenReturn(PRICE); when(tradeBean.getField(TradeBeanFields.TRADING_TYPE)).thenReturn(tradingType); trade = new Trade(tradeBean); }

Page 5: When Tdd Goes Awry (IAD 2013)

Test StoriesEach test should tell a story

Micro tests develop algorithmsScenario tests illustrate the design

Page 6: When Tdd Goes Awry (IAD 2013)

Test StoriesEach test should tell a story

Micro tests develop algorithmsScenario tests illustrate the design

When you are thinking big thoughts, write big tests. When you are thinking little thoughts, write little tests.!Kent Beck, Quora

Page 7: When Tdd Goes Awry (IAD 2013)

Test StoriesEach test should tell a story

Micro tests develop algorithmsScenario tests illustrate the design

When you are thinking big thoughts, write big tests. When you are thinking little thoughts, write little tests.!Kent Beck, Quora

Objects are nouns. Methods are verb. Good design is a good story. A good test remind us of a good design. Do you remember XP Metaphor?

Page 8: When Tdd Goes Awry (IAD 2013)

2001

Page 9: When Tdd Goes Awry (IAD 2013)

2001

My first project

Page 10: When Tdd Goes Awry (IAD 2013)

2001

My first project

Meaningful test names

Page 11: When Tdd Goes Awry (IAD 2013)

12 years later… and still we are talking about TDD

Page 12: When Tdd Goes Awry (IAD 2013)

Test Driven Design

12 years later… and still we are talking about TDD

Page 13: When Tdd Goes Awry (IAD 2013)

Test Driven DesignIt’s a kind of design technique, not a way to test.

12 years later… and still we are talking about TDD

Page 14: When Tdd Goes Awry (IAD 2013)

Test Driven DesignIt’s a kind of design technique, not a way to test.

I get paid for code that works, not for tests, so my philosophy is to test as little as possible to reach a given level of confidence.Kent Beck Stackoverflow

12 years later… and still we are talking about TDD

Page 15: When Tdd Goes Awry (IAD 2013)

Test Driven DesignIt’s a kind of design technique, not a way to test.

When TDD is not useful: when your don’t care about designi.e.. technical spikes, learning exercises

I get paid for code that works, not for tests, so my philosophy is to test as little as possible to reach a given level of confidence.Kent Beck Stackoverflow

12 years later… and still we are talking about TDD

Page 16: When Tdd Goes Awry (IAD 2013)

The caveman house designCarlo Pescio

Question: Why designing for testability result in good design?

Page 17: When Tdd Goes Awry (IAD 2013)

The caveman house designCarlo Pescio

Question: Why designing for testability result in good design?

Global state Hidden dependencies Inflexible behaviour

Things that work together are kept close

Page 18: When Tdd Goes Awry (IAD 2013)

@Testpublic void testGetSwapType_SPOTFWD() { when(tradeBean.getField(TradeBeanFields.SWAP_TYPE)).thenReturn("SPOTFWD"); setUpTrade("SWAP"); assertEquals(TradeType.SPOTFWD, trade.getType()); when(tradeBean.getField(TradeBeanFields.SWAP_TYPE)).thenReturn("SPOTFWD"); setUpTrade("FWDFWDSWAP"); assertEquals(TradeType.SPOTFWD, trade.getType());} private void setUpTrade(final String tradingType) { when(tradeBean.getField(TradeBeanFields.ACCOUNT)).thenReturn(ACCOUNT_VAL); when(tradeBean.getField(TradeBeanFields.PRICE)).thenReturn(PRICE); when(tradeBean.getField(TradeBeanFields.TRADING_TYPE)).thenReturn(tradingType); trade = new Trade(tradeBean); }

Page 19: When Tdd Goes Awry (IAD 2013)

If to test 3 lines of simple code, we have 10 lines of complicated test…!Which is more likely to have a bug? the code or the test?

@Testpublic void testGetSwapType_SPOTFWD() { when(tradeBean.getField(TradeBeanFields.SWAP_TYPE)).thenReturn("SPOTFWD"); setUpTrade("SWAP"); assertEquals(TradeType.SPOTFWD, trade.getType()); when(tradeBean.getField(TradeBeanFields.SWAP_TYPE)).thenReturn("SPOTFWD"); setUpTrade("FWDFWDSWAP"); assertEquals(TradeType.SPOTFWD, trade.getType());} private void setUpTrade(final String tradingType) { when(tradeBean.getField(TradeBeanFields.ACCOUNT)).thenReturn(ACCOUNT_VAL); when(tradeBean.getField(TradeBeanFields.PRICE)).thenReturn(PRICE); when(tradeBean.getField(TradeBeanFields.TRADING_TYPE)).thenReturn(tradingType); trade = new Trade(tradeBean); }

Page 20: When Tdd Goes Awry (IAD 2013)

Let’s start from AssertionsOne of the least followed TDD rule says: “There must be one assertion for test”. Why?

Bad test smells: 1 - Too many assertions

Page 21: When Tdd Goes Awry (IAD 2013)

Let’s start from AssertionsOne of the least followed TDD rule says: “There must be one assertion for test”. Why?

Bad test smells: 1 - Too many assertions

The point behind testing one thing at time is the we want to run all the state checks, every time, independently. !No logic in the tests, not even an “if”. !As less as possible duplication with the logic being tested, ideally no duplication with other tests.

Page 22: When Tdd Goes Awry (IAD 2013)

Let’s start from AssertionsOne of the least followed TDD rule says: “There must be one assertion for test”. Why?

Bad test smells: 1 - Too many assertions

The point behind testing one thing at time is the we want to run all the state checks, every time, independently. !No logic in the tests, not even an “if”. !As less as possible duplication with the logic being tested, ideally no duplication with other tests.

There are 3 kinds of multiple assertions:

Page 23: When Tdd Goes Awry (IAD 2013)

Multi Assertion I@Testpublic void splitInWords() throws Exception { String text = "To be, or not to be: that is the question"; String[] words = getWords(text); //these assertion are increasing the precision, //if one fails it makes no sense to run the next assertNotNull(words); assertTrue(words.length > 0); assertEquals("To", words[0]); assertEquals(10, words.length); assertEquals("question", words[9]);} private String[] getWords(String s) { return s.split(" ");}

Page 24: When Tdd Goes Awry (IAD 2013)

Multi Assertion I@Testpublic void splitInWords() throws Exception { String text = "To be, or not to be: that is the question"; String[] words = getWords(text); //these assertion are increasing the precision, //if one fails it makes no sense to run the next assertNotNull(words); assertTrue(words.length > 0); assertEquals("To", words[0]); assertEquals(10, words.length); assertEquals("question", words[9]);} private String[] getWords(String s) { return s.split(" ");} Sometimes scenario tests

use assertions in this way

Page 25: When Tdd Goes Awry (IAD 2013)

Multi Assertion II@Testpublic void calculateQuote() throws Exception { Quote expected = new Quote("USDGBP", "0.62"); Quote calculated = getQuote("USD", "GBP"); //no very good because if one fail we don't know the value of the other check assertEquals(expected.getSubject(), calculated.getSubject()); assertEquals(expected.getValue(), calculated.getValue());//much better because it compare all fields every time assertThat(expected, sameQuote(calculated));} private Matcher<Quote> sameQuote(Quote quote) { return new QuoteMatcher(quote);} private Quote getQuote(String cur1, String cur2) { return new Quote(cur1+cur2, "0.62"); }

Page 26: When Tdd Goes Awry (IAD 2013)

Multi Assertion III@Testpublic void commutativeProperty() throws Exception { //these three should be put on 3 different tests //or use some kind of data table assertions like spec //or maybe assertAndContinue() assertEquals(5, sum(3,2)); assertEquals(8, sum(3,5)); assertEquals(sum(3,sum(4,5)), sum(sum(3, 4),5));}

Page 27: When Tdd Goes Awry (IAD 2013)

StubsMocks test behaviour, Stubs test state !Stubs doesn’t verify calls !Stubs doesn’t check for parameters !Stubs can be easily reused. Different examples. !No need to create stubs dynamically. !No need to use doubles at all for immutables.

Bad test smells: 2 - Too many doubles

Page 28: When Tdd Goes Awry (IAD 2013)

MocksMocks are complicated, try to use them scarcely. !Use stubs for decorators and close friends, mocks for external collaborators (i.e. listeners) !Stubs can be prepared in setup. Mocks must be “loaded” in the actual test. !Try to verify mocks with actual params or matcher, not any (or maybe you wanted a stub instead?).

Page 29: When Tdd Goes Awry (IAD 2013)

@Testpublic void testGetSwapType_SPOTFWD() { when(tradeBean.getField(TradeBeanFields.SWAP_TYPE)).thenReturn("SPOTFWD"); setUpTrade("SWAP"); assertEquals(TradeType.SPOTFWD, trade.getType()); when(tradeBean.getField(TradeBeanFields.SWAP_TYPE)).thenReturn("SPOTFWD"); setUpTrade("FWDFWDSWAP"); assertEquals(TradeType.SPOTFWD, trade.getType());} private void setUpTrade(final String tradingType) { when(tradeBean.getField(TradeBeanFields.ACCOUNT)).thenReturn(ACCOUNT_VAL); when(tradeBean.getField(TradeBeanFields.PRICE)).thenReturn(PRICE); when(tradeBean.getField(TradeBeanFields.TRADING_TYPE)).thenReturn(tradingType); trade = new Trade(tradeBean); }

Smells: Assertions + Mocks

Page 30: When Tdd Goes Awry (IAD 2013)

@Testpublic void testGetSwapType_SPOTFWD_SWP() { when(tradeBean.getField(TradeBeanFields.SWAP_TYPE)).thenReturn("SPOTFWD"); when(tradeBean.getField(TradeBeanFields.ACCOUNT)).thenReturn(ACCOUNT_VAL); when(tradeBean.getField(TradeBeanFields.PRICE)).thenReturn(PRICE); when(tradeBean.getField(TradeBeanFields.TRADING_TYPE)).thenReturn("SWAP"); trade = new Trade(tradeBean); assertEquals(TradeType.SPOTFWD, trade.getType());} @Testpublic void testGetSwapType_SPOTFWD_FWDSWP() { when(tradeBean.getField(TradeBeanFields.SWAP_TYPE)).thenReturn("SPOTFWD"); when(tradeBean.getField(TradeBeanFields.ACCOUNT)).thenReturn(ACCOUNT_VAL); when(tradeBean.getField(TradeBeanFields.PRICE)).thenReturn(PRICE); when(tradeBean.getField(TradeBeanFields.TRADING_TYPE)).thenReturn("FWDFWDSWAP"); trade = new Trade(tradeBean); assertEquals(TradeType.SPOTFWD, trade.getType());} !

Split and inline

Page 31: When Tdd Goes Awry (IAD 2013)

@Testpublic void useSwapTypeIfTradingIsSwap() { //split test in two //construct concrete bean using a fluent builder tradeBean = SimpleTradeBean.prepare().currencies("EURGBP", "0.67").swapType("SPOTFWD").tradingType("SWAP"); trade = new Trade(tradeBean); //next step: better using a matcher on an expected Trade rather than // compare a field at time assertEquals(TradeType.SPOTFWD, trade.getType());} @Testpublic void useSwapTypeIfTradingIsFwdSwap() { tradeBean = SimpleTradeBean.prepare().currencies("USDGBP", "1.2").swapType("SPOTFWD").tradingType("FWDSWAP"); trade = new Trade(tradeBean); assertEquals(TradeType.SPOTFWD, trade.getType());}

Stubs with builders

Bean is mutable

Page 32: When Tdd Goes Awry (IAD 2013)

@Testpublic void testBusSourceSendMessage() throws Exception { //complex setup, unclear design, high coupling when(mySource.createActivePublisher(any(String.class), any(DataFetcher.class))).thenReturn(myPub); when(myPub.getMessageFactory()).thenReturn(myMsgFactory); when(myMsgFactory.createMessage("EURUSD")).thenReturn(myMsg); myFetcher = new DataFetcher(mySource); mySource.createActivePublisher("quotes", myFetcher); //why mocking a immutable object? MessageItem item = mock(MessageItem.class); when(item.getSubject()).thenReturn("EURUSD"); //7 lines of mocks to test 3 lines of code myFetcher.updateData(item); //hard to understand the goal of this test from the verify verify(mySource).notify(myMsg);} !

High Coupled Design

Page 33: When Tdd Goes Awry (IAD 2013)

High Coupling !!!!!In software engineering, coupling or dependency is the degree to which each program module relies on each one of the other modules. antipattern of high coupling: !cohesion refers to the degree to which the elements of a module belong together.[1] Thus, it is a measure of how strongly-related each piece of functionality expressed by the source code of a software module is. Wikipedia

Bad test smells: 3 - High Coupling

Page 34: When Tdd Goes Awry (IAD 2013)

The usual culprit: Dependency Injection frameworks

The best classes in any application are the ones that do stuff: the BarcodeDecoder, the KoopaPhysicsEngine, and theAudioStreamer. These classes have dependencies; perhaps a BarcodeCameraFinder, DefaultPhysicsEngine, and an HttpStreamer.!

To contrast, the worst classes in any application are the ones that take up space without doing much at all: theBarcodeDecoderFactory, the CameraServiceLoader, and the MutableContextWrapper. These classes are the clumsy duct tape that wires the interesting stuff together.!

Dagger is a replacement for these FactoryFactory classes. It allows you to focus on the interesting classes. Declare dependencies, specify how to satisfy them, and ship your app.!!from Dagger introduction!http://square.github.io/dagger/

Good things about Dagger: good and invisible duct tape

Page 35: When Tdd Goes Awry (IAD 2013)

I beg to differ, duct tape is important!

Page 36: When Tdd Goes Awry (IAD 2013)

I beg to differ, duct tape is important!

That is, it’s important to wiring up our objects in the best possible way. !Write tests to show how your wiring is done !Replace Duct Tape with Demeter

Page 37: When Tdd Goes Awry (IAD 2013)

@Testpublic void testBusSourceSendMessage() throws Exception { //complex setup, unclear design, high coupling when(mySource.createActivePublisher(any(String.class), any(DataFetcher.class))).thenReturn(myPub); when(myPub.getMessageFactory()).thenReturn(myMsgFactory); when(myMsgFactory.createMessage("EURUSD")).thenReturn(myMsg); myFetcher = new DataFetcher(mySource); mySource.createActivePublisher("quotes", myFetcher); //why mocking a immutable object? MessageItem item = mock(MessageItem.class); when(item.getSubject()).thenReturn("EURUSD"); //7 lines of mocks to test 3 lines of code myFetcher.updateData(item); //hard to understand the goal of this test from the verify verify(mySource).notify(myMsg);} !

High Coupled

Page 38: When Tdd Goes Awry (IAD 2013)

@Testpublic void simplifiedBusSourceSendMessage() throws Exception { //simplify the real class to use it in tests BusSource mySource = new SimpleBusSource(); myFetcher = new DataFetcher(mySource); Publisher myPub = mySource.createActivePublisher("quotes", myFetcher); // myFetcher.updateData(item); //verify(mySource).notify(myMsg); //we cannot yet verify the fetcher, so we copied fetcher code here and test it myMsg = myPub.getMessageFactory().createMessage("EURGBP"); assertEquals("EURGBP", myMsg.getId());} !!

Unmock it with a simpler object

Page 39: When Tdd Goes Awry (IAD 2013)

@Testpublic void simplifiedBusSourceSendMessage() throws Exception { //simplify the real class to use it in tests BusSource mySource = new SimpleBusSource(); myFetcher = new DataFetcher(mySource); Publisher myPub = mySource.createActivePublisher("quotes", myFetcher); // myFetcher.updateData(item); //verify(mySource).notify(myMsg); //we cannot yet verify the fetcher, so we copied fetcher code here and test it myMsg = myPub.getMessageFactory().createMessage("EURGBP"); assertEquals("EURGBP", myMsg.getId());} !!

Unmock it with a simpler object

Page 40: When Tdd Goes Awry (IAD 2013)

@Testpublic void whenUpdateSendMessageToListeners() throws Exception { Quote eurusd = new Quote("EURUSD", "1.2"); //Simplified BusSource. More complex versions can exist for reporting, etc. mySource = new SimpleBusSource(); myFetcher = new DataFetcher(mySource); //let's register a listener for all topics //this is the only mock, at the end of the chain of real objects // working together mySource.addTopicListener("*", myListener); //when there is an update on data... myFetcher.updateData(eurusd); //we check same data arrives to interested listeners verify(myListener).refresh(argThat(new SamePayload(eurusd)) );}

Test with a listener mock

Page 41: When Tdd Goes Awry (IAD 2013)

@Testpublic void retrieveModules() throws Exception { Page page = new Page(); Repository repo = mock(Repository.class); UserData context = new UserData("gb"); //first let's get the page layout for the user country in a parsed xml MapOfString pageDescriptor = repo.getPageDescriptor(context.getCountry()); //then get the id of actual modules needed matching user context with page layout List<String> moduleIds = page.selectModules(context, pageDescriptor); //get the modules as string properties from parsed xml List<MapOfString> modules = repo.getModules(moduleIds); //to be safe we need to make sure we are using same stub in this test and the next assertEquals(STUB_MODULES, modules);} @Testpublic void composePage() throws Exception { //here we continue the flow from the previous test Page page = new Page(); //get the modules as string properties from parsed xml List<MapOfString> modules = STUB_MODULES; //compose json page from properties String jsonPage = page.compose(modules); assertEquals(expectedJson, jsonPage);}

Testing Layers

Page 42: When Tdd Goes Awry (IAD 2013)

Lasagna Code

There is no problem in computer science that cannot be solved by adding another layer of indirection, except having too many layers of indirection

Page 43: When Tdd Goes Awry (IAD 2013)

Bad test smells: 4 - Complicated Tests

Page 44: When Tdd Goes Awry (IAD 2013)

We have a problem, Our code is too difficult to test

Bad test smells: 4 - Complicated Tests

Page 45: When Tdd Goes Awry (IAD 2013)

We have a problem, Our code is too difficult to testLet's write a framework to test it!

Bad test smells: 4 - Complicated Tests

Page 46: When Tdd Goes Awry (IAD 2013)

We have a problem, Our code is too difficult to testLet's write a framework to test it!Ok, now we have 2 problems...

Bad test smells: 4 - Complicated Tests

Page 47: When Tdd Goes Awry (IAD 2013)

We have a problem, Our code is too difficult to testLet's write a framework to test it!Ok, now we have 2 problems...

Dedicated test stub must be simple and transparent. They should explain the model, not hide it.

Bad test smells: 4 - Complicated Tests

Page 48: When Tdd Goes Awry (IAD 2013)

We have a problem, Our code is too difficult to testLet's write a framework to test it!Ok, now we have 2 problems...

Same problem for who has to develop against a big framework: even if I have the framework tests, how can I be sure of not losing pieces around? Let's model domain simply as whole and then split it up for the framework.

Dedicated test stub must be simple and transparent. They should explain the model, not hide it.

Bad test smells: 4 - Complicated Tests

Page 49: When Tdd Goes Awry (IAD 2013)

@Testpublic void retrieveModules() throws Exception { Page page = new Page(); Repository repo = mock(Repository.class); UserData context = new UserData("gb"); //first let's get the page layout for the user country in a parsed xml MapOfString pageDescriptor = repo.getPageDescriptor(context.getCountry()); //then get the id of actual modules needed matching user context with page layout List<String> moduleIds = page.selectModules(context, pageDescriptor); //get the modules as string properties from parsed xml List<MapOfString> modules = repo.getModules(moduleIds); //to be safe we need to make sure we are using same stub in this test and the next assertEquals(STUB_MODULES, modules);} @Testpublic void composePage() throws Exception { //here we continue the flow from the previous test Page page = new Page(); //get the modules as string properties from parsed xml List<MapOfString> modules = STUB_MODULES; //compose json page from properties String jsonPage = page.compose(modules); assertEquals(expectedJson, jsonPage);}

Testing Layers separately

Page 50: When Tdd Goes Awry (IAD 2013)

@Testpublic void composePage() throws Exception { //we can use a single test here from xml to json //only mock Repository, because implementation is in another sub-project Repository repo = mock(Repository.class); when(repo.getLayoutPage(COUNTRY)).thenReturn(LAYOUT_XML); when(repo.getModules(MODULE_IDS)).thenReturn(MODULES_XML); UserData context = new UserData(COUNTRY); //instead of MapOfStrings we use a proper object to keep layout Layout page = Layout.buildFromXml(repo.getLayoutPage(context.getCountry())); //we use Module object to keep module properties and methods List<Module> moduleList = page.prepareModules(repo, context); //render the list of modules, easier than with strings properties String jsonPage = renderer.toJson(moduleList); //checking matcher assertThat(EXPECTED_JSON, sameJson(jsonPage));}

Testing Layers together

Page 51: When Tdd Goes Awry (IAD 2013)

How to improve

Page 52: When Tdd Goes Awry (IAD 2013)

How to improveIf your tests give you pain don't ignore it. Localise the cause.

Page 53: When Tdd Goes Awry (IAD 2013)

How to improveIf your tests give you pain don't ignore it. Localise the cause.

Page 54: When Tdd Goes Awry (IAD 2013)

How to improveIf your tests give you pain don't ignore it. Localise the cause.

Ask to new team members or dev from other teams their impressions.

Page 55: When Tdd Goes Awry (IAD 2013)

How to improveIf your tests give you pain don't ignore it. Localise the cause.

Ask to new team members or dev from other teams their impressions.

Page 56: When Tdd Goes Awry (IAD 2013)

How to improveIf your tests give you pain don't ignore it. Localise the cause.

Ask to new team members or dev from other teams their impressions.

Experiment and share.

Page 57: When Tdd Goes Awry (IAD 2013)

How to improveIf your tests give you pain don't ignore it. Localise the cause.

Ask to new team members or dev from other teams their impressions.

Experiment and share.

Page 58: When Tdd Goes Awry (IAD 2013)

How to improveIf your tests give you pain don't ignore it. Localise the cause.

Ask to new team members or dev from other teams their impressions.

Experiment and share.

Rule 0: TDD is supposed to be fun and simple.