miércoles, febrero 23, 2011

Du Erkennst Mich Nicht Wieder Unerkannt Bin Ich Die halbe Nacht Noch Um Die Häuser Gerannt

JUnit 4 has many features, which can be considered "hidden". I am sure that developers that always read JUnit changelogs will know some of these features, but for those who don't read changelogs, I am going to discover them:

@Ignore: this annotation indicates to JUnit runner that this test should not be executed. This annotation makes sense in TDD/BDD environments, where you write unit test classes first and then source code is written. It is important that if you have not implemented a functionality, in your CI system does not appear as a failed, but as ignored, so with quick view (in Jenkins?) you can get an idea of which specifications pass, which failed, and which are pending to implement.
@Ignore("Pending Implementation")
@Test
public void anEmailShouldBeSentAfterTransaction() {}

@RunWith(Parameterized): this annotation is used for data-driven test. Test class defined as parameterized, takes a large set of test data, and the expected result, and verifies expected value with calculated value. Think as a Spreadsheet where last column is the expected value, and previous columns are input data. Then your test simply iterate over each row, using N-1 columns as input parameters, and N column as expected value. This annotation is useful when you need flexibility between data and expected results, which can vary depending on input.


Column names should not be included.
@RunWith(Parameterized.class)
public class HolidaysCalculatorDataTest {
 private int workedDays;
 private int expectedNumberHolidays; 
 @Parameters
 public static Collection<Object[]> data() throws IOException {         
 //Object[] represents one row, where last position is expected value.
  InputStream is = new FileInputStream("./src/test/resources/...");
  return new SpreadsheetData(is).getData();
 } 
 public HolidaysCalculatorDataTest(int workedDays, int expectedNumberHolidays) {
  //Constructor takes fields from test data.Last parameter should be the expected value.
  this.workedDays = workedDays;
  this.expectedNumberHolidays = expectedNumberHolidays;
 } 
 @Test
 public void shouldCalculateCorrectHolidays() {
  HolidayCalculator holidayCalculator = ...;
  assertThat(holidayCalculator.calculate(this.workedDays), is(this.expectedNumberHolidays));
 }
}


@Rule TemporaryFolder: If your test should write data to a file or read data from a file, this file could no be created in a static directory because computer that executes test, could not have write permissions or parent path simply do not exists, and more important if you forget deleting those temporary files, your testing environment starts to grow with trash data. To avoid that, TemporaryFolder rule is available. This temporary folder is a folder, which provides methods for creating files. All files created using TemporaryFolder are deleted after each Test execution.

@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Test
public void aFileShouldBeCreatedWithData() {
     File output = folder.newFile("data.txt");
     //Use file
     ...
     //Assertation with file data
     assertThat(output, contains("log"));
}


@Rule TestWatchman: Usually when a test passes or fails, the result is notified to user, in case of running into IDE, with green/red bar, or in case of Maven results are stored into a surefire report. But what's happening if you wanted to notify/report the result of a test differently? TestWatchman interface helps you. This interface defines two methods (failed and succeeded) called depending on result of test. This can be useful for QA department. Imagine your QA requires a Spreadsheet where you have a column with specification id, the next column is the test name that check the specification, and the third one, if PASS or FAIL so anyone could view status of the project in a quick view. Because filling this spreadsheet manually is not human, you need to automatize spreadsheet modification. So simply implements TestWatchman interface for modifying Spreadsheet depending on result.
@Rule
public MethodRule watchman = new TestWatchman() {
     @Override
     public void failed(final Throwable e, final FrameworkMethod method) {
          System.out.println("Failed: " + method.getName());
     }
    @Override
     public void succeeded(final FrameworkMethod method) {
          System.out.println("Succeeded: " + method.getName());
     }
};

@Rule Timeout: This rule defines the maximum time that a test should take. If after defined time test has no result, execution of test is canceled and is considered as failed. I usually use this feature in performance tests, for monitoring Hibernate queries. In my work, response time is so important, queries that takes more than 60 ms should be changed, re-factored or at least justify why takes more than 60 ms. Thanks of timeout rule, I have a flag (the test fails) that warns me in case some query does not meet this limitation. Also can be used in systems where any kind of time response between heterogeneous systems is required.
@Rule
public MethodRule globalTimeout = new Tomeout(60);

@RunWith(Categories): Categories is a new feature in JUnit that is used for grouping tests. Tests are grouped (using @Category annotation) as different types using interfaces. Test classes or test methods can be annotated, so a test class can contain test methods of different groups. Using Categories implies:

- Define categories. In fact implies creating an interface, named with group name.
public interface IntegrationTests {}
public Interface PerformanceTests extends IntegrationTests {}

- Annotate tests with @Category.
public class HolidaysCalculatorTest {
  
      ...
     @Category(PerformanceTests.class)
     @Test
     public void adminUserShouldExists() {
          ...
     }
     @Category(IntegrationTests.class)
     @Test
     public void adminUserShouldSendEmails() {
          ...
     }
     @Test
     public void thisIsANormalTest() {
          ...
     }
}

- Set up a test suite with active categories. Also is required to mention which tests classes belongs to Test Suite.
@RunWith(Categories.class)
@IncludeCategory(PerformanceTests.class) //Category to execute
@SuiteClasses({HolidaysCalculatorTest.class})
public class PerformanceTestSuite() {
}

Categories are really helpful for grouping your test cases, and not execute them each time. For example in my projects I have a Performance Category. This category groups the tests that validates that performance are acceptable for my requirements. Performance Test Suite is not run every Night (as Unit Tests do), but only on Fridays night.


@RunWith(Theories): A theory is something that is expected to be true for all data. Theories should be used for finding bugs in edge cases. Theories are like Parameterized on the fact that the two uses a large set of test data. The difference is that in theories there is no expected value for each row, there is only one valid result because theories should hold true for all data with same expected value.

@RunWith(Theories.class)
public class CarTheories {

     @DataPoints
     public static Car[] data() {
          return getAllCars();
     }

     //Theory method have one parameter where theory is asserted.
    @Theory
    public void theoryAllCarsShouldHaveFourWheels(Car car) {
         assertThat(car.getNumberWheels(), is(4));
    }
}

I have explained some new features on JUnit 4.8.x. In fact there are more @Rule classes, but I have decided to show only those that are useful in my diary work. I wish you find it interesting and I am sure your unit testing live will change after reading the post.

0 comentarios: