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!

Made with Slides.com