refactoring in tdd the missing part

97
Test Driven Development

Upload: gabriele-lana

Post on 20-May-2015

4.490 views

Category:

Technology


1 download

TRANSCRIPT

Page 1: Refactoring In Tdd The Missing Part

TestDrivenDevelopment

Page 2: Refactoring In Tdd The Missing Part

Debugging Sucks

Testing Rocks

Page 3: Refactoring In Tdd The Missing Part

Cos

t

Bugfixing

Bugs

• Cost of bugfix = Number of bugs x Cost per fix, but...

• How many bugs do you plan to have?

• How hard do you think they are to fix?

Page 4: Refactoring In Tdd The Missing Part

Cos

t

Bugfixing

Bugs/Features

Tests

• Cost of tests = Number and complexity of features

• How many features do you plan to have?

• How complex they are?

Page 5: Refactoring In Tdd The Missing Part

TestAutomationLifecycle

Page 6: Refactoring In Tdd The Missing Part

all the things we need to have in place in order to run a test and expect

a particular outcome

Fixture

Page 7: Refactoring In Tdd The Missing Part

Fixture Setup

Steps

Fixture Setup

Fixture

Page 8: Refactoring In Tdd The Missing Part

System Under Testis whatever thing we

are testing

SUT

Page 9: Refactoring In Tdd The Missing Part

Exercise SUT

Steps

Exercise SUT

Fixture Setup

Fixture

SUT

Page 10: Refactoring In Tdd The Missing Part

Steps

Exercise SUT

Fixture Setup

Verify Result

Fixture

SUT

Verify Result

Page 11: Refactoring In Tdd The Missing Part

“Don't call us, we'll call you”

Page 12: Refactoring In Tdd The Missing Part

Steps

Exercise SUT

Fixture Teardown

Fixture Setup

Verify Result

Fixture

SUT

Fixture Teardown

Page 13: Refactoring In Tdd The Missing Part

• Is really automatic

• Should be easy to invoke one or more tests

• Must determine for itself whether it passed or failed

• Test everything that’s likely to break

• Must be independent from the environment and each other test

• Should be repeatable, could be run over and over again, in any order and produce the same results

• The code is clean as the production code

a test is a good one if...

Page 14: Refactoring In Tdd The Missing Part

the kind of test is not determined by the used tool

Page 15: Refactoring In Tdd The Missing Part

Unit testsA test is not a unit test if:

1. It talks to a database

2. It communicates across the network

3. It touches the file system

4. You have to do things to your environment to run it (eg, change config files)

Tests that do this are integration tests

Michael Feathers

Page 16: Refactoring In Tdd The Missing Part

public void marriageIsSimmetric() { Customer alice = new Customer("alice"); Customer bob = new Customer("bob"); bob.marry(alice); assertTrue(bob.isMarriedTo(alice)); assertTrue(alice.isMarriedTo(bob)); }

Simple Unit Test

Page 17: Refactoring In Tdd The Missing Part

public void marriageIsSimmetric() { Customer alice = new Customer("alice"); Customer bob = new Customer("bob"); bob.marry(alice); assertTrue(bob.isMarriedTo(alice)); assertTrue(alice.isMarriedTo(bob)); }

Fixture Setup

Page 18: Refactoring In Tdd The Missing Part

public void marriageIsSimmetric() { Customer alice = new Customer("alice"); Customer bob = new Customer("bob"); bob.marry(alice); assertTrue(bob.isMarriedTo(alice)); assertTrue(alice.isMarriedTo(bob)); }

Exercise SUT

Page 19: Refactoring In Tdd The Missing Part

public void marriageIsSimmetric() { Customer alice = new Customer("alice"); Customer bob = new Customer("bob"); bob.marry(alice); assertTrue(bob.isMarriedTo(alice)); assertTrue(alice.isMarriedTo(bob)); }

Verify Result

Page 20: Refactoring In Tdd The Missing Part

Test-Driven Development

Page 21: Refactoring In Tdd The Missing Part

As a developer,I want to learn TDD,so that I can write

clean code that works

Page 22: Refactoring In Tdd The Missing Part

Clean code that works

Clean code is simple and direct. Clean code reads like well-written prose. Clean code never

obscures the designer’s intent but rather is full of crisp abstractions and straightforward lines

of control

Grady Booch

Page 23: Refactoring In Tdd The Missing Part

Clean code that worksClean code always looks like it was written by someone who cares. There is nothing obvious that you can do to make it better. All of those

things were thought about by the code’s author, and if you try to imagine improvements,

you’re led back to where you are, sitting in appreciation of the code someone left for you,

code left by someone who cares deeply about the craft.

Michael Feathers

Page 24: Refactoring In Tdd The Missing Part

Clean code that works

You know you are working on clean code when each routine you read turns out to be pretty

much what you expected. You can call it beautiful code when the code also makes it look like the

