Take (back) Control over your Test Suite (I.T.A.K.E 2014)

Take (back) Control over your Test Suite (I.T.A.K.E 2014)

We all know testing is important. Unfortunately, even the best intentions are often crushed by slow and brittle tests hindering development more than they actually help.

But this must not necessarily be the case. In this talk I want to present some simple techniques helping you to write better tests. In particular, the following topics will be covered:

- how to write maintainable tests that don’t break on every change.
- how tests help you writing better code.
- how to speedup your test suite.
- how to avoid integration tests

http://2014.itakeunconf.com/sebastian-benz-take-back-control-over-your-test-suite/

A5a5cadf1ec1a7781c109f9bc7b37028?s=128

Sebastian Benz

May 29, 2014
Tweet

Transcript

  1. 12.

    T

  2. 13.

    T

  3. 15.

    High test to code ratio indicates high coupling Light Bulb

    by Roy Verhaag from The Noun Project As long as extracted classes do to not contain new behaviour, more tests do not necessarily improve the feedback but increase fragility, as tests are more coupled to the implementation.
  4. 16.

    ! EclipseFileSystem fileSystem = new EclipseFileSystem(); CodeGenerator codeGenerator = new

    CodeGenerator(fileSystem); ! ! Input someInput = ...; codeGenerator.generate(someInput); assertEquals("contents”, fileSystem.contensOf(“MyFile.java")); Problem: coupling to implementation details makes it hard to focus test
  5. 17.

    public class CodeGenerator { private EclipseFileSystem fileSystem; ! public CodeGenerator(EclipseFileSystem

    fileSystem) { this.fileSystem = fileSystem; } ! public void generate(Input input) { ... fileSystem.writeFile(fileName, content); } ! }
  6. 18.

    public class CodeGenerator { private FileSystem fileSystem; ! public CodeGenerator(FileSystem

    fileSystem) { this.fileSystem = fileSystem; } ! public void generate(Input input) { ... fileSystem.writeFile(fileName, content); } ! } public interface FileSystem { ! void writeFile(String fileName, String content); ! void contentsOf(String fileName,); ! }
  7. 19.

    FileSystem fileSystem = new InMemoryFileSystem(); CodeGenerator codeGenerator = new CodeGenerator(fileSystem);

    codeGenerator.generate(someInput); assertEquals("contents”, fileSystem.contensOf(“MyFile.java")); Faster, more focused test based on abstraction
  8. 24.

    FileSystem fileSystem = mock(FileSystem.class); CodeGenerator codeGenerator = new CodeGenerator(fileSystem); Input

    someInput = ...; codeGenerator.generate(someInput); verify(fileSystem).writeFile("MyFile.java", "expected contents");
  9. 26.

    public abstract class FileSystemContractTest { @Test public void writesContentToFile() {

    FileSystem fileSystem = createFileSystem(); fileSystem.writeFile("my_file.txt", "content"); assertEquals("content", contentsOf("my_file.txt")); } ! protected abstract FileSystem createFileSystem(); ! } public class EclipseFileSystemContractTest extends FileSystemContractTest { ! @Override protected FileSystem createFileSystem() { return new EclipseFileSystem(...); } ! }
  10. 27.

    FEEDBACK > FRAGILITY Before writing a test, ask yourself: “Does

    the feedback by the test justify the potential fragility?”
  11. 28.

    and

  12. 31.

    public class PersonTest { @Test public void underAgePersonsAreNotAllowedToDrive() { Person

    underAgePerson = new Person(17); assertFalse(underAgePerson.isAllowedToDrive()); } ! @Test public void adultsAreAllowedToDrive() { Person adultPerson = new Person(18); assertTrue(adultPerson.isAllowedToDrive()); } ! @Test public void underAgePersonsAreNotAllowedToDrink() { Person underAgePerson = new Person(17); assertFalse(underAgePerson.isAllowedToDrink()); } ! @Test public void adultsAreAllowedToDrink() { Person adultPerson = new Person(18); assertTrue(adultPerson.isAllowedToDrink()); } }
  13. 32.

    public class PersonTest { @Test public void underAgePersonsAreNotAllowedToDrive() { Person

    underAgePerson = new Person(17); assertFalse(underAgePerson.isAllowedToDrive()); } ! @Test public void adultsAreAllowedToDrive() { Person adultPerson = new Person(18); assertTrue(adultPerson.isAllowedToDrive()); } ! @Test public void underAgePersonsAreNotAllowedToDrink() { Person underAgePerson = new Person(17); assertFalse(underAgePerson.isAllowedToDrink()); } ! @Test public void adultsAreAllowedToDrink() { Person adultPerson = new Person(18); assertTrue(adultPerson.isAllowedToDrink()); } }
  14. 34.

    public class PersonTest { @Test public void underAgePersonsAreNotAllowedToDrive() { Person

    underAgePerson = aPersonWithAge(17); assertFalse(underAgePerson.isAllowedToDrive()); } ! @Test public void adultsAreAllowedToDrive() { Person adultPerson = aPersonWithAge(18); assertTrue(adultPerson.isAllowedToDrive()); } ! @Test public void underAgePersonsAreNotAllowedToDrink() { Person underAgePerson = aPersonWithAge(17); assertFalse(underAgePerson.isAllowedToDrink()); } ! ... ! private Person aPersonWithAge(int age){ return new Person("any name", age); } }
  15. 35.

    public class PersonTest { @Test public void underAgePersonsAreNotAllowedToDrive() { assertFalse(anUnderAgePerson().isAllowedToDrive());

    } ! @Test public void adultsAreAllowedToDrive() { assertTrue(anAdultPerson().isAllowedToDrive()); } ! @Test public void underAgePersonsAreNotAllowedToDrink() { assertFalse(anUnderAgePerson().isAllowedToDrink()); } ! private Person anAdultPerson(){ return aPersonWithAge(18); } ! private Person anUnderAgePerson(){ return aPersonWithAge(17); } ! private Person aPersonWithAge(int age){ return new Person("any name", age); } }
  16. 36.

    public class PersonTest { @Test public void underAgePersonsAreNotAllowedToDrive() { assertFalse(anUnderAgePerson().isAllowedToDrive());

    } ! @Test public void adultsAreAllowedToDrive() { assertTrue(anAdultPerson().isAllowedToDrive()); } ! @Test public void underAgePersonsAreNotAllowedToDrink() { assertFalse(anUnderAgePerson().isAllowedToDrink()); } ! private Person anAdultPerson(){ return aPersonWithAge(Person.FULL_AGE); } ! private Person anUnderAgePerson(){ return aPersonWithAge(Person.FULL_AGE - 1); } ! private Person aPersonWithAge(int age){ return new Person("any name", age); } } Beware of hidden duplicated coupling
  17. 37.

    public class PersonTest { @Test public void underAgePersonsAreNotAllowedToDrive() { assertFalse(Persons.anUnderAgePerson().isAllowedToDrive());

    } ! @Test public void adultsAreAllowedToDrive() { assertTrue(Persons.anAdultPerson().isAllowedToDrive()); } ! @Test public void underAgePersonsAreNotAllowedToDrink() { assertFalse(Persons.anUnderAgePerson().isAllowedToDrink()); } ! }
  18. 39.

    public class CodeGenerationInWorkspaceTest { @Before public void setup(){ Workspaces.clean(); }

    ... ! ! } public class WorkspaceFileImportTest { @Before public void setup(){ Workspaces.clean(); } ... ! }
  19. 40.

    public class CodeGenerationInWorkspaceTest { @Before public void setup(){ Editors.closeAllOpen(); Workspaces.clean();

    } ... ! ! } public class WorkspaceFileImportTest { @Before public void setup(){ Workspaces.clean(); } ... ! }
  20. 42.

    public class WorkspaceRule implements TestRule { ! @Override public Statement

    apply(final Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { Workspaces.clean(); Editors.closeAllOpen(); base.evaluate(); } }; } ! } JUnit Rules FTW
  21. 43.

    public class CodeGenerationInWorkspaceTest { @Rule public WorkspaceRule workspace = new

    WorkspaceRule(); ! ... ! } public class WorkspaceFileImportTest { @Rule public WorkspaceRule workspace = new WorkspaceRule(); ! ! ... ! } JUnit Rules FTW Favour composition over inheritance
  22. 51.

    Take testing pains as a chance to ask: “How can

    I improve my design to make testing easier?”
  23. 52.

    THREE WAYS TO TAKE BACK CONTROL #1 Focus your tests

    #2 Write clean tests #3 Listen to your tests
  24. 54.

    Recommended Reading -Growing Object-Oriented Systems guided by tests, Nat Pryce

    & Steve Freeman, http://www.growing-object-oriented-software.com -XUnit Test Patterns, Gerard Meszaros, http://xunitpatterns.com -Michael Feathers - the deep synergy between testability and good design https://www.youtube.com/watch?v=4cVZvoFGJTU&feature=kp -Integration Tests are Scam, J. B. Rainsberger, http://www.infoq.com/ presentations/integration-tests-scam -Subcutaneus Tests, Martin Fowler, http://martinfowler.com/bliki/ SubcutaneousTest.html -Duplicated Test Code & High Coupling, Jason Gorman, http:// codemanship.co.uk/parlezuml/blog/?postid=1182