SlideShare una empresa de Scribd logo
1 de 97
Descargar para leer sin conexión
Test
Driven
Development
Debugging
  Sucks

            Testing
            Rocks
Bugfixing
Cost




                      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?
Bugfixing
          Tests
Cost




                      Bugs/Features




•      Cost of tests = Number and complexity of features

•      How many features do you plan to have?

•      How complex they are?
Test
Automation
 Lifecycle
Fixture

all the things we need to
  have in place in order
 to run a test and expect
   a particular outcome
Fixture Setup
                 Fixture


    Steps

Fixture Setup
SUT


 System Under Test
is whatever thing we
     are testing
Exercise SUT
                      Fixture


    Steps
                SUT
Fixture Setup

Exercise SUT
Verify Result
                       Fixture


    Steps
                 SUT
Fixture Setup

Exercise SUT

Verify Result
“Don't call us, we'll call you”
Fixture Teardown
                         Fixture


     Steps
                   SUT
 Fixture Setup

 Exercise SUT

  Verify Result

Fixture Teardown
a test is a good one if...
• 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
the kind of test is not
determined by the used tool
Unit tests
A 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
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
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
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
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
Test-Driven
Development
As a developer,
I want to learn TDD,
  so that I can write
clean code that works
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
Clean code that works
 Clean 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
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
Simple design

The code is simple enough when it:
  0. Pass all the tests
  1. Expresses every idea that we need to express
  2. Contains no duplication
  3. Has the minimum number of classes and functions
(In this order)



                Adapted from Extreme Programming Installed by Ron Jeffries et al.
Clean code that works


• First we'll solve the “that works” part
• Then we'll solve the “clean code” part
Code that works could Smell




         Refactoring by Martin Fowler
... then Refactor

 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




                                     Martin Fowler
through small steps




          Refactoring by Martin Fowler
... 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


                   Extract Class, Extract Subclass, Extract Interface, Replace Data
  Large Class
                                          Value with Object

                  Extract Method, Replace Temporary Variable with Query, Replace
 Long Method
                       Method with Method Object, Decompose Conditional
... fight the Smells
    Smell                  Common Refactorings
 Shotgun Surgery                 Move Method, Move Field, Inline Class


                      Replace Parameter with Method, Introduct Parameter Object,
Long Parameter List
                                       Preserve Whole Object


    Data Class          Move Method, Encapsulate Field, Encapsulate Collection


    Comments                     Extract Method, Introduce Assertion
an example of
Refactoring and
   Unit tests
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) {
WTF?!?
import java.util.*;

public class FileDetail {




                                                                   make it
    private Date lastModified;
    private String fileName;

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



                                                                   compile
        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";

                                                                     stub
    public class ScpTo {

          public static void send(String filePathToSend) {

          }
              // TODO: no need to implement :-(
                                                                 dependencies
    }
is supposed
to work, but
how it works?
prepare your engine :-)
import org.junit.* ;                   import org.junit.* ;
import static org.junit.Assert.* ;     import static org.junit.Assert.* ;

public class StubTest {                public class StubTest {

    @Test                                  @Test
    public void shouldAlwaysWork() {       public void shouldNeverWork() {
        assertTrue(true);                      assertTrue(false);
    }                                      }

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

    } catch (FileNotFoundException e) {
        ...
    }
}
Extract Method
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();
}
doing refactoring
  without tests
    is unsafe
Characterization Test
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
@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);
}
Magic Number
@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);
}




    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);
}
Syntax Noise
     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());
         }
     }




                        replace For with Loop
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());
}
Syntax Noise
     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());
         }
     }




                   replace Test with Assertion
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());
}
now we can play safe
// 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
// 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
   // 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();
narrow your target
        and your tests
// for each of those directory
String directoryName = reader.readLine();
while (directoryName != null) {
                                                         Comment
   // 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
// 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

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


// 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");
           }
   });

    ...
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");
           }
   });

    ...