language was made for the problem

Ward Cunningham

Page 25: Refactoring In Tdd The Missing Part

Simple design

The code is simple enough when it:0. Pass all the tests1. Expresses every idea that we need to express2. Contains no duplication3. Has the minimum number of classes and functions

(In this order)

Adapted from Extreme Programming Installed by Ron Jeffries et al.

Page 26: Refactoring In Tdd The Missing Part

Clean code that works

• First we'll solve the “that works” part

• Then we'll solve the “clean code” part

Page 27: Refactoring In Tdd The Missing Part

Code that works could Smell

Refactoring by Martin Fowler

Page 28: Refactoring In Tdd The Missing Part

Is the process of changing a software system in such a way that it does not

alter the external behaviour of the code yet improves its internal structure

... then Refactor

Martin Fowler

Page 29: Refactoring In Tdd The Missing Part

Refactoring by Martin Fowler

through small steps

Page 30: Refactoring In Tdd The Missing Part

... fight the Smells

Smell Common Refactorings

Duplicated Code Extract Method, Extract Class, Pull-Up Method, Template Method

Feature Envy Move Method, Move Field, Extract Method

Large ClassExtract Class, Extract Subclass, Extract Interface, Replace Data

Value with Object

Long MethodExtract Method, Replace Temporary Variable with Query, Replace

Method with Method Object, Decompose Conditional

Page 31: Refactoring In Tdd The Missing Part

Smell Common Refactorings

Shotgun Surgery Move Method, Move Field, Inline Class

Long Parameter ListReplace Parameter with Method, Introduct Parameter Object,

Preserve Whole Object

Data Class Move Method, Encapsulate Field, Encapsulate Collection

Comments Extract Method, Introduce Assertion

... fight the Smells

Page 32: Refactoring In Tdd The Missing Part

an example ofRefactoring and

Unit tests

Page 33: Refactoring In Tdd The Missing Part

public class VobasBackupService implements Runnable {

public void run() { Map fileDetails = new Hashtable(); try { BufferedReader reader = new BufferedReader(new FileReader(MainFrame.WATCHED_DATA)); String directoryName = reader.readLine(); File fileData = new File(directoryName, ".vobas"); while (directoryName != null) { if (!fileData.exists()) { fileData.createNewFile(); } else { ObjectInputStream fileDetailsReader = new ObjectInputStream(new FileInputStream(fileData)); FileDetail fileDetail = (FileDetail) fileDetailsReader.readObject(); while (fileDetail != null) { fileDetails.put(fileDetail.getName(), fileDetail); try { fileDetail = (FileDetail) fileDetailsReader.readObject(); } catch (EOFException e) { break; } } } File[] files = new File(directoryName).listFiles(); for (File file : files) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName()); if (fileDetail == null) { ScpTo.send(directoryName + File.separatorChar + file.getName()); fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); } else if (file.lastModified() > fileDetail.getModificationDate().getTime()) { ScpTo.send(directoryName + File.separatorChar + file.getName()); fileDetails.remove(file.getName()); fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); } } ObjectOutput objectOutput = new ObjectOutputStream(new FileOutputStream(new File(directoryName, ".vobas"))); for (FileDetail fileDetail : fileDetails.values()) { objectOutput.writeObject(fileDetail); } objectOutput.close(); directoryName = reader.readLine(); } reader.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }

}

}

Page 34: Refactoring In Tdd The Missing Part

WTF?!?

Page 35: Refactoring In Tdd The Missing Part

import java.util.*;

public class FileDetail {

private Date lastModified; private String fileName;

public FileDetail(Date lastModified, String fileName) { this.lastModified = lastModified; this.fileName = fileName; }

public Date getModificationDate() { return this.lastModified; }

public String getName() { return this.fileName; }

}

public class MainFrame {

public static final String WATCHED_DATA = "/dev/null"; }

public class ScpTo { public static void send(String filePathToSend) { // TODO: no need to implement :-( }

}

make itcompile

stubdependencies

Page 36: Refactoring In Tdd The Missing Part

is supposedto work, buthow it works?

Page 37: Refactoring In Tdd The Missing Part

import org.junit.* ;import static org.junit.Assert.* ;

public class StubTest { @Test public void shouldAlwaysWork() { assertTrue(true); }

}

import org.junit.* ;import static org.junit.Assert.* ;

public class StubTest { @Test public void shouldNeverWork() { assertTrue(false); }

}

prepare your engine :-)

Page 38: Refactoring In Tdd The Missing Part

public void run() { try { Map fileDetails = new Hashtable(); BufferedReader reader = new BufferedReader(new FileReader(MainFrame.WATCHED_DATA)); ...

} catch (FileNotFoundException e) { ... }}

Untestable

Page 39: Refactoring In Tdd The Missing Part

