Refactoring JUnit Tests

Franziska Sauerwein

@Singsalad

Setup (find a pair!)

  • install IDE (USB)
  • import/download project
  • run ConferenceTest

https://github.com/franziskas/RefactoringJUnitTests

The problem: Tests that are

  • complex to read
  • hard to maintain
  • take a long time to run

-> Tests lose their value

Some reasons for this:

  • complex object models
  • bad code design
  • use of external resources

What we can do (short term)

  • simplify setup & assertions
  • clean test code
  • separate dependencies

Builder

  • hides irrelevant data
  • allows easy changes to test defaults (e.g. new validation)
  • simplifies creation & enable reuse

Matcher

  • increases test readability
  • improves test failure message
  • is combinable & reusable

Combining Matchers

allOf(..) 

anyOf(..) 

not(..)

Object Matchers

equalTo(..) / sameInstance(..)

instanceOf(..) / isCompatibleType(..)

notNullValue(..) / nullValue(..)

Collection Matchers

array(..) / hasItemInArray(..)

hasEntry(..) / hasKey(..) / hasValue(..)

hasItem(..), hasItems(..)

Number Matchers

closeTo(..)

greaterThan(..) / greaterThanOrEqualTo(..)

lessThan(..) / lessThanOrEqualTo(..)

String Matchers

equalToIgnoringCase(..)

equalToIgnoringWhiteSpace(..)

containsString(..)

endsWith(..) / startsWith(..)

Custom Matchers

private Matcher<User> hasEmail() {
   return new TypeSafeDiagnosingMatcher<Foo>() {

      @Override
      public void describeTo(final Description description) {
         description.appendText("expected user to have an email");
      }
 
      @Override
      protected boolean matchesSafely(final User user, 
                   final Description mismatchDescription) {
         mismatchDescription
          .appendText(" was ")
          .appendValue(user.getEmail();

         return user.getEmail()!= null 
                && user.getEmail().length() > 3;
}   };}

Custom Matchers

private Matcher<User> hasEmail() {
   return new TypeSafeDiagnosingMatcher<Foo>() {

      @Override
      public void describeTo(final Description description) {
         description.appendText("expected user to have an email");
      }
 
      @Override
      protected boolean matchesSafely(final User user, 
                   final Description mismatchDescription) {
         mismatchDescription
          .appendText(" was ")
          .appendValue(user.getEmail();

         return user.getEmail()!= null 
                && user.getEmail().length() > 3;
}   };}

Custom Matchers

private Matcher<User> hasEmail() {
   return new TypeSafeDiagnosingMatcher<Foo>() {

      @Override
      public void describeTo(final Description description) {
         description.appendText("expected user to have an email");
      }
 
      @Override
      protected boolean matchesSafely(final User user, 
                   final Description mismatchDescription) {
         mismatchDescription
          .appendText(" was ")
          .appendValue(user.getEmail();

         return user.getEmail()!= null 
                && user.getEmail().length() > 3;
}   };}

Custom Matchers

private Matcher<User> hasEmail() {
   return new TypeSafeDiagnosingMatcher<Foo>() {

      @Override
      public void describeTo(final Description description) {
         description.appendText("expected user to have an email");
      }
 
      @Override
      protected boolean matchesSafely(final User user, 
                   final Description mismatchDescription) {
         mismatchDescription
          .appendText(" was ")
          .appendValue(user.getEmail();

         return user.getEmail()!= null 
                && user.getEmail().length() > 3;
}   };}

www.marcphilipp.de/blog/2013/01/02/hamcrest-quick-reference/

Modularisation 

  • one test - one concern
  • unit tests for functionality
  • larger tests for journey

Summary

  • fix the code design
  • in the meantime, ease the pain
  • treat test code at least as well as production code

Try it yourself!

Refactoring JUnit tests

By Franziska Sauerwein

Refactoring JUnit tests

Code that grows over time without refactoring can get quite messy and hard to test. The same applies for to test code, and curiously, they often appear together. Not always is it possible to fix the design problems first, creating a need to write clean tests for hard to test code. In this workshop, participants will learn some techniques to clean up messy JUnit tests to make them more readable and easier to change. After a short introduction and an example refactoring, participants will work in pairs to refactor some example unit tests and learn how to write better ones from scratch. Key takeaways: create complex objects easily with the builder patternuse hamcrest matchers to improve your assertionsdiscover the power of JUnit rules Prerequisites: Please install the latest IntelliJ Community edition (https://www.jetbrains.com/idea/download/) which is available for Mac, Windows and Linux, and have Java and JUnit running from within this.

  • 2,871