Imagine a bacon-wrapped Ferrari. Still not better than our free technical reports.
See all our reports

Oleg’s half-smart hacks: Put a timestamp on @Ignore with Java 8’s new Date and Time API

ignore-tests

Sometimes, there are moments when it seems that nothing is right. Usually, you feel this after a huge refactoring in the system, when you introduce massive changes without mercy. Then, the sky is falling, the grass gets less green by the minute and your safety net of tests fails to catch you.

It’s a problem when you have a major restructuring of your project while also having a great amount of unit tests covering your old system design. You know what they do and don’t want to just delete them because they still going to be useful later.

You may also face a requirement to make a change in the system that is more important than to have the perfect coverage. Don’t throw stuff at me, but imagine you have two layers of tests present: unit tests and integration tests. And you don’t have time to evolve both of them at the same time, so you trust your integration tests because they simulate the user more accurately than low-level unit tests.

At the same time, you still want a green build. You know that there are failures, but the most major indicator of how stable the project is its green/red lights.

Why can’t I just ignore tests that fail?

There are multiple ways to deal with tests for which you neither have time to fix nor the will to delete them.

The usual solution is to slap an @Ignore annotation onto the test. In a team of good developers, this action is also accompanied by creating a task to unignore them at a later date. But, as you know, it’s easy for this this task to get pushed forward from sprint to sprint, sitting right under your nose until it becomes part of the background and you stop noticing it.

What is Oleg’s half-smart hack, you may ask? A timestamped @Ignore. Here’s how an annotation like that could look:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface IgnoreUntil {
  String until() default "1970-01-01";
}

Processing class annotations is as trivial as it can get, we just get the Java Class instance and ask for an annotation of our type. Below is all the code for the custom JUnit test runner you will need, but if you want to here’s a Github repo as well: TimedIgnore.

public class TimedIgnoreTestRunner extends BlockJUnit4ClassRunner {
  @Override
  public void run(RunNotifier notifier) {
    IgnoreUntil ignoreUntil = getTestClass().getJavaClass().getAnnotation(IgnoreUntil.class);

    if(shouldIgnore(ignoreUntil)) {
      notifier.fireTestIgnored(getDescription());
      return;
    }
    super.run(notifier);
  }

  boolean shouldIgnore(IgnoreUntil ignoreUntil) {
    if(ignoreUntil == null) {
      return false;
    }
    DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE;
    //how awesome Java 8 date and time api is? We don't have to deal with timezones. Pure joy!
    LocalDate date = LocalDate.parse(ignoreUntil.until(), formatter);
    return date.isAfter(LocalDate.now());
  }

  public TimedIgnoreTestRunner(Class<?> klass) throws InitializationError {
    super(klass);
  }
}

All this code is super simple, and we created an annotation to be applicable to test classes. Our smart test runner is capable of recognizing the @IgnoreUntil and checking if it is time to run the test properly. And now with Java 8’s new Date and Time API it is even pleasant to work with dates. LocalDate class represents date without timezones, timestamps or anything that distracts you. Basically, it’s what humans think when they say December 1st or February 2nd.

How to know if it works?

One good thing is that we can use the same project to illustrate how the annotation is working. Indeed it does work well, executing mvn clean test shows that a test which is ignored until a date in the future is not executed, but when the time on the annotation is in the past, it runs. Below you can see an example of such test class. See how easy it is to somewhat ignore your tests now?

@RunWith(TimedIgnoreTestRunner.class)
@IgnoreUntil(until = "2100-12-10")
public class IgnoredTest {

  @Test
  public void testUnderTimedIgnore() {
    fail("This test is expected to fail");
  }
}

Pretty cool, eh? Almost like the Mailbox app for fighting email bloat, this hack will let you safely ignore tests that you don’t feel like fixing for a couple of weeks, and they’ll catch up with you further down the road. I think we’re onto something here.

timed-ignore

PS. on a serious note, what do you do when tests fail and paint your build red, but you don’t have an immediate capacity to fix them? Do you ignore the build status, dig deeper to check what has failed, ignore or delete that test?

Describe your approach in comments below or find and chat with me on Twitter: @shelajev.


For more on unit tests, mocking and other stuff, check out the RebelLabs report Go Away, Bugs! Keeping Your Code Safe with JUnit, TestNG and Mockito by Kostis Kapelonis.

SEE THE PDF

 

  • htfv

    So this is why new JRebel releases break old functionality so often ;)