public void run() { try { dontKnowWhatItDoes(MainFrame.WATCHED_DATA);

} catch (FileNotFoundException e) { ... }

public void dontKnowWhatItDoes(String directoryPathToBackup) throws ... { BufferedReader reader = new BufferedReader(new FileReader(directoryPathToBackup)); Map fileDetails = new Hashtable(); String directoryName = reader.readLine(); File fileData = new File(directoryName, ".vobas"); while (directoryName != null) { ... } reader.close(); }

Extract Method

Page 40: Refactoring In Tdd The Missing Part

doing refactoringwithout tests

is unsafe

Page 41: Refactoring In Tdd The Missing Part

public class VobasBackupServiceCharacterizationTest {

private List<File> directoriesToCleanup;

@Before public void setUp() throws Exception { directoriesToCleanup = new ArrayList<File>(); }

@After public void tearDown() throws Exception { for (File directoryToCleanup : directoriesToCleanup) { deleteDirectory(directoryToCleanup); } ScpTo.sended = new ArrayList<String>(); }

@Test public void backupOneDirectoryWithOneFreshFile() throws Exception { VobasBackupService service = new VobasBackupService();

File oneDirectoryWithOneFile = createDirectoryToBackupWithFiles(1); File listOfDirectoriesToBackup = listOfDirectoriesToBackupIntoFile(oneDirectoryWithOneFile);

service.backup(listOfDirectoriesToBackup.getAbsolutePath()); assertEquals(1, ScpTo.sended.size());

directoriesToCleanup.add(oneDirectoryWithOneFile); }

Characterization Test

Page 42: Refactoring In Tdd The Missing Part

@Test public void backupOneDirectoryWithTwoFreshFiles() throws Exception { VobasBackupService service = new VobasBackupService();

File oneDirectoryWithTwoFiles = createDirectoryToBackupWithFiles(2); File listOfDirectoriesToBackup = listOfDirectoriesToBackupIntoFile(oneDirectoryWithTwoFiles);

service.backup(listOfDirectoriesToBackup.getAbsolutePath()); assertEquals(2, ScpTo.sended.size());

directoriesToCleanup.add(oneDirectoryWithTwoFiles); }

@Test public void backupTwoDirectoriesWithOneFreshFile() throws Exception { VobasBackupService service = new VobasBackupService();

File oneDirectoryWithOneFile = createDirectoryToBackupWithFiles(1); File anotherDirectoryWithOneFile = createDirectoryToBackupWithFiles(1);

File listOfDirectoriesToBackup = listOfDirectoriesToBackupIntoFile( oneDirectoryWithOneFile, anotherDirectoryWithOneFile);

service.backup(listOfDirectoriesToBackup.getAbsolutePath()); assertEquals(2, ScpTo.sended.size());

directoriesToCleanup.add(oneDirectoryWithOneFile); directoriesToCleanup.add(anotherDirectoryWithOneFile); }

Characterization Test

Page 43: Refactoring In Tdd The Missing Part

@Test public void backupOneDirectoryWithTwoFreshFiles() throws Exception { VobasBackupService service = new VobasBackupService();

File oneDirectoryWithTwoFiles = createDirectoryToBackupWithFiles(2); File listOfDirectoriesToBackup = listOfDirectoriesToBackupIntoFile(oneDirectoryWithTwoFiles);

service.backup(listOfDirectoriesToBackup.getAbsolutePath()); assertEquals(2, ScpTo.sended.size());

directoriesToCleanup.add(oneDirectoryWithTwoFiles); }

Magic Number

replace Magic Number with Expression @Test public void backupOneDirectoryWithTwoFreshFiles() throws Exception { VobasBackupService service = new VobasBackupService();

File oneDirectoryWithTwoFiles = createDirectoryToBackupWithFiles(2); File listOfDirectoriesToBackup = listOfDirectoriesToBackupIntoFile(oneDirectoryWithTwoFiles);

service.backup(listOfDirectoriesToBackup.getAbsolutePath()); assertEquals(oneDirectoryWithTwoFiles.list().length, ScpTo.sended.size());

directoriesToCleanup.add(oneDirectoryWithTwoFiles); }

Page 44: Refactoring In Tdd The Missing Part

private void deleteDirectory(File directoryToDelete) throws Exception { if (directoryToDelete.isDirectory()) { String[] children = directoryToDelete.list(); for (int i=0; i<children.length; i++) { deleteDirectory(new File(directoryToDelete, children[i])); } } if (!directoryToDelete.delete()) { throw new Exception("unable to delete " + directoryToDelete.getAbsolutePath()); } }

Syntax Noise

private void deleteDirectory(File directoryToDelete) throws Exception { if (directoryToDelete.isDirectory()) { for (File child : directoryToDelete.listFiles()) { deleteDirectory(child); } } assert directoryToDelete.delete() : "unable to delete " + directoryToDelete.getAbsolutePath()); }

replace For with Loop

Page 45: Refactoring In Tdd The Missing Part

private void deleteDirectory(File directoryToDelete) throws Exception { if (directoryToDelete.isDirectory()) { String[] children = directoryToDelete.list(); for (int i=0; i<children.length; i++) { deleteDirectory(new File(directoryToDelete, children[i])); } } if (!directoryToDelete.delete()) { throw new Exception("unable to delete " + directoryToDelete.getAbsolutePath()); } }

Syntax Noise

private void deleteDirectory(File directoryToDelete) throws Exception { if (directoryToDelete.isDirectory()) { for (File child : directoryToDelete.listFiles()) { deleteDirectory(child); } } assert directoryToDelete.delete() : "unable to delete " + directoryToDelete.getAbsolutePath()); }

replace Test with Assertion

Page 46: Refactoring In Tdd The Missing Part

// read all directories to backup from a file BufferedReader reader = new BufferedReader(new FileReader(directoryPathToBackup)); // for each of those directory String directoryName = reader.readLine(); while (directoryName != null) {

// get details on files in these directory Map fileDetails = new Hashtable(); File fileData = new File(directoryName, ".vobas"); if (!fileData.exists()) { fileData.createNewFile(); } else { ObjectInputStream fileDetailsReader = new ObjectInputStream( new FileInputStream(fileData)); FileDetail fileDetail = (FileDetail) fileDetailsReader.readObject(); while (fileDetail != null) { fileDetails.put(fileDetail.getName(), fileDetail); try { fileDetail = (FileDetail) fileDetailsReader.readObject(); } catch (EOFException e) { break; } } }

now we can play safe

Page 47: Refactoring In Tdd The Missing Part

// select only files to backup in directory File[] files = new File(directoryName).listFiles(new FilenameFilter() { public boolean accept(File directory, String fileName) { return ! fileName.equals(".vobas"); } });

// for each of those files for (File file : files) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName());

// if no previous details are given if (fileDetail == null) { // send to backup ScpTo.send(directoryName + File.separatorChar + file.getName()); // save details fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName()));

// if details are given but file has been modified } else if (file.lastModified() > fileDetail.getModificationDate().getTime()) { // send to backup ScpTo.send(directoryName + File.separatorChar + file.getName()); // save details fileDetails.remove(file.getName()); fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); } }

now we can play safe

Page 48: Refactoring In Tdd The Missing Part

now we can play safe

// save all details on files to .vobas file ObjectOutput objectOutput = new ObjectOutputStream( new FileOutputStream(new File(directoryName, ".vobas"))); for (Object value : fileDetails.values()) { FileDetail fileDetail = (FileDetail) value; objectOutput.writeObject(fileDetail); } objectOutput.close();

// next directory to backup please... directoryName = reader.readLine(); } reader.close();

Page 49: Refactoring In Tdd The Missing Part

narrow your targetand your tests

// for each of those directory String directoryName = reader.readLine(); while (directoryName != null) {

// read details on files in directory Map fileDetails = new Hashtable(); File fileData = new File(directoryName, ".vobas"); if (!fileData.exists()) { fileData.createNewFile(); } else { ObjectInputStream fileDetailsReader = new ObjectInputStream( new FileInputStream(fileData)); FileDetail fileDetail = (FileDetail) fileDetailsReader.readObject(); while (fileDetail != null) { fileDetails.put(fileDetail.getName(), fileDetail); try { fileDetail = (FileDetail) fileDetailsReader.readObject(); } catch (EOFException e) { break; } } }

Comment

Page 50: Refactoring In Tdd The Missing Part

// for each of those directory String directoryName = reader.readLine(); while (directoryName != null) {

// read details on files in directory Map fileDetails = new Hashtable(); File fileData = new File(directoryName, ".vobas"); if (!fileData.exists()) { fileData.createNewFile(); } else { ObjectInputStream fileDetailsReader = new ObjectInputStream( new FileInputStream(fileData)); FileDetail fileDetail = (FileDetail) fileDetailsReader.readObject(); while (fileDetail != null) { fileDetails.put(fileDetail.getName(), fileDetail); try { fileDetail = (FileDetail) fileDetailsReader.readObject(); } catch (EOFException e) { break; } } }

Extract Method

Page 51: Refactoring In Tdd The Missing Part

public Map readDetailsOnFilesFrom(String directoryPath) throws ... { Map fileDetails = new Hashtable(); File fileData = new File(directoryPath, ".vobas"); if (!fileData.exists()) { fileData.createNewFile(); } else { ObjectInputStream fileDetailsReader = new ObjectInputStream( new FileInputStream(fileData)); FileDetail fileDetail = (FileDetail) fileDetailsReader.readObject(); while (fileDetail != null) { fileDetails.put(fileDetail.getName(), fileDetail); try { fileDetail = (FileDetail) fileDetailsReader.readObject(); } catch (EOFException e) { break; } } } return fileDetails; }

