Title: Retrofitting Unit Tests
1Retrofitting Unit Tests
- Steve FreemanXtC, Thomson Financial
- Paul SimmonsXtC
2Motivation
- Stroking his chin sagely the old man replied,
- Well now, if I was going there I wouldnt be
starting from here.
3Retrofitting Unit Tests
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
- What is Retrofitting?
- Box of fears
- Show me the code
- Opening the box
- Retrofitting Revisited
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
4Why Unit Tests?
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
- Powerful design/specification technique
- Focus on what matters
- Executable documentation
- Support for code maintenance
- Refactor with (justified) confidence
- Quality is free
- Cuts debug time
5Why retrofit Unit Tests?
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
- The Scenario
- youre working on legacy code
- i.e. anything without unit tests
- you need to make changes
- The Dilemma
- no testing safety net to support agility
- complete test suite is expensive takes time
- code not suitable for testing, anyway
6Box of Fears
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
7A Three-Step Approach
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
- Some judicious functional testing.
- To catch gross errors
- Identify code smells
- Long methods, Singletons, c, c.
- Carefully refactor
- without unit tests!
8Code Example (i)
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
- Smells Singleton, Static Initialiser
- Refactor Pass singletons through, Replace class
with interface - What does the test look like?
- Why bother?
9Singleton
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
cashMachine.withdraw(200) public class
CashMachine private User user
User.getCurrent() public void withdraw(int
amount) accountManager.debit(user.getAccou
ntId(), amount)
- Cannot substitute User for testing
- Worse if creation of singleton User is also
private - Often, User is a catch-all class with lots of
irrelevant detail
10Refactor pass singleton through
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
cashMachine.withdraw(200, User.getCurrent()) pub
lic class CashMachine //private User user
User.getCurrent() public void withdraw(int
amount, User user) accountManager.debit(us
er.getAccountId(),
amount)
- Start to pull out global state
- Still too hard to substitute a User
11User
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
public class User current // create
singleton public AccountId getAccountId()
static public User getCurrent()
return current
- No external control over how Users are created
- Hidden away in static code
12Refactor introduce interface
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
public interface AccountHolder AccountId
getAccountId() public class User implements
AccountHolder public AccountId getAccountId()
static public User getCurrent()
return current
- Separate out that part of the object we need
- Why? Types with clear responsibilities
13Before use of interface
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
cashMachine.withdraw(200, User.getCurrent()) pub
lic class CashMachine public void
withdraw(int amount, User user)
accountManager.debit(user.getAccountId(),
amount)
- Start to pull out global state
- Still too hard to substitute a User
14Refactor apply interface
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
cashMachine.withdraw(200, User.getCurrent()) pub
lic class CashMachine public void
withdraw(int amount,
AccountHolder accountHolder)
accountManager.debit( accountHolder.getAc
countId(), amount)
- Code is more focussed
- Can substitute other implementations
15enables unit testing
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
public void testWithdrawal() final AccountId
accountId new AccountId() AccountHolder
holderStub new AccountHolder() public
AccountId getAccountId() return
accountId cashMachine.setAccountMana
ger(mockAcctManager) mockAcctManager.setExpecte
dWithdraw( accountId,
200) cashMachine.withdraw(200, holderStub)
mockAcctManager.verify()
1. 2. 3. 4. 5.
16Why bother (i)?
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
- Now we can test the cash machine in isolation
- Similarly for the accountManager
- Better modularity means less test setup
- Testable in isolation
- No need to refer to external resources
(databases, files) - More focussed code is easier to think about
17Code Example (ii)
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
- Smells bleeding across layers
- Refactor introduce interface
18Bleeding across layers
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
public class BillPaymentScreen private
MainScreen main public void payBill(String
payee, Amount amount) Company company
companies.lookupCode(payee)
main.status.setText("Paying " company)
billPayer.pay(company, amount)
main.status.setText(company " paid") ...
main.status.setForeground(Color.Red)
main.status.setText("Could not pay bill")
- Difficult to test payBill() in isolation
- Cannot create a BillPaymentScreen without a
MainScreen - Behaviour and GUI feedback all mixed up
19Refactor introduce interface
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
public class BillPaymentScreen private
StatusPresenter status public void
payBill(String payee, Amount amount)
Company company companies.lookupCode(payee)
status.show("Paying " company)
billPayer.pay(company, amount)
status.show(company " paid") ...
status.warn("Could not pay bill") ...
- Clarifies BillPaymentScreen dependencies
- Not really interested in implementation of
StatusPresenter - First step towards breaking out domain model
20ScreenStatusPresenter
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
public class ScreenStatusPresenter extends
JTextArea implements StatusPresenter public
void show(String message)
setText(message) public void warn(String
message) setForeground(Color.Red)
setText(message)
- Have MainScreen implement StatusPresenter itself
- Or, replace basic text area field in MainScreen
- New methods describe intention, not just
implementation
21...enables unit testing
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
MockStatusPresenter mockStatus new
MockStatusPresenter() BillPaymentScreen
paymentScreen new BillPaymentScreen(mockStat
us) public void testPayBill()
mockStatus.addExpectedShow("Paying Tuesday
Club") mockStatus.addExpectedShow("Tuesday
Club paid") mockBillPayer.expectPay(tuesdayClub
, 150) paymentScreen.payBill("xtc", 150)
mockBillPayer.verify() mockStatus.verify()
22MainScreen
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
public class MainScreen private
ScreenStatusPresenter status private
BillPaymentScreen billPayment ... private
void setupDisplay() status new
ScreenStatusPresenter() billPayment new
BillPaymentScreen(status) addComponent(billPa
yment) addComponent(status) ...
- Start introducing structure to MainScreen
- Clarify status presentation for whole
application
23Why bother (ii)?
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
- Now we can unit test the BillPaymentScreen
- Don't have to create the whole GUI
- Begun to clarify model vs. View
- StatusPresenter becomes a model listener
- Introducing modularity into the GUI
- Isolating all the status handling
- Do the same for other features
24Code Smells
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
- Code Smells
- Singleton
- Complex construction
- Static Initialization
- Bleeding across layers
- Classes as parameters
- Data class
- Imprecise exceptions
- Long method
- Refactorings
- Pass singletons through
- Separate construction from use
- Remove static initializers
- Weaken dependancies between layers
- Replace class with interface
25Open the Box of Fears
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
26Retrofitting Revisited
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
- Retrofitting unit tests is painful and expensive,
but necessary - What really matters is attitude
- and someone to watch your back
- You can chip away at your code base
- Build up a test suite incrementally
- Sometimes its better to rewrite
- On the small scale
27Retrofitting Unit Tests
Retrofitting?
Box of Fears
Code Examples
Open the Box
Revisited
If you do not start adding unit tests today
then one year from now you will still not have a
good unit test suite. Don Wells