Duplicated Code, Complex Conditional
// 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()));
}
Consolidate duplicated conditional fragments
// 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()));
}
Consolidate duplicated conditional fragments
// 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.put(file.getName(), new FileDetail(new Date(), file.getName()));

}
Consolidate duplicated conditional fragments
// if no previous details are given or
// if details are given but file has been modified
if ((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()));

}
Extract Method
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

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);
}
Rename Method

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




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()));
    }
}
Extract Method

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
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);
}
Replace temporary variable with Query


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


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


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



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);
}
Inconsistent Name
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();
}
Extract Method, replace For with Loop
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());
    }
}
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);
}
Feature Envy, Large Class
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
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()));
}
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));
    }

}
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);
}
Feature Envy



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);
}
Move Method, Rename Method



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



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();
}
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();
}
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();
}
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;

}
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();
    }

    ...
}
Test-Driven
Development
   Cycle
Write a test
public class AdderTest {
  @Test
  public void testTwoPlusThree() {
    Adder a = new Adder();
    assertEquals(5, a.add(2, 3));
  }
}
Now it compiles
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; }
}
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
Just pretend
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 5; }
}
Remove the duplicated “5”
   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 a+b; }
   }
The procedure

1. Write a test

2. Make it compile
                  Expected 5, was 0

3. Make it pass

4. Refactor
Red

Refactor             Green




   Repeat every 2-10 min.
It’s not about testing

• TDD is a design technique
• The tests are not the main point
• The design emerges in small steps
Clean code, why?
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!
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
Refactor, why?
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
No silver bullet

• Needs lots of practice
• Requires discipline
• Must think and be alert at all times!
Test Driven Development Debugging Sucks Testing Rocks

Más contenido relacionado

La actualidad más candente

冬のLock free祭り safe
冬のLock free祭り safe冬のLock free祭り safe
冬のLock free祭り safeKumazaki Hiroki
 
SSE4.2の文字列処理命令の紹介
SSE4.2の文字列処理命令の紹介SSE4.2の文字列処理命令の紹介
SSE4.2の文字列処理命令の紹介MITSUNARI Shigeo
 
ドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したことドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したことBIGLOBE Inc.
 
PHP の GC の話
PHP の GC の話PHP の GC の話
PHP の GC の話y-uti
 
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭するCEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭するYoshifumi Kawai
 
C#/.NETがやっていること 第二版
C#/.NETがやっていること 第二版C#/.NETがやっていること 第二版
C#/.NETがやっていること 第二版信之 岩永
 
20分くらいでわかった気分になれるC++20コルーチン
20分くらいでわかった気分になれるC++20コルーチン20分くらいでわかった気分になれるC++20コルーチン
20分くらいでわかった気分になれるC++20コルーチンyohhoy
 
Database Anti Patterns
Database Anti PatternsDatabase Anti Patterns
Database Anti PatternsRobert Treat
 
std::pin の勘所
std::pin の勘所std::pin の勘所
std::pin の勘所Hiroaki Goto
 
「黒騎士と白の魔王」gRPCによるHTTP/2 - API, Streamingの実践
「黒騎士と白の魔王」gRPCによるHTTP/2 - API, Streamingの実践「黒騎士と白の魔王」gRPCによるHTTP/2 - API, Streamingの実践
「黒騎士と白の魔王」gRPCによるHTTP/2 - API, Streamingの実践Yoshifumi Kawai
 
GoogleのSHA-1のはなし
GoogleのSHA-1のはなしGoogleのSHA-1のはなし
GoogleのSHA-1のはなしMITSUNARI Shigeo
 
関数プログラミング入門
関数プログラミング入門関数プログラミング入門
関数プログラミング入門Hideyuki Tanaka
 
Domain Modeling Made Functional (DevTernity 2022)
Domain Modeling Made Functional (DevTernity 2022)Domain Modeling Made Functional (DevTernity 2022)
Domain Modeling Made Functional (DevTernity 2022)Scott Wlaschin
 
条件分岐とcmovとmaxps
条件分岐とcmovとmaxps条件分岐とcmovとmaxps
条件分岐とcmovとmaxpsMITSUNARI Shigeo
 
Master Canary Forging: 新しいスタックカナリア回避手法の提案 by 小池 悠生 - CODE BLUE 2015
Master Canary Forging: 新しいスタックカナリア回避手法の提案 by 小池 悠生 - CODE BLUE 2015Master Canary Forging: 新しいスタックカナリア回避手法の提案 by 小池 悠生 - CODE BLUE 2015
Master Canary Forging: 新しいスタックカナリア回避手法の提案 by 小池 悠生 - CODE BLUE 2015CODE BLUE
 