Extract Method

Page 52: Refactoring In Tdd The Missing Part

Extract Method

// for each of those directory String directoryName = reader.readLine(); while (directoryName != null) {

// read details on files in directory Map fileDetails = readDetailsOnFilesFrom(directoryName); // select only files to backup in directory File[] files = new File(directoryName).listFiles(new FilenameFilter() { public boolean accept(File directory, String fileName) { return ! fileName.equals(".vobas"); } });

...

Page 53: Refactoring In Tdd The Missing Part

Extract Method

// for each of those directory String directoryName = reader.readLine(); while (directoryName != null) {

Map fileDetails = readDetailsOnFilesFrom(directoryName); // select only files to backup in directory File[] files = new File(directoryName).listFiles(new FilenameFilter() { public boolean accept(File directory, String fileName) { return ! fileName.equals(".vobas"); } });

...

Page 54: Refactoring In Tdd The Missing Part

Duplicated Code, Complex Conditional

// if no previous details are givenif (fileDetail == null) { // send to backup ScpTo.send(directoryName + File.separatorChar + file.getName());

// save details fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName()));

// if details are given but file has been modified} else if (file.lastModified() > fileDetail.getModificationDate().getTime()) { // send to backup ScpTo.send(directoryName + File.separatorChar + file.getName());

// save details fileDetails.remove(file.getName()); fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName()));}

Page 55: Refactoring In Tdd The Missing Part

// if no previous details are givenif (fileDetail == null) { // send to backup ScpTo.send(directoryName + File.separatorChar + file.getName());

// save details fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName()));

// if details are given but file has been modified} else if (file.lastModified() > fileDetail.getModificationDate().getTime()) { // send to backup ScpTo.send(directoryName + File.separatorChar + file.getName());

// save details fileDetails.remove(file.getName()); fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName()));}

Consolidate duplicated conditional fragments

Page 56: Refactoring In Tdd The Missing Part

// if no previous details are givenif (fileDetail == null) {

// send to backup ScpTo.send(directoryName + File.separatorChar + file.getName());

// save details fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName()));

// if details are given but file has been modified} else if (file.lastModified() > fileDetail.getModificationDate().getTime()) {

// send to backup ScpTo.send(directoryName + File.separatorChar + file.getName());

// save details fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName()));

}

Consolidate duplicated conditional fragments

Page 57: Refactoring In Tdd The Missing Part

// if no previous details are given or// if details are given but file has been modifiedif ((fileDetail == null) || (file.lastModified() > fileDetail.getModificationDate().getTime())) {

// send to backup ScpTo.send(directoryName + File.separatorChar + file.getName());

// save details fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName()));

}

Consolidate duplicated conditional fragments

Page 58: Refactoring In Tdd The Missing Part

for (File file : files) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName());

if (noPrevioiusDetailsAreGiven(fileDetail) ||

fileHasBeenModifiedSinceLastBackup(fileDetail, file)) {

// send to backup ScpTo.send(directoryName + File.separatorChar + file.getName());

// save details fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); }}

private boolean noPrevioiusDetailsAreGiven(FileDetail fileDetail) { return fileDetail == null;}

private boolean fileHasBeenModifiedSinceLastBackup(FileDetail fileDetail, File file) { return file.lastModified() > fileDetail.getModificationDate().getTime();}

Extract Method

Page 59: Refactoring In Tdd The Missing Part

for (File file : files) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName());

if (needBackup(fileDetail, file)) {

// send to backup ScpTo.send(directoryName + File.separatorChar + file.getName());

// save details fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); }}

private boolean needBackup(FileDetail fileDetail, File file) { return noPrevioiusDetailsAreGiven(fileDetail) || fileHasBeenModifiedSinceLastBackup(fileDetail, file); }

Extract Method

Page 60: Refactoring In Tdd The Missing Part

public void backupDirectories(String listOfDirectoriesToBackup) throws ... {

for (File file : files) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName());

if (needBackup(fileDetail, file)) {

// send to backup ScpTo.send(directoryName + File.separatorChar + file.getName());

// save details fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); }}

Rename Method

public void dontKnowWhatItDoes(String directoryPathToBackup) throws ... { ...}

Page 61: Refactoring In Tdd The Missing Part

public void backupDirectories(String listOfDirectoriesToBackup) throws ... { ... for (File file : files) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName());

if (needBackup(fileDetail, file)) { backupFile(file); updateFileDetails(fileDetails, file); } } ...}

private void backupFile(File fileToBackup) { ScpTo.send(file.getAbsolutePath()); }

private void updateFileDetails(Map fileDetails, File file) { fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); }

Extract Method

Page 62: Refactoring In Tdd The Missing Part

