génie logiciel avancé cours 7 kick-starting and...

56
Génie Logiciel Avancé Cours 7 — Kick-starting and Maintaining TDD Stefano Zacchiroli [email protected] Laboratoire PPS, Université Paris Diderot 2014–2015 URL http://upsilon.cc/zack/teaching/1415/gla/ Copyright © 2013–2015 Stefano Zacchiroli License Creative Commons Attribution-ShareAlike 4.0 International License http://creativecommons.org/licenses/by-sa/4.0/deed.en_US Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 1 / 49

Upload: ngohanh

Post on 06-Mar-2018

215 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Génie Logiciel AvancéCours 7 — Kick-starting and Maintaining TDD

Stefano [email protected]

Laboratoire PPS, Université Paris Diderot

2014–2015

URL http://upsilon.cc/zack/teaching/1415/gla/Copyright © 2013–2015 Stefano ZacchiroliLicense Creative Commons Attribution-ShareAlike 4.0 International License

http://creativecommons.org/licenses/by-sa/4.0/deed.en_US

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 1 / 49

Page 2: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Reminder

GOOS, Figure 1.1

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 2 / 49

Page 3: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Reminder (cont.)

GOOS, Figure 5.2

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 2 / 49

Page 4: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Outline

1 Kick-starting TDD

2 Maintaining TDD — part 1Test smellsTest readabilityConstructing complex test dataTest diagnostics

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 3 / 49

Page 5: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Outline

1 Kick-starting TDD

2 Maintaining TDD — part 1Test smellsTest readabilityConstructing complex test dataTest diagnostics

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 4 / 49

Page 6: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

TDD — the bigger picture

It is tempting to use only unit tests to implement TDD, but:

you might end up having a lot of unused well-tested units

you don’t know where to start, nor when to stop

That’s why TDD leverages both acceptance (outer feedback loop)and unit tests (inner feedback loop):

GOOS, Figure 1.2

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 5 / 49

Page 7: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

TDD — the bigger picture

It is tempting to use only unit tests to implement TDD, but:

you might end up having a lot of unused well-tested units

you don’t know where to start, nor when to stop

That’s why TDD leverages both acceptance (outer feedback loop)and unit tests (inner feedback loop):

GOOS, Figure 1.2

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 5 / 49

Page 8: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

End-to-end testing

Acceptance tests should exercise the system end-to-end

black-box testing at system levelñ no instantiation/invocation of internal objectsñ use the system via its interfaces (user interface, external API,

parsing its output and producing its inputs, etc.)

test both the system and its processesñ buildñ deployment in a realistic environment

« don’t trust the results of acceptance tests run in developmentenvironments

ñ any other qualification mechanism« e.g. static analyses, stress testing, benchmark, etc.

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 6 / 49

Page 9: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Testing, quality, feedback

External quality: howwell the system meetsthe needs of its users

Internal quality: howwell the system meetsthe needs of itsdevelopers

e.g. good design:low coupling & highcohesion

it is often harder topush for internalthan external quality,but we need to do soto cope with changes

GOOS, Figure 1.3

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 7 / 49

Page 10: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

First feature paradox

Writing the first acceptance test at the beginning of a project isproblematic:

we want to test end-to-end the system and its processesbut we don’t have yet the tooling to make the test fail

To get out of the paradox we compromise a bit, implementing awalking skeleton to kick start TDD.

Definition (walking skeleton)

An implementation of the smallest possible part of real functionalitythat we can automatically build, deploy, and test end-to-end.

To implement the walking skeleton we need to automate a lot ofprocesses. That will force us to understand them better.

Example

The walking skeleton of a DBMS-backed web application will justshow a static “Hello, World” web page.

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 8 / 49

Page 11: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

First feature paradox

Writing the first acceptance test at the beginning of a project isproblematic:

we want to test end-to-end the system and its processesbut we don’t have yet the tooling to make the test fail

To get out of the paradox we compromise a bit, implementing awalking skeleton to kick start TDD.

Definition (walking skeleton)

An implementation of the smallest possible part of real functionalitythat we can automatically build, deploy, and test end-to-end.

To implement the walking skeleton we need to automate a lot ofprocesses. That will force us to understand them better.

Example

The walking skeleton of a DBMS-backed web application will justshow a static “Hello, World” web page.

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 8 / 49

Page 12: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Walking skeleton

(Some of the) tasks to be completed as part of a walking skeleton:

create a VCS repository, check in the codeñ requirements: choose Version Control System, choose hosting

automate the build processñ requirement: choose build tool (e.g. Ant, Maven)ñ note: “just click a button in Eclipse” ≠ automation

automate deployment in a realistic environmentñ requirement: choose packaging/deployment mechanisms

automate test executionñ requirement: choose test frameworkñ again: “just click a button in Eclipse” ≠ automation

. . .

iteration 0: implement, deploy, test first feature

Yes, it’s a lot of work!

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 9 / 49

Page 13: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Kick-starting TDD

GOOS, Figure 4.2

Note: “Broad-Brush Design” ≠ “Big Design Up Front (BDUF)”

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 10 / 49

Page 14: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

TDD as a whole

Periodically reassess both your understanding of the problem andthe toolchain

GOOS, Figure 4.2

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 11 / 49

Page 15: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

TDD as a whole

Periodically reassess both your understanding of the problem andthe toolchain

GOOS, Figure 4.3

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 11 / 49

Page 16: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Test suites organization

unit and integration test suitesñ should always passñ should run fast

acceptance test suiteñ catch regressionsñ should always passñ might take longer to run

new acceptance test suiteñ corresponds work in progressñ will keep on failing during inner loop iterations

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 12 / 49

Page 17: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Outline

1 Kick-starting TDD

2 Maintaining TDD — part 1Test smellsTest readabilityConstructing complex test dataTest diagnostics

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 13 / 49

Page 18: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Listen to the tests

you encounter a featurewhich is difficult to test

don’t work around that,investigate why it is the case

often, that leads to missedrefactoring from previousiterations of the inner loop

ñ potential futuremaintenance problem

take the chance and do therefactoring GOOS, Figure 5.3

More generally: notice test smells and let them guide your design (ifthe feature is yet to be implemented) or your refactoring (otherwise)

note: in the following, most of the code examples are from the GOOS book

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 14 / 49

Page 19: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Sommaire

1 Kick-starting TDD

2 Maintaining TDD — part 1Test smellsTest readabilityConstructing complex test dataTest diagnostics

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 15 / 49

Page 20: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Tell, don’t ask

“Train wreck” code: series of getters chained together like thecarriages in a train:

( ( EditSaveCustomizer ) master . getModelisable ( ). getDockablePanel ( )

. getCustomizer ( ) ). getSaveItem ( ) . setEnabled ( Boolean . FALSE . booleanValue ( ) ) ;

what it actually means:

master . allowSavingOfCustomisations ( ) ;

“Tell, don’t ask” principle

Don’t ask (recursively) access to objects internals that allow you toperform a specific operation. Rather, tell the (outermost) object todo something on your behalf; let it do the same, recursively, asneeded.

This makes your tests (and code in general) more resistant tochanges in object organization.Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 16 / 49

Page 21: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Tell, don’t ask (cont.)

When you really have to ask, do so explicitly via well-defined querymethods (i.e. queries that have clear names and well-definedsemantics):

public void reserveSeats ( ReservationRequest request ) {for ( Carriage carriage : carriages ) {

i f ( carriage . getSeats ( ) . getPercentReserved ( ) < percentReservedBarrier ) {request . reserveSeatsIn ( carriage ) ;return ;

} }request . cannotFindSeats ( ) ; }

⇓public void reserveSeats ( ReservationRequest request ) {

for ( Carriage carriage : carriages ) {i f ( carriage .hasSeatsAvailableWithin(percentReservedBarrier) ) {

request . reserveSeatsIn ( carriage ) ;return ;

} }request . cannotFindSeats ( ) ; }

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 17 / 49

Page 22: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Object relationships

When writing tests and designing OO systems, the followingclassification of object relationships come in handy [GOOS]:

Dependencies: services that the object requires from its peers so itcan perform its responsibilities. Object cannot functionwithout these services. It should not be possible tocreate the object without them.

Notifications: peers that need to be kept up to date with the object’sactivity. Notifications are fire-and-forget (i.e. can belost) and the notifier doesn’t care about which peers arelistening.

Adjustments: peers that adjust the object’s behaviour to the widerneeds of the system. E.g. policy objects (cfr. thestrategy design pattern).

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 18 / 49

Page 23: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Singletons are dependencies

public boolean acceptRequest ( Request request ) {f inal Date now = new Date();i f ( dateOfFirstRequest == null ) {

dateOfFirstRequest = now;} else i f ( f irstDateIsDifferentFrom (now) ) {

return false ;}// process the requestreturn true ;

}

@Test public void rejectsRequestsNotWithinTheSameDay ( ) {receiver . acceptRequest ( FIRST_REQUEST ) ;// the next day ← how do we implement this?assertFalse ( " too late now" ,

receiver . acceptRequest (SECOND_REQUEST ) ) ;}

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 19 / 49

Page 24: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Singletons are dependencies (cont.)

Problem: singletons hide dependencies — not surprising at all, assingletons are global variables.Solution: make the dependency explicit.

public boolean acceptRequest ( Request request ) {f inal Date now = clock.now();i f ( dateOfFirstRequest == null ) {

dateOfFirstRequest = now;} else i f ( f irstDateIsDifferentFrom (now) ) {

return false ;}// process the requestreturn true ;

}@Test public void rejectsRequestsNotWithinTheSameDay ( ) {

Receiver receiver = new Receiver(stubClock);stubClock . setNextDate (TODAY) ;receiver . acceptRequest ( FIRST_REQUEST ) ;stubClock . setNextDate (TOMORROW) ;assertFalse ( " too late now" , receiver . acceptRequest (SECOND_REQUEST ) ) ;

}

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 20 / 49

Page 25: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Singletons are dependencies (cont.)

Problem: singletons hide dependencies — not surprising at all, assingletons are global variables.Solution: make the dependency explicit.

public boolean acceptRequest ( Request request ) {f inal Date now = clock.now();i f ( dateOfFirstRequest == null ) {

dateOfFirstRequest = now;} else i f ( f irstDateIsDifferentFrom (now) ) {

return false ;}// process the requestreturn true ;

}@Test public void rejectsRequestsNotWithinTheSameDay ( ) {

Receiver receiver = new Receiver(stubClock);stubClock . setNextDate (TODAY) ;receiver . acceptRequest ( FIRST_REQUEST ) ;stubClock . setNextDate (TOMORROW) ;assertFalse ( " too late now" , receiver . acceptRequest (SECOND_REQUEST ) ) ;

}Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 20 / 49

Page 26: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Singletons are dependencies (cont.)

The general problem highlighted by singletons is that hidingdependencies (e.g. via global variables) does not make themdisappear. They are still there, ready to strike back—e.g. when, totest the code, you need to change/fake them.

Dependencies — golden rule

An object should only deal with values that are either local(i.e. created and managed within its scope) or passed in explicitly(e.g. at construction or method invocation-time).

This is the key rule to achieve context independent objects.

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 21 / 49

Page 27: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Bloated constructor

By adding one dependency at a time we might end up with:

public class MessageProcessor {public MessageProcessor (MessageUnpacker unpacker ,

AuditTrai l auditor ,CounterPartyFinder counterpartyFinder ,LocationFinder locationFinder ,DomesticNotifier domesticNotifier ,ImportedNotifier importedNotifier )

{ /* set the f i e lds here */ }

public void onMessage (Message rawMessage ) {UnpackedMessage unpacked =

unpacker . unpack ( rawMessage , counterpartyFinder ) ;auditor . recordReceiptOf (unpacked ) ;// some other ac t i v i ty herei f ( locationFinder . isDomestic (unpacked ) ) {

domesticNotifier . not i fy (unpacked . asDomesticMessage ( ) ) ;} else {

importedNotifier . not i fy (unpacked . asImportedMessage ( ) )}

}Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 22 / 49

Page 28: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Bloated constructor (cont.)

Solution #1: look for objects that are always used together ⇒ theyprobably denote a single concept that should be factorized out.

counterpartyFinder always used with unpacker? then

UnpackedMessage unpacked = unpacker.unpack(rawMessage);

locationFinder + 2 notifiers always used together? then

public MessageProcessor (MessageUnpacker unpacker ,AuditTrai l auditor ,MessageDispatcher dispatcher )// much better!

{ /* set the f i e lds here */ }

public void onMessage (Message rawMessage ) {UnpackedMessage unpacked =

unpacker . unpack ( rawMessage ) ;auditor . recordReceiptOf (unpacked ) ;// some other ac t i v i ty heredispatcher . dispatch (unpacked ) ;

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 23 / 49

Page 29: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Confused object

public class Handset {public Handset (Network network , Camera camera , Display display ,

DataNetwork dataNetwork , AddressBook addressBook ,Storage storage , Tuner tuner , . . . )

{ /* set the f i e lds here */ }public void placeCallTo ( DirectoryNumber number) {

network . openVoiceCallTo (number ) ;}public void takePicture ( ) {

Frame frame = storage . allocateNewFrame ( ) ;camera . takePictureInto ( frame ) ;display . showPicture ( frame ) ;

}public void showWebPage(URL ur l ) {

display . renderHtml ( dataNetwork . retrievePage ( ur l ) ) ;}public void showAddress ( SearchTerm searchTerm ) {

display . showAddress ( addressBook . findAddress ( searchTerm ) ) ;}public void playRadio ( Frequency frequency ) {

tuner . tuneTo ( frequency ) ;tuner . play ( ) ;

}// . . .

}Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 24 / 49

Page 30: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Confused object (cont.)

Another instance of bloated constructor.

Different problem: the object has too many responsibilities.

Solution #2: split distinct objects, each one with a singleresponsibility, possibly assemblying objects back using containerswhere needed. The resulting objects can now be tested separatelyand more easily — in particular test setup is much easier.

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 25 / 49

Page 31: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Alleged dependencies

public class RacingCar {private final Track track ;private Tyres tyres ;private Suspension suspension ;private Wing frontWing ;private Wing backWing ;private double fuelLoad ;private CarListener l i s tener ;private DrivingStrategy driver ;public RacingCar ( Track track , DrivingStrategy driver , Tyres tyres ,

Suspension suspension , Wing frontWing , Wing backWing ,double fuelLoad , CarListener l i s tener )

{this . track = track ;this . dr iver = driver ;this . tyres = tyres ;this . suspension = suspension ;this . frontWing = frontWing ;this . backWing = backWing ;this . fuelLoad = fuelLoad ;this . l i s tener = l i s tener ;

}}

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 26 / 49

Page 32: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Alleged dependencies (cont.)

Another instance of bloated constructor.Different problem: the object declares unneeded dependencies,i.e. some of them are not real dependencies. Objects can in fact beinstantiated without providing them.Solution #3: make arguments optional and rely on sane defaultvalues and getters/setters.

private DrivingStrategy driver = DriverTypes . borderlineAggressiveDriving ( ) ;private Tyres tyres = TyreTypes . mediumSlicks ( ) ;private Suspension suspension = SuspensionTypes . mediumStiffness ( ) ;private Wing frontWing = WingTypes .mediumDownforce ( ) ;private Wing backWing = WingTypes .mediumDownforce ( ) ;private double fuelLoad = 0.5;private CarListener l i s tener = CarListener .NONE;

public RacingCar ( Track track ) { this . track = track ; }

public void setSuspension ( Suspension suspension ) { /* . . . */public void setTyres ( Tyres tyres ) { /* . . . */public void setEngine ( Engine engine ) { /* . . . */public void setListener ( CarListener l i s tener ) { /* . . . */

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 27 / 49

Page 33: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Sommaire

1 Kick-starting TDD

2 Maintaining TDD — part 1Test smellsTest readabilityConstructing complex test dataTest diagnostics

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 28 / 49

Page 34: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Test readability

In general, we strive for readable (production) code.

developers spend more time reading code than writing it

readable code ⇒ code that is easy to change/debug/fix

For the same reasons, we want readable tests.

tests break for valid reasons (refactoring, regressions, etc.)

when that happens we want to be able to fix them easily

otherwise TDD becomes a burden; we will be tempted to:ñ @Ignore specific testsñ drop TDD as a whole

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 29 / 49

Page 35: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Test readability (cont.)

On the other hand: test readability has different goals than(production) code readability

Production code readability

Production code should be abstract on the values it manipulates, butconcrete about how it gets the job done

Test readability

Tests describe what the production code does; test should read asdeclarative descriptions of what is being tested

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 29 / 49

Page 36: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Test names describe feature

public class TargetObjectTest // very bad convention// no information conveyed

@Test public void test1 ( ) {@Test public void test2 ( ) {@Test public void test3 ( ) {

public class TargetObjectTest { // bad convention too// you should test features , not methods

@Test public void isReady ( ) { [ . . . ]@Test public void choose ( ) { [ . . . ]@Test public void choose1 ( ) { [ . . . ]

public class TargetObject {public void isReady ( ) { [ . . . ]public void choose ( Picker picker ) { [ . . . ]

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 30 / 49

Page 37: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

TestDox

A good naming convention for test is known as TestDox.each test name reads like a sentenceeach test has the target class as implicit subject

E.g.:a List holds items in the order they were addeda List can hold multiple references to the same itema List throws an exception when removing a missing item

public class ListTests {@Test public voidholdsItemsInTheOrderTheyWereAdded ( ) { //

@Test public voidcanHoldMultipleReferencesToTheSameItem ( ) { //

@Test public voidthrowsAnExceptionWhenRemovingAnItemItDoesntHold ( ) { //

Note: we don’t care about name length. Test names are discoveredvia reflection, we never have to type them.Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 31 / 49

Page 38: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

TestDox — IDE support

Once a convention like TestDox is adopted, IDE support can beadded to it, e.g.:

GOOS, Figure 21.1

Figure: TestDox IntelliJ plug-in

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 32 / 49

Page 39: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Canonical test structure

1 Setup: prepare the test context (environment, input, etc.)2 Execute: call target code3 Verify: check visible effects of target code4 Teardown: (optional) cleanup leftover state that could break test

isolation

Leaving teardown implicit, an equivalent mnemonic is Arrange, Act,Assert.

public class StringTemplateTest {@Test public void expandsMacrosSurroundedWithBraces ( ) {

StringTemplate template =new StringTemplate ( " { a } { b } " ) ; // 1. setup

HashMap<String , Object> macros = new HashMap<String , Object > ( ) ;macros . put ( "a " , "A" ) ;macros . put ( "b" , "B" ) ;

Str ing expanded = template .expand(macros ) ; // 2. executeassertEquals (expanded , "AB" ) ; // 3. assert

} // 4. (no) teardown}Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 33 / 49

Page 40: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Canonical test structure (cont.)

Recommended test writing order:1 start by writing the test name (helps to clarify the goal)

2 write target code invocation

3 write assertions4 fill in the gaps, i.e. setup and teardown

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 34 / 49

Page 41: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Literals and variables

Test code tends to be more concrete than production code⇒ test code tends to have more literal values.

Book book = catalog . lookup (666);assertNull (book ) ;

Problem: a literal value does not describe its role(unless it’s really really obvious, which is almost never the case).Solution: use constants with self-describing names instead of literals

public static f inal int INVALID_BOOK_ID = 666;

Book book = catalog . lookup ( INVALID_BOOK_ID ) ;assertNull (book ) ;

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 35 / 49

Page 42: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Sommaire

1 Kick-starting TDD

2 Maintaining TDD — part 1Test smellsTest readabilityConstructing complex test dataTest diagnostics

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 36 / 49

Page 43: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Complex test data

@Test public voidchargesCustomerForTotalCostOfAllOrderedItems ( ) {

Order order = new Order (new Customer ( " Sherlock Holmes" ,

new Address ( "221b Baker Street " ,"London" ,new PostCode ( "NW1" , "3RX" ) ) ) ) ;

order . addLine (new OrderLine ( " Deerstalker Hat " , 1 ) ) ;order . addLine (new OrderLine ( "Tweed Cape" , 1 ) ) ;// . . .

}

Effects:

tests with complex setup logic ⇒ hard to read

tests which will break often, not for good reasonsñ changes in constructor arguments, object structure, etc.

How can we be more resilient to this kind of changes?

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 37 / 49

Page 44: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Object mother pattern

Order order = ExampleOrders . newDeerstalkerAndCapeOrder ( ) ;

Good for readability!

. . . but doesn’t scale well to increase in variability:

Order order1 =ExampleOrders . newDeerstalkerAndCapeAndSwordstickOrder ( ) ;

Order order2 = ExampleOrders . newDeerstalkerAndBootsOrder ( ) ;

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 38 / 49

Page 45: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Test data builders

A better solution is to apply the builder pattern to test data.

We create a test data builder that has:

a field for each constructor parameter, set to sane defaults

chainable public methods for overwriting its default values

a build() factory method, to be called last

Note: this is essentially a functional version of the builder pattern.

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 39 / 49

Page 46: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Test data builders — examplepublic class OrderBuilder {

private Customer customer = new CustomerBuilder ( ) . build ( ) ;private List <OrderLine> l ines = new ArrayList <OrderLine > ( ) ;private BigDecimal discountRate = BigDecimal .ZERO;public static OrderBuilder anOrder() {

return new OrderBuilder ( ) ; }public OrderBuilder withCustomer (Customer customer ) {

this . customer = customer ;return this ; }

public OrderBuilder withOrderLines ( OrderLines l ines ) {this . l ines = l ines ;return this ; }

public OrderBuilder withDiscount ( BigDecimal discountRate ) {this . discountRate = discountRate ;return this ; }

public Order build ( ) {Order order = new Order ( customer ) ;for ( OrderLine l ine : l ines ) {

order . addLine ( l ine ) ;order . setDiscountRate ( discountRate ) ; }

return order ;} }Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 40 / 49

Page 47: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Test data builders — example (cont.)

Order order1 = new OrderBuilder ( ) . build ( ) ;

Order order2 = newOrderBuilder ( ) . withDiscount (6 .7 ) . build ( ) ;

tests are more readable, due to reduced syntactic noise

the default case is simple, the non-default cases stand-off

tests are protected against changes in object structure

errors are easier to spot, e.g.:

TestAddresses .newAddress ( "221b Baker Street " , "London" , "NW1 6XE" ) ;

vs

new AddressBuilder ( ). withStreet ( "221b Baker Street " ). withStreet2 ( "London" ). withPostCode ( "NW1 6XE" ). build ( ) ;

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 41 / 49

Page 48: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Creating similar objects

We can use test data builders to factorize the creation of similarobjects:

Order orderWithSmallDiscount = new OrderBuilder ( ). withLine ( " Deerstalker Hat " , 1). withLine ( "Tweed Cape" , 1). withDiscount (0.10). build ( ) ;

Order orderWithLargeDiscount = new OrderBuilder ( ). withLine ( " Deerstalker Hat " , 1). withLine ( "Tweed Cape" , 1). withDiscount (0.25). build ( ) ;

⇓OrderBuilder hatAndCape = new OrderBuilder ( )

. withLine ( " Deerstalker Hat " , 1)

. withLine ( "Tweed Cape" , 1 ) ;

I.e.: builders as higher-order functions (take #1)

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 42 / 49

Page 49: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Creating similar objects — side-effects FAIL!

Order orderWithDiscount = hatAndCape . withDiscount (0 .10) . build ( ) ;Order orderWithGiftVoucher =

hatAndCape . withGiftVoucher ( "abc " ) . build ( ) ;

Will you get a discount for orderWithGiftVoucher?

Yes :-(Solution #1: copy constructor

Order orderWithDiscount = new OrderBuilder(hatAndCape). withDiscount (0 .10) . build ( ) ;

Order orderWithGiftVoucher = new OrderBuilder(hatAndCape). withGiftVoucher ( "abc " ) . build ( ) ;

Solution #2: factory method that returns the builder

Order orderWithDiscount =hatAndCape.but() . withDiscount (0 .10) . build ( ) ;

Order orderWithGiftVoucher =hatAndCape.but() . withGiftVoucher ( "abc " ) . build ( ) ;

I.e. builders as higher-order functions (take #2)

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 43 / 49

Page 50: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Creating similar objects — side-effects FAIL!

Order orderWithDiscount = hatAndCape . withDiscount (0 .10) . build ( ) ;Order orderWithGiftVoucher =

hatAndCape . withGiftVoucher ( "abc " ) . build ( ) ;

Will you get a discount for orderWithGiftVoucher?Yes :-(Solution #1: copy constructor

Order orderWithDiscount = new OrderBuilder(hatAndCape). withDiscount (0 .10) . build ( ) ;

Order orderWithGiftVoucher = new OrderBuilder(hatAndCape). withGiftVoucher ( "abc " ) . build ( ) ;

Solution #2: factory method that returns the builder

Order orderWithDiscount =hatAndCape.but() . withDiscount (0 .10) . build ( ) ;

Order orderWithGiftVoucher =hatAndCape.but() . withGiftVoucher ( "abc " ) . build ( ) ;

I.e. builders as higher-order functions (take #2)Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 43 / 49

Page 51: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Combining builders

Nested .build() invocations are hard to read (syntactic noise,again). We can avoid them by passing builders around, instead ofthe objects built by them.

Order orderWithNoPostcode = new OrderBuilder ( ). withCustomer (

new CustomerBuilder ( ). withAddress (new AddressBuilder ( ) . withNoPostcode ( ) . build ( ) ). build ( ) )

. build ( ) ;

⇓Order order = new OrderBuilder ( )

. withCustomer (new CustomerBuilder ( )

. withAddress (new AddressBuilder().withNoPostcode() ) ). build ( ) ;

I.e. builders as higher-order functions (take #3)

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 44 / 49

Page 52: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Sommaire

1 Kick-starting TDD

2 Maintaining TDD — part 1Test smellsTest readabilityConstructing complex test dataTest diagnostics

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 45 / 49

Page 53: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Diagnostic — reminder

GOOS, Figure 5.2

Test readability wasconcerned with a staticquality of tests asdocumentation.

Test diagnostics isconcerned with a dynamicquality: how informativetheir runtime results are.

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 46 / 49

Page 54: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Explanatory assertions

Customer customer = order . getCustomer ( ) ;assertEquals ( "573242" , customer . getAccountId ( ) ) ;assertEquals (16301, customer . getOutstandingBalance ( ) ) ;

// result in test fa i lures l i ke :ComparisonFailure : expected:<[16301]> but was:<[16103]>

⇓assertEquals ("account id" , "573242" , customer . getAccountId ( ) ) ;assertEquals ("outstanding balance" , 16301,

customer . getOustandingBalance ( ) ) ;

// result in :ComparisonFailure :outstanding balance expected:<[16301]> but was:<[16103]>

which one do you prefer?

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 47 / 49

Page 55: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Self-describing values

As an alternative, we can instrument values to be self-describing

Date startDate = new Date(1000);Date endDate = new Date(2000);

// result in :ComparisonFailure : expected : <Thu Jan 01 01:00:01 GMT 1970>

but was : <Thu Jan 01 01:00:02 GMT 1970>

it is correct, but doesn’t tell us the meaning of these dates for thepurposes of the test ⇓Date startDate = namedDate(1000, " startDate " ) ;Date endDate = namedDate(2000, "endDate" ) ;

Date namedDate( long timeValue , f inal String name) {return new Date ( timeValue ) {

public String toString ( ) { return name; } } ;}

// result in :ComparisonFailure : expected : <startDate> but was : <endDate>

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 48 / 49

Page 56: Génie Logiciel Avancé Cours 7 Kick-starting and ...zack/teaching/1415/gla/cours-05-tdd-maint.pdf · End-to-end testing Acceptance tests shouldexercise the system end-to-end black-box

Obviously canned value

Sometimes you cannot instrument values to become self-describing

e.g.: primitive data types in Java

In those cases, try to use obviously canned values, i.e. values thatwhen read will trigger reactions like “this cannot possibly be acorrect value”, e.g.:

negative values (where everyone would expect a positive one)

Integer.MAX_VALUE

. . .

Stefano Zacchiroli (Paris Diderot) Maintaining TDD 2014–2015 49 / 49