わしわし的おすすめ .gitconfig 設定 (と見せかけて実はみんなのおすすめ .gitconfig 設定を教えてもらう魂胆) #広島Git 勉強会
わしわし的おすすめ  .gitconfig 設定 (と見せかけて実はみんなのおすすめ .gitconfig 設定を教えてもらう魂胆) #広島Git 勉強会わしわし的おすすめ  .gitconfig 設定 (と見せかけて実はみんなのおすすめ .gitconfig 設定を教えてもらう魂胆) #広島Git 勉強会
わしわし的おすすめ .gitconfig 設定 (と見せかけて実はみんなのおすすめ .gitconfig 設定を教えてもらう魂胆) #広島Git 勉強会Yukinori KITADAI
 

La actualidad más candente (20)

冬のLock free祭り safe
冬のLock free祭り safe冬のLock free祭り safe
冬のLock free祭り safe
 
SSE4.2の文字列処理命令の紹介
SSE4.2の文字列処理命令の紹介SSE4.2の文字列処理命令の紹介
SSE4.2の文字列処理命令の紹介
 
ドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したことドメイン駆動設計 失敗したことと成功したこと
ドメイン駆動設計 失敗したことと成功したこと
 
PHP の GC の話
PHP の GC の話PHP の GC の話
PHP の GC の話
 
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭するCEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
 
C#/.NETがやっていること 第二版
C#/.NETがやっていること 第二版C#/.NETがやっていること 第二版
C#/.NETがやっていること 第二版
 
20分くらいでわかった気分になれるC++20コルーチン
20分くらいでわかった気分になれるC++20コルーチン20分くらいでわかった気分になれるC++20コルーチン
20分くらいでわかった気分になれるC++20コルーチン
 
Database Anti Patterns
Database Anti PatternsDatabase Anti Patterns
Database Anti Patterns
 
std::pin の勘所
std::pin の勘所std::pin の勘所
std::pin の勘所
 
「黒騎士と白の魔王」gRPCによるHTTP/2 - API, Streamingの実践
「黒騎士と白の魔王」gRPCによるHTTP/2 - API, Streamingの実践「黒騎士と白の魔王」gRPCによるHTTP/2 - API, Streamingの実践
「黒騎士と白の魔王」gRPCによるHTTP/2 - API, Streamingの実践
 
GoogleのSHA-1のはなし
GoogleのSHA-1のはなしGoogleのSHA-1のはなし
GoogleのSHA-1のはなし
 
関数プログラミング入門
関数プログラミング入門関数プログラミング入門
関数プログラミング入門
 
Domain Modeling Made Functional (DevTernity 2022)
Domain Modeling Made Functional (DevTernity 2022)Domain Modeling Made Functional (DevTernity 2022)
Domain Modeling Made Functional (DevTernity 2022)
 
フラグを愛でる
フラグを愛でるフラグを愛でる
フラグを愛でる
 
条件分岐とcmovとmaxps
条件分岐とcmovとmaxps条件分岐とcmovとmaxps
条件分岐とcmovとmaxps
 
C#で速度を極めるいろは
C#で速度を極めるいろはC#で速度を極めるいろは
C#で速度を極めるいろは
 
TDDBC お題
TDDBC お題TDDBC お題
TDDBC お題
 
最速C# 7.x
最速C# 7.x最速C# 7.x
最速C# 7.x
 
Master Canary Forging: 新しいスタックカナリア回避手法の提案 by 小池 悠生 - CODE BLUE 2015
Master Canary Forging: 新しいスタックカナリア回避手法の提案 by 小池 悠生 - CODE BLUE 2015Master Canary Forging: 新しいスタックカナリア回避手法の提案 by 小池 悠生 - CODE BLUE 2015
Master Canary Forging: 新しいスタックカナリア回避手法の提案 by 小池 悠生 - CODE BLUE 2015
 
わしわし的おすすめ .gitconfig 設定 (と見せかけて実はみんなのおすすめ .gitconfig 設定を教えてもらう魂胆) #広島Git 勉強会
わしわし的おすすめ  .gitconfig 設定 (と見せかけて実はみんなのおすすめ .gitconfig 設定を教えてもらう魂胆) #広島Git 勉強会わしわし的おすすめ  .gitconfig 設定 (と見せかけて実はみんなのおすすめ .gitconfig 設定を教えてもらう魂胆) #広島Git 勉強会
わしわし的おすすめ .gitconfig 設定 (と見せかけて実はみんなのおすすめ .gitconfig 設定を教えてもらう魂胆) #広島Git 勉強会
 

