Pro Yearly is on sale from $80 to $50! »

EclipseCon Europe 2013: Take back Control over your Test Suite

EclipseCon Europe 2013: Take back Control over your Test Suite

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 for your Eclipse plugins. 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.

A5a5cadf1ec1a7781c109f9bc7b37028?s=128

Sebastian Benz

October 31, 2013
Tweet

Transcript

  1. TAKE BACK CONTROL OVER YOUR TEST SUITE SEBASTIAN BENZ E

    S R L A B S
  2. SLOW& BRITTLE …tests are no help

  3. #1 FOCUS YOUR TESTS

  4. Try it at home…

  5. Test your core functionality without Eclipse. Your Architecture thanks you!

  6. @Test public void generatesCode() { IProject project = workspace.getProject("MyProject"); project.create(...);

    CodeGenerator codeGenerator = new CodeGenerator(project); ! ! ! ! } Input someInput = ...; codeGenerator.generate(someInput); IFile generatedFile = project.getFile("MyFile.java"); assertTrue(generatedFile.exists()); Lack of focus
  7. import org.eclipse.core.resources.IProject; ! public class CodeGenerator { private IProject project;

    ! public CodeGenerator(IProject project) { this.project = project; } ! public void generate(Input input) { ... IFile file = project.getFile(someFileName); file.create(someContent, true, monitor); } ! } Problem: Hard-wired dependency to Eclipse workspace
  8. public class CodeGenerator { private FileSystem fileSystem; ! public CodeGenerator(FileSystem

    fileSystem) { this.fileSystem = fileSystem; } ! public void generate(Input input) { ... ! fileSystem.write(fileName, content); } ! } public interface FileSystem { ! void write(String fileName, String content); ! } simpler implementation
  9. FileSystem fileSystem = new EclipseFileSystem("MyProject"); CodeGenerator codeGenerator = new CodeGenerator(fileSystem);

    codeGenerator.generate(someInput); Eclipse Command Line Test MockFileSystem fileSystem = new MockFileSystem(); CodeGenerator codeGenerator = new CodeGenerator(fileSystem); codeGenerator.generate(someInput); fileSystem.assertExists("SomeFile.java"); JavaIOFileSystem fileSystem = new JavaIOFileSystem("~/MyProject"); CodeGenerator codeGenerator = new CodeGenerator(fileSystem); codeGenerator.generate(someInput); this wasn’t possible before
  10. EclipseFileSystem CodeGenerator Problem: classes depending on implementation details

  11. FileSystem CodeGenerator EclipseFileSystem Dependency Inversion is your friend

  12. FileSystem CodeGenerator EclipseFileSystem ModelExporter ModelImporter Integration Test? …and here? …and

    here?
  13. FileSystem CodeGenerator EclipseFileSystem Interaction Test

  14. @Test public void writesGeneratedFilesToFileSystem() { FileSystem fileSystem = mock(FileSystem.class); CodeGenerator

    codeGenerator = new CodeGenerator(fileSystem); Input someInput = ...; CodeGenerator.generate(someInput); ! verify(fileSystem).write("MyFile.java", "expected contents”); }
  15. Interaction Test FileSystem CodeGenerator EclipseFileSystem Contract Test

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

    FileSystem fileSystem = createFileSystem(); fileSystem.write("my_file.txt", "content"); assertEquals("content", contentsOf("my_file.txt")); } ! protected abstract FileSystem createFileSystem(); ! protected abstract String contentsOf(String file); ! } public class JavaIoFileSystemTest extends FileSystemContractTest { @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); ! @Override protected String contentsOf(String return Files.toString(tempFolder.newFile(fileName), UTF_8); } ! @Override protected FileSystem createFileSystem() { return new JavaIOFileSystem(tempFolder.getRoot().getAbsolutePath()); } ! } public class JavaIoFileSystemContractTest extends FileSystemContractTest { ! @Override protected String contentsOf(String fileName) { return Files.toString(fileName)); } ! @Override protected FileSystem createFileSystem() { return new JavaIOFileSystem(...); } ! }
  17. Contract Interaction + Tests over Integration Tests. Favor

  18. #2 WRITE CLEAN TESTS

  19. 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()); } }
  20. 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()); } } Problem: ‘new’ couples implementation and test
  21. Avoid duplicated coupling between test and implementation.

  22. 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()); } }
  23. public class PersonTest { @Test public void underAgePersonsAreNotAllowedToDrive() { Person

    underAgePerson = createPerson(17); assertFalse(underAgePerson.isAllowedToDrive()); } ! @Test public void adultsAreAllowedToDrive() { Person adultPerson = createPerson(18); assertTrue(adultPerson.isAllowedToDrive()); } ! @Test public void underAgePersonsAreNotAllowedToDrink() { Person underAgePerson = createPerson(17); assertFalse(underAgePerson.isAllowedToDrink()); } ! ... ! private void createPerson(int age){ return new Person(age); } }
  24. 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 void anUnderAgePerson(){ return new Person(17); } ! private void anAdultPerson(){ return new Person(18); } ! private void createPerson(int age){ return new Person(age); } }
  25. public class PersonTest { @Test public void underAgePersonsAreNotAllowedToDrive() { assertFalse(PersonBuilder.underAgePerson().build().isAllowedToDrive());

    } ! @Test public void adultsAreAllowedToDrive() { assertTrue(PersonBuilder.adultPerson().build().isAllowedToDrive()); } ! @Test public void underAgePersonsAreNotAllowedToDrink() { assertFalse(PersonBuilder.underAgePerson().build().isAllowedToDrink()); } ! }
  26. public class CodeGenerationInWorkspaceTest { @Before public void cleanWorkspace(){ Workspaces.clean(); }

    ... ! ! } public class WorkspaceFileImportTest { @Before public void cleanWorkspace(){ Workspaces.clean(); } ... ! }
  27. public class CodeGenerationInWorkspaceTest { @Before public void cleanWorkspace(){ Workspaces.clean(); Editors.closeAllOpen();

    } ... ! ! } public class WorkspaceFileImportTest { @Before public void cleanWorkspace(){ Workspaces.clean(); } ... ! }
  28. Encapsulate test infrastructure setups.

  29. public class WorkspaceRule implements TestRule { ! @Override public Statement

    apply(final Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { clearWorkspace(); base.evaluate(); } }; } private void clearWorkspace(){ Workspaces.clean(); Editors.closeAllOpen(); } ! } JUnit Rules FTW
  30. public class WorkspaceRule implements TestRule { ! @Override public Statement

    apply(final Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { clearWorkspace(); base.evaluate(); } }; } private void clearWorkspace(){ Workspaces.clean(); Editors.closeAllOpen(); } ! } JUnit Rules FTW this runs the test
  31. public class CodeGenerationInWorkspaceTest { @Rule public WorkspaceRule workspace = new

    WorkspaceRule(); ! ... ! } public class WorkspaceFileImportTest { @Rule public WorkspaceRule workspace = new WorkspaceRule(); ! ! ... ! } JUnit Rules FTW
  32. #3 LISTEN TO YOUR TESTS!

  33. How can I improve my design to make the test

    easier? When something is hard to test, ask yourself:
  34. Tests give you important feedback about your system’s design. -

    you just have to listen!
  35. THREE WAYS TO TAKE BACK CONTROL #1 Focus your tests

    #2 Write clean tests #3 Listen to your tests
  36. 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 - 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 - Working Effectively with Legacy Code, Michael Feathers, http:// www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/ dp/0131177052
  37. @sebabenz www.jnario.org E S R L A B S