public void backupDirectories(String listOfDirectoriesToBackup) throws ... { BufferedReader reader = new BufferedReader(new FileReader(listOfDirectoriesToBackup)); String directoryPath = reader.readLine(); while (directoryPath != null) { backupDirectory(directoryPath); directoryPath = reader.readLine(); } reader.close(); }

public void backupDirectory(String directoryPath) throws ... { Map fileDetails = readDetailsOnFilesFrom(directoryPath); File[] files = filesToBackupInto(directoryPath);

for (File file : files) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName());

if (needBackup(fileDetail, file)) { backupFile(file); updateFileDetails(fileDetails, file); } } writeDetailsOnFilesTo(directoryPath, fileDetails); }

Extract Method

Page 63: Refactoring In Tdd The Missing Part

public void backupDirectory(String directoryPath) throws ... { Map fileDetails = readDetailsOnFilesFrom(directoryPath); File[] files = filesToBackupInto(directoryPath);

for (File file : files) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName());

if (needBackup(fileDetail, file)) { backupFile(file); updateFileDetails(fileDetails, file); } } writeDetailsOnFilesTo(directoryPath, fileDetails); }

Replace temporary variable with Query

Page 64: Refactoring In Tdd The Missing Part

public void backupDirectory(String directoryPath) throws ... { Map fileDetails = readDetailsOnFilesFrom(directoryPath);

for (File file : filesToBackupInto(directoryPath)) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName());

if (needBackup(fileDetail, file)) { backupFile(file); updateFileDetails(fileDetails, file); } } writeDetailsOnFilesTo(directoryPath, fileDetails); }

Replace temporary variable with Query

Page 65: Refactoring In Tdd The Missing Part

public void backupDirectory(String directoryPath) throws ... { Map fileDetails = readDetailsOnFilesFrom(directoryPath);

for (File file : filesToBackupInto(directoryPath)) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName());

if (needBackup(fileDetail, file)) { backupFile(file); updateFileDetails(fileDetails, file); } } writeDetailsOnFilesTo(directoryPath, fileDetails); }

Introduce Generics

Page 66: Refactoring In Tdd The Missing Part

public void backupDirectory(String directoryPath) throws ... { Map<String, FileDetail> fileDetails = readDetailsOnFilesFrom(directoryPath);

for (File file : filesToBackupInto(directoryPath)) { if (needBackup(fileDetails, file)) { backupFile(file); updateFileDetails(fileDetails, file); } }

writeDetailsOnFilesTo(directoryPath, fileDetails); }

Introduce Generics

Page 67: Refactoring In Tdd The Missing Part

public void backupDirectories(String listOfDirectoriesToBackup) throws ... { BufferedReader reader = new BufferedReader(new FileReader(listOfDirectoriesToBackup)); String directoryPath = reader.readLine(); while (directoryPath != null) { backupDirectory(directoryPath); directoryPath = reader.readLine(); } reader.close(); }

Inconsistent Name

Page 68: Refactoring In Tdd The Missing Part

public void run() { try { backupDirectories(listOfDirectoriesToBackup(MainFrame.WATCHED_DATA)); ... } }

public List<File> listOfDirectoriesToBackup(String listOfDirectories) throws ... { List<File> directoriesToBackup = new ArrayList<File>(); BufferedReader reader = new BufferedReader(new FileReader(listOfDirectories)); String directoryName = reader.readLine(); while (directoryName != null) { directoriesToBackup.add(new File(directoryName)); directoryName = reader.readLine(); } return directoriesToBackup; }

public void backupDirectories(List<File> listOfDirectoriesToBackup) throws ... { for (File directoryToBackup : listOfDirectoriesToBackup) { backupDirectory(directoryToBackup.getAbsolutePath()); } }

Extract Method, replace For with Loop

Page 69: Refactoring In Tdd The Missing Part

public void run() { try { backupDirectories(listOfDirectoriesToBackup(MainFrame.WATCHED_DATA));

} catch (Exception e) { throw new RuntimeException(e); } }

public void backupDirectories(List<File> listOfDirectoriesToBackup) throws ... { for (File directoryToBackup : listOfDirectoriesToBackup) { backupDirectory(directoryToBackup.getAbsolutePath()); } }

public void backupDirectory(String directoryPath) throws ... { Map<String, FileDetail> fileDetails = readDetailsOnFilesFrom(directoryPath);

for (File file : filesToBackupInto(directoryPath)) { if (needBackup(fileDetails, file)) { backupFile(file); updateFileDetails(fileDetails, file); } } writeDetailsOnFilesTo(directoryPath, fileDetails); }

Page 70: Refactoring In Tdd The Missing Part

public void writeDetailsOnFilesTo(String directoryPath, Map<String, FileDetail> fileDetails) throws ... { ... }

public Map<String, FileDetail> readDetailsOnFilesFrom(String directoryPath) throws ... { ... }