Similar a Test Driven Development Debugging Sucks Testing Rocks

Writing Swift code with great testability
Writing Swift code with great testabilityWriting Swift code with great testability
Writing Swift code with great testabilityJohn Sundell
 
Tutorial on developing a Solr search component plugin
Tutorial on developing a Solr search component pluginTutorial on developing a Solr search component plugin
Tutorial on developing a Solr search component pluginsearchbox-com
 
Fighting Fear-Driven-Development With PHPUnit
Fighting Fear-Driven-Development With PHPUnitFighting Fear-Driven-Development With PHPUnit
Fighting Fear-Driven-Development With PHPUnitJames Fuller
 
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014FalafelSoftware
 
Breaking Dependencies to Allow Unit Testing
Breaking Dependencies to Allow Unit TestingBreaking Dependencies to Allow Unit Testing
Breaking Dependencies to Allow Unit TestingSteven Smith
 
Atlassian Groovy Plugins
Atlassian Groovy PluginsAtlassian Groovy Plugins
Atlassian Groovy PluginsPaul King
 
Property Based Testing in PHP
Property Based Testing in PHPProperty Based Testing in PHP
Property Based Testing in PHPvinaikopp
 
Gradle For Beginners (Serbian Developer Conference 2013 english)
Gradle For Beginners (Serbian Developer Conference 2013 english)Gradle For Beginners (Serbian Developer Conference 2013 english)
Gradle For Beginners (Serbian Developer Conference 2013 english)Joachim Baumann
 
New Features Of JDK 7
New Features Of JDK 7New Features Of JDK 7
New Features Of JDK 7Deniz Oguz
 
Test-driven development for TYPO3 (T3DD11)
Test-driven development for TYPO3 (T3DD11)Test-driven development for TYPO3 (T3DD11)
Test-driven development for TYPO3 (T3DD11)Oliver Klee
 
More on Fitnesse and Continuous Integration (Silicon Valley code camp 2012)
More on Fitnesse and Continuous Integration (Silicon Valley code camp 2012)More on Fitnesse and Continuous Integration (Silicon Valley code camp 2012)
More on Fitnesse and Continuous Integration (Silicon Valley code camp 2012)Jen Wong
 
Tdd is not about testing (OOP)
Tdd is not about testing (OOP)Tdd is not about testing (OOP)
Tdd is not about testing (OOP)Gianluca Padovani
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Michelangelo van Dam
 
Pragmatic unittestingwithj unit
Pragmatic unittestingwithj unitPragmatic unittestingwithj unit
Pragmatic unittestingwithj unitliminescence
 
Grails unit testing
Grails unit testingGrails unit testing
Grails unit testingpleeps
 
Intro To JavaScript Unit Testing - Ran Mizrahi
Intro To JavaScript Unit Testing - Ran MizrahiIntro To JavaScript Unit Testing - Ran Mizrahi
Intro To JavaScript Unit Testing - Ran MizrahiRan Mizrahi
 
Android Automated Testing
Android Automated TestingAndroid Automated Testing
Android Automated Testingroisagiv
 

Similar a Test Driven Development Debugging Sucks Testing Rocks (20)

Unit testing
Unit testingUnit testing
Unit testing
 
Writing Swift code with great testability
Writing Swift code with great testabilityWriting Swift code with great testability
Writing Swift code with great testability
 
Tutorial on developing a Solr search component plugin
Tutorial on developing a Solr search component pluginTutorial on developing a Solr search component plugin
Tutorial on developing a Solr search component plugin
 
Fighting Fear-Driven-Development With PHPUnit
Fighting Fear-Driven-Development With PHPUnitFighting Fear-Driven-Development With PHPUnit
Fighting Fear-Driven-Development With PHPUnit
 
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014
 
Breaking Dependencies to Allow Unit Testing
Breaking Dependencies to Allow Unit TestingBreaking Dependencies to Allow Unit Testing
Breaking Dependencies to Allow Unit Testing
 
From Java to Python
From Java to PythonFrom Java to Python
From Java to Python
 
Atlassian Groovy Plugins
Atlassian Groovy PluginsAtlassian Groovy Plugins
Atlassian Groovy Plugins
 
