Refactoring tested code - has mocking gone wrong?
-
Upload
ben-stopford -
Category
Documents
-
view
2.686 -
download
0
description
Transcript of Refactoring tested code - has mocking gone wrong?
![Page 1: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/1.jpg)
Managing Refactoring in a Test Driven World
RefT
est Refactoring Tested
Code: Has Mocking Gone Wrong?
Ben StopfordRoyal Bank of Scotland
![Page 2: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/2.jpg)
“I've always been a old fashioned classic TDDer and thus far I don't see any
reason to change. I don't see any compelling benefits for mockist TDD, and am concerned about the consequences of coupling tests to implementation.”
![Page 3: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/3.jpg)
This is about as far from the fence as Martin ever gets!!
Mocking, BDD, Interaction based testing. The belief is that they inhibit refactoring because of the coupling they add between test and source. This is what we will be looking at today.
![Page 4: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/4.jpg)
A Chronology of Testing
Isolating Functionality
in State Based Tests Using Stubs
Interaction Based Testing
The Affect IBT has on
Refactoring
Managing Refactoring in an IBT World
![Page 5: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/5.jpg)
On eA Chronology
![Page 6: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/6.jpg)
XP
• Introduced in late 1990’s• Automated testing features heavily in the 12 key
practices• Designed to reduce the risk of change• Practices that seem somewhat counterintuitive, but
work
Test
First
• Write tests before writing code• Change in programming process• Focuses intention on required functionality
![Page 7: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/7.jpg)
Test Drive
n
• Evolution of Test First• Work in a tight loop: Test-Code-Refactor• Demarcate test areas with stubs: So you
break off no more than you can chew.
Interaction Base
d Testin
g
• Characterised by the use of Mocking Frameworks
• Contrasting technique to State Based testing• Interactions between collaborating classes
are tested not the class’ final state.
![Page 8: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/8.jpg)
Nomenclature
Mock:Objects pre-programmed with expectations which form a specification of the calls they are expected to receive.
Stubs: Provide canned answers to calls made during the test.
Dummy:Objects are passed around but never actually used. Usually they are just used to fill parameter lists.
![Page 9: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/9.jpg)
Two Isolating the Functionality Under Test
![Page 10: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/10.jpg)
State Based Testing
Testing the CashTransfer object involves testing the whole dependency tree.
![Page 11: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/11.jpg)
State Based Testing
@Test void shouldMoveCashToNewAccount(){Transfer tran = new CashTransfer(5,…);tran.execute(newAcc);
assertEqual(expected, newAcc.balance());}
Test body that Exercises the Class under test
State based assertion
![Page 12: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/12.jpg)
State Based Tests Often Overlap
Save Button
Save Action
ValidityCheck
ORMDB
![Page 13: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/13.jpg)
Failure in the ORM causes all tests to fail
Save Button
Save Action
ValidityCheck
ORMDB
![Page 14: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/14.jpg)
Key
Poin
t Tests that do not ‘isolate’ the code being tested will likely overlap. This makes it hard to diagnose the source of a break.
![Page 15: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/15.jpg)
Isolate the area under test with stubbed interfaces that provide fixed behaviour.
Save Button
Save Action
ValidityCheck
ORM DB
class StubValididityCheck{ boolean valid(){ return true; }}
Stub n’ State
approach
![Page 16: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/16.jpg)
Tests now break in isolation
Save Button
Save Action
ValidityCheck
ORM DB
![Page 17: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/17.jpg)
Stubs isolate sections of the Object Graph because they have no real behaviour. They provide pre-canned answers.
o = new ruleComposite(new A(…), new B(…));result = o.run(user);assertEqual(Result.VALID, result);
o = new ObjectUnderTest(stubA, stubB);result = o.doSomething();assertEqual(expected, result);
B
A
B
A
![Page 18: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/18.jpg)
Each test is isolated from change in other classes
![Page 19: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/19.jpg)
Key
Poin
t To unit test properly you need to isolate the area of the code under test.
![Page 20: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/20.jpg)
Conjunction of all tests are also tested End-to-End
<End-to-End Test>
Save Button
Save Action
ValidityCheck
ORM DB
… we need to make sure all the dots tie up!(else our wrapped up units might start to diverge)
![Page 21: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/21.jpg)
Thre
e Interaction Based Testing
![Page 22: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/22.jpg)
A better model is the ‘Budding’ model
This object is easy to test as it is naturally isolated.
This object is harder to test in isolation as it forwards calls down the stack.
![Page 23: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/23.jpg)
Problem: Classes that don’t change observable state
Marshellers
Proxies
Caches
![Page 24: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/24.jpg)
Composite
Assert Here
A Composite Object
Behaviour is defined by forwarding calls to composed objects
No observable change in state
![Page 25: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/25.jpg)
Assert Here
Mocking frameworks automate the testing of the interactions between classes
Mock based test
![Page 26: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/26.jpg)
Interaction Based Testing
Rather than testing changes in state, the interaction between objects are asserted.
o = new RuleComposite(mockRuleA, mockRuleB);check(new Expectation(){
oneof(mockRuleA.run(user)); oneof(mockRuleB.run(user)).throws(getExep());};o.run(obj);
B
A
![Page 27: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/27.jpg)
Key
Poin
tMocked objects add additional coupling between test and source as mocks assert how an object behaves towards its collaborators, not just how it changes state or what it returns.
![Page 28: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/28.jpg)
How does this relate to the State based testing with Stubs?
Both allow us to isolate the code under test.
In practice Mocking leads to a very different development process largely because you tend to mock at a class by class level, teasing out roles for collaborating classes.
![Page 29: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/29.jpg)
Mocks used here. No need
to develop Classes yet!
Mocking facilitates a different development process.
Develop Class and Test in a tight loop
Collaborators do not need to be implemented for the test to pass. They are simply mocked.
![Page 30: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/30.jpg)
The ‘Mockist’ approach is different
If the class under test needs to collaborate with another class then a mock is used.
This teases out roles a class requires from its collaborators (similar to Design by Contract).
All classes are tested in complete isolation, demarcated by mocked objects.
The interactions between classes form the primary driver for assertions rather than changes in state.
![Page 31: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/31.jpg)
Four Refactoring Interaction
Tested Code
![Page 32: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/32.jpg)
Mocking Increases Coupling
Using Interaction Based Testing increases the coupling between test and source code.
Tests assert on whether a method is called, with what arguments and how many times.
This breaks encapsulation as the internals of how the class interacts with it’s collaborators is exposed.
Thus, if refactoring changes the way an class interacts with collaborators tests may fail.
![Page 33: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/33.jpg)
Increased Coupling makes refactoring harderSt
ub n
’ Sta
teIn
tera
ction
Ba
sed
Testi
ng
Refactoring a class may change the way it communicates with collaborating classes, breaking interaction based tests.
Refactoring should not change the behavior of a class. Hence state based tests should not break.
![Page 34: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/34.jpg)
Interaction Based Testing is harder. There is more metaphorical rope.
Most horror stories associated with Interaction Based Tests are a result of excessive coupling produced by poor implementation
![Page 35: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/35.jpg)
How Mocking Can Add Unnecessary Coupling?
Mocking Value Objects: An orange is always an orange
![Page 36: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/36.jpg)
How Mocking Can Add Unnecessary Coupling
Complex Constructors: There’s a test trying to get out
![Page 37: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/37.jpg)
Key
Poin
t If good OO principals are not rigorously applied mock driven tests will may become overly complex. The tests are very sensitive to the design
![Page 38: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/38.jpg)
Small classes or classes with low
functional content
Increase ratio between tested interactions and
functional content
Increase the cost of changeInhibit refactoring
Smaller classes / classes with little functionality increase the support burden needlessly.
![Page 39: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/39.jpg)
For Example the Extract Class refactor
Increases the number of interaction points whilst holding the functional content constant
![Page 40: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/40.jpg)
Key
Poin
t The Mockist’s needs to be maintain a balance between the number of interaction based tests and the corresponding functional content.
![Page 41: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/41.jpg)
Five Managing Refactoring in
a Test Driven World
![Page 42: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/42.jpg)
Best Practices for Interaction Based Testing
Don’t mock behaviours that are not relevant to the test (stub them).
Avoid complex constructors, static initialisers or other setup code that crosscuts multiple execution paths.
Only mock classes under your control.
Don’t mock value objects.
You don’t need to mock everything.
![Page 43: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/43.jpg)
You don’t need to mock everything!
Demarcating groups of objects with mocks and using state based testing internally good practice.
A group of collaborating objects are isolated with mocks. Internally state based testing is used.
All interactions are mocked
![Page 44: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/44.jpg)
So What Do We Have?
Unit tests requires isolating the code under test to ensure we get accurate feedback on test failures => Mocks or Stubs.
Both mocks and stubs add a small maintenance burden to the project as they must be kept up to date.
IBT facilitates a different method of doing TDD. It allows you to drive out code for a class without developing its collaborators.
Interaction Based Testing with Mocking Frameworks introduces tighter couplings between test and source. This makes refactoring more difficult.
![Page 45: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/45.jpg)
So What Do We Have?
...But most horror stories resulting from the use of IBTs arise from coupling introduced by poor implementation, not an intrinsic property of the process.
Some of these problems have been highlighted here (complex constructor, mocking value objects etc)
The Mockist approach of applying IBT at a class by class level magnifies poor design.
The Mockist approach encourages isolation at a class level but this is not mandatory. Mixing IBT and state based testing provides a balanced approach.
![Page 46: Refactoring tested code - has mocking gone wrong?](https://reader033.fdocuments.in/reader033/viewer/2022061206/5481cf71b4af9f960d8b4601/html5/thumbnails/46.jpg)
Finally, my personal thoughts…
Mockist TDD is a pleasant process to follow.
I like to start with the stub n’ state approach (using a mocking framework to create the stubs), then add expectations that relate to the particular test.
I also tend not to mock interactions between classes I consider to be closely coupled, I tend to favour state based tests with the demarcation of mocks surrounding the group.
To me the two approaches are not mutually exclusive, you have to have some demarcation. The trick is to know how much to test in isolation and when to assert expectations over stubs