$30 off During Our Annual Pro Sale. View Details »

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.

Sebastian Benz

October 31, 2013
Tweet

More Decks by Sebastian Benz

Other Decks in Programming

Transcript

  1. TAKE BACK
    CONTROL OVER
    YOUR TEST
    SUITE
    SEBASTIAN BENZ
    E S R
    L A B S

    View Slide

  2. SLOW& BRITTLE
    …tests are no help

    View Slide

  3. #1 FOCUS YOUR TESTS

    View Slide

  4. Try it at home…

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  10. EclipseFileSystem
    CodeGenerator
    Problem: classes depending on implementation details

    View Slide

  11. FileSystem
    CodeGenerator
    EclipseFileSystem
    Dependency Inversion is your friend

    View Slide

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

    View Slide

  13. FileSystem
    CodeGenerator
    EclipseFileSystem
    Interaction Test

    View Slide

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

    View Slide

  15. Interaction Test
    FileSystem
    CodeGenerator
    EclipseFileSystem
    Contract Test

    View Slide

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

    View Slide

  17. Contract Interaction
    + Tests over Integration Tests.
    Favor

    View Slide

  18. #2 WRITE CLEAN TESTS

    View Slide

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

    View Slide

  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

    View Slide

  21. Avoid duplicated coupling between
    test and implementation.

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  28. Encapsulate test infrastructure setups.

    View Slide

  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

    View Slide

  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

    View Slide

  31. public class CodeGenerationInWorkspaceTest {
    @Rule public WorkspaceRule workspace = new WorkspaceRule();
    !
    ...
    !
    }
    public class WorkspaceFileImportTest {
    @Rule public WorkspaceRule workspace = new WorkspaceRule();
    !
    !
    ...
    !
    }
    JUnit Rules FTW

    View Slide

  32. #3 LISTEN TO YOUR TESTS!

    View Slide

  33. How can I improve my design
    to make the test easier?
    When something is hard to test,
    ask yourself:

    View Slide

  34. Tests give you important feedback
    about your system’s design.
    - you just have to listen!

    View Slide

  35. THREE WAYS TO TAKE BACK CONTROL
    #1 Focus your tests
    #2 Write clean tests
    #3 Listen to your tests

    View Slide

  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

    View Slide

  37. @sebabenz
    www.jnario.org
    E S R
    L A B S

    View Slide