Property Based Testing in PHP
Property Based Testing in PHPProperty Based Testing in PHP
Property Based Testing in PHP
 
Gradle For Beginners (Serbian Developer Conference 2013 english)
Gradle For Beginners (Serbian Developer Conference 2013 english)Gradle For Beginners (Serbian Developer Conference 2013 english)
Gradle For Beginners (Serbian Developer Conference 2013 english)
 
New Features Of JDK 7
New Features Of JDK 7New Features Of JDK 7
New Features Of JDK 7
 
Test-driven development for TYPO3 (T3DD11)
Test-driven development for TYPO3 (T3DD11)Test-driven development for TYPO3 (T3DD11)
Test-driven development for TYPO3 (T3DD11)
 
More on Fitnesse and Continuous Integration (Silicon Valley code camp 2012)
More on Fitnesse and Continuous Integration (Silicon Valley code camp 2012)More on Fitnesse and Continuous Integration (Silicon Valley code camp 2012)
More on Fitnesse and Continuous Integration (Silicon Valley code camp 2012)
 
Tdd & unit test
Tdd & unit testTdd & unit test
Tdd & unit test
 
Tdd is not about testing (OOP)
Tdd is not about testing (OOP)Tdd is not about testing (OOP)
Tdd is not about testing (OOP)
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12
 
Pragmatic unittestingwithj unit
Pragmatic unittestingwithj unitPragmatic unittestingwithj unit
Pragmatic unittestingwithj unit
 
Grails unit testing
Grails unit testingGrails unit testing
Grails unit testing
 
Intro To JavaScript Unit Testing - Ran Mizrahi
Intro To JavaScript Unit Testing - Ran MizrahiIntro To JavaScript Unit Testing - Ran Mizrahi
Intro To JavaScript Unit Testing - Ran Mizrahi
 
Android Automated Testing
Android Automated TestingAndroid Automated Testing
Android Automated Testing
 

Más de Gabriele Lana

Microservice Architectures
Microservice ArchitecturesMicroservice Architectures
Microservice ArchitecturesGabriele Lana
 
Professional Programmer 2018
Professional Programmer 2018Professional Programmer 2018
Professional Programmer 2018Gabriele Lana
 
Parse Everything With Elixir
Parse Everything With ElixirParse Everything With Elixir
Parse Everything With ElixirGabriele Lana
 
Professional Programmer (3 Years Later)
Professional Programmer (3 Years Later)Professional Programmer (3 Years Later)
Professional Programmer (3 Years Later)Gabriele Lana
 
Resource Oriented Design
Resource Oriented DesignResource Oriented Design
Resource Oriented DesignGabriele Lana
 
Agileday Coderetreat 2013
Agileday Coderetreat 2013Agileday Coderetreat 2013
Agileday Coderetreat 2013Gabriele Lana
 
Milano Legacy Coderetreat 2013
Milano Legacy Coderetreat 2013Milano Legacy Coderetreat 2013
Milano Legacy Coderetreat 2013Gabriele Lana
 
Minimum Viable Product
Minimum Viable ProductMinimum Viable Product
Minimum Viable ProductGabriele Lana
 
Professional Programmer
Professional ProgrammerProfessional Programmer
Professional ProgrammerGabriele Lana
 
It is not supposed to fly but it does
It is not supposed to fly but it doesIt is not supposed to fly but it does
It is not supposed to fly but it doesGabriele Lana
 
Introduction to Nodejs
Introduction to NodejsIntroduction to Nodejs
Introduction to NodejsGabriele Lana
 
Nodejs Explained with Examples
Nodejs Explained with ExamplesNodejs Explained with Examples
Nodejs Explained with ExamplesGabriele Lana
 

Más de Gabriele Lana (20)

Microservice Architectures
Microservice ArchitecturesMicroservice Architectures
Microservice Architectures
 
Professional Programmer 2018
Professional Programmer 2018Professional Programmer 2018
Professional Programmer 2018
 
Beyond Phoenix
Beyond PhoenixBeyond Phoenix
Beyond Phoenix
 
Parse Everything With Elixir
Parse Everything With ElixirParse Everything With Elixir
Parse Everything With Elixir
 
The Magic Of Elixir
The Magic Of ElixirThe Magic Of Elixir
The Magic Of Elixir
 
