JUnit theories

So, theories. Whereas parameterized tests are generally built around a known set of inputs and expected outputs, a theories-based test focuses on the generalized relationship between inputs and outputs. It might help you to understand the difference if you try to recall learning about mathematical functions for the first time in junior high or high school.

Structure of a theory class

Structurally, a theory-based class is simpler than a parameterized test class. The class declaration should be annotated with @RunWith(Theories.class), and it must provide two entities:

  1. A data method that generates and returns test data, and
  2. A theory.

The data method must be annotated with @DataPoints, and each theory must be annotated with @Theory. As with an ordinary unit test, each theory should contain at least one assertion. I recommend limiting each theory to a single assertion; this constrains the scope of any given theory and makes failures more meaningful.

But what exactly is a theory?

Functionally, a theory is a just a kind of test — specifically, an alternative to JUnit's parameterized tests. Semantically, a theory encapsulates the tester's understanding of an object's universal behavior. That is, whatever it is that a theory asserts, it is expected to be true for all data. In theory, theories should be especially useful for finding bugs in edge cases.

Contrast this with a typical unit test, which asserts that a specific data point will have a specific outcome, and only asserts that. (For this reason, typical unit tests are sometimes called example-based tests to contrast them with theories.)

Theory example

    1 import static org.junit.Assert.assertThat;
    2 import static org.junit.matchers.JUnitMatchers.hasItem;
    3 
    4 import org.junit.experimental.theories.DataPoints;
    5 import org.junit.experimental.theories.Theories;
    6 import org.junit.experimental.theories.Theory;
    7 import org.junit.runner.RunWith;
    8 
    9 import com.yoyodyne.employee.Oligarch;
   10 
   11 @RunWith(Theories.class)
   12 public class TheoriesExample 
   13 {
   14     /\*
   15      \* Our test data are stored in an array of Oligarch objects.
   16      \* Since this is a method like any other, you can generate data 
   17      \* dynamically or fetch it from an external source, if needed.
   18      \*/
   19     @DataPoints
   20     public static Oligarch[] data() {
   21         return new Oligarch[] {
   22             new Oligarch("Monty Burns"),
   23             new Oligarch("Don Geiss"),
   24             new Oligarch("Arthur Jensen")
   25         }
   26     }
   27     
   28     /\*
   29      \* This theory confirms that all oligarchs have yachts.
   30      \*/
   31     @Theory
   32     public void theoryOligarchsHaveYachts(Oligarch suit) {
   33         assertThat(suit.getVehicles(), hasItem("yacht"));
   34     }
   35     
   36     /\*
   37      \* This theory confirms that all oligarchs have hearts of darkest black.
   38      \*/
   39     @Theory
   40     public void theoryOligarchsAreEvil(Oligarch suit) {
   41         assertThat(suit.getSoul().getOwner().getName(), is("The Devil"));
   42     }
   43 }

When to use theories and when to use parameterized tests

As the example code hopefully shows, a theory class is generally easier to read than a parameterized test class; note how this class doesn't need fields or a constructor. And theories are intended to be more expressive of the tester's goals; the original paper that proposed theories called them "specifications that catch bugs."

You should have noticed, however, that there is no means of pairing a specific result with a specific data point. You should use theories when you can express in the form of an assertion the general relationship between a data point and an expected result, and when that relationship will hold true for all data.

In cases where you have a large set of inputs with varying results, then, you will still need to write parameterized tests. Parameterized tests give the you greater flexibility as an author, but the semantics of the test are usually implicit. (If you want to be picky, you could actually write a test in the parameterized test style that acted like a theory, but nobody except a tester likes a pedant.)

Comments:

Post a Comment:
Comments are closed for this entry.
About

A weblog about identity management and testing. See here.

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today