private boolean needBackup(Map<String, FileDetail> fileDetails, File file) { FileDetail fileDetail = fileDetails.get(file.getName()); return noPrevioiusDetailsAreGiven(fileDetail) || fileHasBeenModifiedSinceLastBackup(fileDetail, file); }

private boolean noPrevioiusDetailsAreGiven(FileDetail fileDetail) { return fileDetail == null; }

private boolean fileHasBeenModifiedSinceLastBackup(FileDetail fileDetail, File file) { return file.lastModified() > fileDetail.getModificationDate().getTime(); }

private void updateFileDetails(Map<String, FileDetail> fileDetails, File file) { fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); }

Feature Envy, Large Class

Page 71: Refactoring In Tdd The Missing Part

public void writeDetailsOnFilesTo(String directoryPath, Map<String, FileDetail> fileDetails) throws ... { ... }

public Map<String, FileDetail> readDetailsOnFilesFrom(String directoryPath) throws ... { ... }

private boolean needBackup(Map<String, FileDetail> fileDetails, File file) { FileDetail fileDetail = fileDetails.get(file.getName()); return noPrevioiusDetailsAreGiven(fileDetail) || fileHasBeenModifiedSinceLastBackup(fileDetail, file); }

private boolean noPrevioiusDetailsAreGiven(FileDetail fileDetail) { return fileDetail == null; }

private boolean fileHasBeenModifiedSinceLastBackup(FileDetail fileDetail, File file) { return file.lastModified() > fileDetail.getModificationDate().getTime(); }

private void updateFileDetails(Map<String, FileDetail> fileDetails, File file) { fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); }

Primitive Obsession, Data Clumps

Page 72: Refactoring In Tdd The Missing Part

Extract Class class BackupReport {

public Map<String, FileDetail> fileDetails;

private BackupReport(File directoryToBackup) throws ... { fileDetails = new Hashtable<String, FileDetail>(); File fileData = new File(directoryToBackup, ".vobas"); if (!fileData.exists()) { fileData.createNewFile(); } else { ObjectInputStream fileDetailsReader = new ObjectInputStream(new FileInputStream(fileData)); FileDetail fileDetail = (FileDetail) fileDetailsReader.readObject(); while (fileDetail != null) { fileDetails.put(fileDetail.getName(), fileDetail); try { fileDetail = (FileDetail) fileDetailsReader.readObject(); } catch (EOFException e) { break; } } } }

public static BackupReport readFrom(File directoryToBackup) throws ... { return new BackupReport(directoryToBackup); }

public static BackupReport readFrom(String pathToDirectoryToBackup) throws ... { return new BackupReport(new File(pathToDirectoryToBackup)); }

}

Page 73: Refactoring In Tdd The Missing Part

Extract Class

public void backupDirectory(String directoryPath) throws ... { BackupReport lastBackupReport = BackupReport.readFrom(directoryPath);

for (File file : filesToBackupInto(directoryPath)) { if (needBackup(lastBackupReport.fileDetails, file)) { backupFile(file); updateFileDetails(lastBackupReport.fileDetails, file); } }

writeDetailsOnFilesTo(directoryPath, lastBackupReport.fileDetails); }

Page 74: Refactoring In Tdd The Missing Part

public void backupDirectory(String directoryPath) throws ... { BackupReport lastBackupReport = BackupReport.readFrom(directoryPath);

for (File file : filesToBackupInto(directoryPath)) { if (needBackup(lastBackupReport.fileDetails, file)) { backupFile(file); updateFileDetails(lastBackupReport.fileDetails, file); } }

writeDetailsOnFilesTo(directoryPath, lastBackupReport.fileDetails); }

Feature Envy

Page 75: Refactoring In Tdd The Missing Part

public void backupDirectory(String directoryPath) throws ... { BackupReport lastBackupReport = BackupReport.readFrom(directoryPath);

for (File file : filesToBackupInto(directoryPath)) { if (lastBackupReport.needBackup(file)) { backupFile(file); lastBackupReport.update(file); } }

lastBackupReport.save(); }

Move Method, Rename Method

Page 76: Refactoring In Tdd The Missing Part

public void backupDirectory(String directoryPath) throws ... { BackupReport lastBackupReport = BackupReport.readFrom(directoryPath);

for (File file : filesToBackupInto(directoryPath)) { if (lastBackupReport.needBackup(file)) { backupFile(file); lastBackupReport.update(file); } }

lastBackupReport.save(); }

Inconsistent Name

Page 77: Refactoring In Tdd The Missing Part

Rename Class, Replace Data Value with Object

public void backupDirectory(File directoryToBackup) throws ... { DirectoryBackupStatus directoryBackupStatus = new DirectoryBackupStatus(directoryToBackup);

for (File file : filesToBackupInto(directoryToBackup)) { if (directoryBackupStatus.needBackup(file)) { backupFile(file); directoryBackupStatus.update(file); } }

directoryBackupStatus.save(); }