Professional Programmer (3 Years Later)
Professional Programmer (3 Years Later)Professional Programmer (3 Years Later)
Professional Programmer (3 Years Later)
 
Resource Oriented Design
Resource Oriented DesignResource Oriented Design
Resource Oriented Design
 
Agileday Coderetreat 2013
Agileday Coderetreat 2013Agileday Coderetreat 2013
Agileday Coderetreat 2013
 
Milano Legacy Coderetreat 2013
Milano Legacy Coderetreat 2013Milano Legacy Coderetreat 2013
Milano Legacy Coderetreat 2013
 
Minimum Viable Product
Minimum Viable ProductMinimum Viable Product
Minimum Viable Product
 
API Over HTTP
API Over HTTPAPI Over HTTP
API Over HTTP
 
coderetreat
coderetreatcoderetreat
coderetreat
 
Professional Programmer
Professional ProgrammerProfessional Programmer
Professional Programmer
 
It is not supposed to fly but it does
It is not supposed to fly but it doesIt is not supposed to fly but it does
It is not supposed to fly but it does
 
Introduction to Nodejs
Introduction to NodejsIntroduction to Nodejs
Introduction to Nodejs
 
MongoDB With Style
MongoDB With StyleMongoDB With Style
MongoDB With Style
 
Nosql
NosqlNosql
Nosql
 
Magic of Ruby
Magic of RubyMagic of Ruby
Magic of Ruby
 
Nodejs Explained with Examples
Nodejs Explained with ExamplesNodejs Explained with Examples
Nodejs Explained with Examples
 
Why couchdb is cool
Why couchdb is coolWhy couchdb is cool
Why couchdb is cool
 

Último

Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Paola De la Torre
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...Neo4j
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking MenDelhi Call girls
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...gurkirankumar98700
 
Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024The Digital Insurer
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking MenDelhi Call girls
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processorsdebabhi2
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024Rafal Los
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...apidays
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Drew Madelung
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEarley Information Science
 
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsTop 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsRoshan Dwivedi
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfEnterprise Knowledge
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking MenDelhi Call girls
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024Results
 

Último (20)

Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
Kalyanpur ) Call Girls in Lucknow Finest Escorts Service 🍸 8923113531 🎰 Avail...
 
Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsTop 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024
 

Test Driven Development Debugging Sucks Testing Rocks

  • 2. Debugging Sucks Testing Rocks
  • 3. Bugfixing Cost 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?
  • 4. Bugfixing Tests Cost Bugs/Features • Cost of tests = Number and complexity of features • How many features do you plan to have? • How complex they are?
  • 6. Fixture all the things we need to have in place in order to run a test and expect a particular outcome
  • 7. Fixture Setup Fixture Steps Fixture Setup
  • 8. SUT System Under Test is whatever thing we are testing
  • 9. Exercise SUT Fixture Steps SUT Fixture Setup Exercise SUT
  • 10. Verify Result Fixture Steps SUT Fixture Setup Exercise SUT Verify Result
  • 11. “Don't call us, we'll call you”
  • 12. Fixture Teardown Fixture Steps SUT Fixture Setup Exercise SUT Verify Result Fixture Teardown
  • 13. a test is a good one if... • 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
  • 14. the kind of test is not determined by the used tool
  • 15. Unit tests A 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
  • 16. 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
  • 17. 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
  • 18. 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
  • 19. 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
  • 21. As a developer, I want to learn TDD, so that I can write clean code that works
  • 22. 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
  • 23. Clean code that works Clean 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
  • 24. 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
  • 25. Simple design The code is simple enough when it: 0. Pass all the tests 1. Expresses every idea that we need to express 2. Contains no duplication 3. Has the minimum number of classes and functions (In this order) Adapted from Extreme Programming Installed by Ron Jeffries et al.
  • 26. Clean code that works • First we'll solve the “that works” part • Then we'll solve the “clean code” part
  • 27. Code that works could Smell Refactoring by Martin Fowler
  • 28. ... then Refactor 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 Martin Fowler
  • 29. through small steps Refactoring by Martin Fowler
  • 30. ... 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 Extract Class, Extract Subclass, Extract Interface, Replace Data Large Class Value with Object Extract Method, Replace Temporary Variable with Query, Replace Long Method Method with Method Object, Decompose Conditional
  • 31. ... fight the Smells Smell Common Refactorings Shotgun Surgery Move Method, Move Field, Inline Class Replace Parameter with Method, Introduct Parameter Object, Long Parameter List Preserve Whole Object Data Class Move Method, Encapsulate Field, Encapsulate Collection Comments Extract Method, Introduce Assertion
  • 32. an example of Refactoring and Unit tests
  • 33. 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) {
  • 35. import java.util.*; public class FileDetail { make it private Date lastModified; private String fileName; public FileDetail(Date lastModified, String fileName) { this.lastModified = lastModified; compile 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"; stub public class ScpTo { public static void send(String filePathToSend) { } // TODO: no need to implement :-( dependencies }
  • 36. is supposed to work, but how it works?
  • 37. prepare your engine :-) import org.junit.* ; import org.junit.* ; import static org.junit.Assert.* ; import static org.junit.Assert.* ; public class StubTest { public class StubTest { @Test @Test public void shouldAlwaysWork() { public void shouldNeverWork() { assertTrue(true); assertTrue(false); } } } }
  • 38. Untestable public void run() { try { Map fileDetails = new Hashtable(); BufferedReader reader = new BufferedReader(new FileReader(MainFrame.WATCHED_DATA)); ... } catch (FileNotFoundException e) { ... } }
  • 39. Extract Method 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(); }
  • 40. doing refactoring without tests is unsafe
  • 41. Characterization Test 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); }
  • 42. Characterization Test @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); }
  • 43. Magic Number @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); } 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); }
  • 44. Syntax Noise 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()); } } replace For with Loop 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()); }
  • 45. Syntax Noise 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()); } } replace Test with Assertion 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()); }
  • 46. now we can play safe // 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; } } }
  • 47. now we can play safe // 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())); } }
  • 48. 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();
  • 49. narrow your target and your tests // for each of those directory String directoryName = reader.readLine(); while (directoryName != null) { Comment // 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; } } }
  • 50. Extract Method // 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; } } }
  • 51. Extract Method 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; }
  • 52. 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"); } }); ...
  • 53. 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"); } }); ...
  • 54. Duplicated Code, Complex Conditional // 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())); }
  • 55. Consolidate duplicated conditional fragments // 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())); }
  • 56. Consolidate duplicated conditional fragments // 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.put(file.getName(), new FileDetail(new Date(), file.getName())); }
  • 57. Consolidate duplicated conditional fragments // if no previous details are given or // if details are given but file has been modified if ((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())); }
  • 58. Extract Method 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(); }
  • 59. Extract Method 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); }
  • 60. Rename Method public void dontKnowWhatItDoes(String directoryPathToBackup) throws ... { ... } 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())); } }
  • 61. Extract Method 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())); }
  • 62. Extract Method 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); }
  • 63. Replace temporary variable with Query 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); }
  • 64. Replace temporary variable with Query 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); }
  • 65. Introduce Generics 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); }
  • 66. Introduce Generics 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); }
  • 67. Inconsistent Name 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(); }
  • 68. Extract Method, replace For with Loop 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()); } }
  • 69. 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); }
  • 70. Feature Envy, Large Class 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())); }
  • 71. Primitive Obsession, Data Clumps 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())); }
  • 72. 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)); } }
  • 73. 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); }
  • 74. Feature Envy 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); }
  • 75. Move Method, Rename Method 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(); }
  • 76. Inconsistent Name 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(); }
  • 77. 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(); }
  • 78. 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(); }
  • 79.
  • 80. 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; }
  • 81. 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(); } ... }
  • 83. Write a test public class AdderTest { @Test public void testTwoPlusThree() { Adder a = new Adder(); assertEquals(5, a.add(2, 3)); } }
  • 84. Now it compiles 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; } }
  • 85. 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
  • 86. Just pretend 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 5; } }
  • 87. Remove the duplicated “5” 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 a+b; } }
  • 88. The procedure 1. Write a test 2. Make it compile Expected 5, was 0 3. Make it pass 4. Refactor
  • 89. Red Refactor Green Repeat every 2-10 min.
  • 90. It’s not about testing • TDD is a design technique • The tests are not the main point • The design emerges in small steps
  • 92. 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!
  • 93. 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
  • 95. 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
  • 96. No silver bullet • Needs lots of practice • Requires discipline • Must think and be alert at all times!