Page 78: Refactoring In Tdd The Missing Part

public void run() { try { backupDirectories(listOfDirectoriesToBackup(MainFrame.WATCHED_DATA)); } catch (Exception e) { throw new RuntimeException(e); } }

public void backupDirectories(List<File> listOfDirectoriesToBackup) throws ... { for (File directoryToBackup : listOfDirectoriesToBackup) { backupDirectory(directoryToBackup); } }

public void backupDirectory(File directoryToBackup) throws ... { DirectoryBackupStatus directoryBackupStatus = new DirectoryBackupStatus(directoryToBackup);

for (File file : filesToBackupInto(directoryToBackup)) { if (directoryBackupStatus.needBackup(file)) { backupFile(file); directoryBackupStatus.update(file); } }

directoryBackupStatus.save(); }

Page 79: Refactoring In Tdd The Missing Part
Page 80: Refactoring In Tdd The Missing Part

class BackupPlan {

public BackupPlan(File configuration) throws BackupFailedException { this.directoriesToBackup = directoriesToBackup(backupPlan); }

public void runWith(BackupService backupService) throws BackupFailedException { for (File directoryToBackup : directoriesToBackup) { new DirectoryBackupService(directoryToBackup).backupWith(backupService); } }

private List<File> directoriesToBackup(File configuration) throws BackupFailedException { ... }

private List<File> directoriesToBackup;

}

Page 81: Refactoring In Tdd The Missing Part

class DirectoryBackupService {

public DirectoryBackupService(File directoryToBackup) throws BackupFailedException { readBackupStatusFrom(directoryToBackup); }

public void backupWith(BackupService backupService) throws BackupFailedException { for (File file : listOfFilesToBackup()) { if (needBackup(file)) { backupService.backup(file); update(file); } } save(); }

... }

Page 82: Refactoring In Tdd The Missing Part

Test-Driven Development

Cycle

Page 83: Refactoring In Tdd The Missing Part

public class AdderTest { @Test

public void testTwoPlusThree() {Adder a = new Adder();assertEquals(5, a.add(2, 3));

}}

Write a test

Page 84: Refactoring In Tdd The Missing Part

Now it compilespublic class AdderTest { @Test

public void testTwoPlusThree() {Adder a = new Adder();assertEquals(5, a.add(2, 3));

}}

public class Adder { public int add(int a, int b) { return 0; }}

Page 85: Refactoring In Tdd The Missing Part

Red bar!public class AdderTest { @Test

public void testTwoPlusThree() {Adder a = new Adder();assertEquals(5, a.add(2, 3));

}}

public class Adder { public int add(int a, int b) { return 0; }}

Expected 5, was 0

Page 86: Refactoring In Tdd The Missing Part

Just pretendpublic class AdderTest { @Test

public void testTwoPlusThree() {Adder a = new Adder();assertEquals(5, a.add(2, 3));

}}

public class Adder { public int add(int a, int b) { return 5; }}

Page 87: Refactoring In Tdd The Missing Part

Remove the duplicated “5”public class AdderTest {

@Testpublic void testTwoPlusThree() {

Adder a = new Adder();assertEquals(5, a.add(2, 3));

}}

public class Adder { public int add(int a, int b) { return a+b; }}

Page 88: Refactoring In Tdd The Missing Part

1. Write a test

2. Make it compile

3. Make it pass

4. Refactor

The procedure

Expected 5, was 0

Page 89: Refactoring In Tdd The Missing Part

Red

GreenRefactor

Repeat every 2-10 min.

Page 90: Refactoring In Tdd The Missing Part

It’s not about testing

• TDD is a design technique

• The tests are not the main point

• The design emerges in small steps

Page 91: Refactoring In Tdd The Missing Part

Clean code, why?

Page 92: Refactoring In Tdd The Missing Part

Clean code, why?

• Design is the great accelerator:

• If you drop quality for speed, you will get neither

• If you aim for quality...

• ... and you know how to get it...

• ... you will also be fast!

Page 93: Refactoring In Tdd The Missing Part

Test first, why?

• You think code from the point of view of the caller

• This perspective makes for better design

• Test coverage is a useful byproduct

Page 94: Refactoring In Tdd The Missing Part

Refactor, why?

Page 95: Refactoring In Tdd The Missing Part

Refactor, why?

• What is clean code today could be bad code tomorrow

• Refactoring is when I do design

• Design emerges, with thought, care and small steps

• I don’t claim I can guess the right design

• Because I can: the tests support refactoring

Page 96: Refactoring In Tdd The Missing Part

No silver bullet

• Needs lots of practice

• Requires discipline

• Must think and be alert at all times!

Page 97: Refactoring In Tdd The Missing Part