Upgrade to Pro — share decks privately, control downloads, hide ads and more …

New Features in JUnit 4.x

New Features in JUnit 4.x

Marc Philipp

March 14, 2011
Tweet

More Decks by Marc Philipp

Other Decks in Programming

Transcript

  1. About JUnit Kent Beck: A programmer-oriented testing framework for Java.

    David Saff: JUnit is the intersection of all possible useful Java test frameworks, not their union. Not only for Unit Tests! March 14, 2011 2
  2. A presentation about JUnit? Every Java developer knows JUnit. Really?

    Everyone thinks he knows JUnit. I thought so, too. However, a lot has happended since release 4.0! March 14, 2011 3
  3. A presentation about JUnit? Every Java developer knows JUnit. Really?

    Everyone thinks he knows JUnit. I thought so, too. However, a lot has happended since release 4.0! March 14, 2011 3
  4. A presentation about JUnit? Every Java developer knows JUnit. Really?

    Everyone thinks he knows JUnit. I thought so, too. However, a lot has happended since release 4.0! March 14, 2011 3
  5. A presentation about JUnit? Every Java developer knows JUnit. Really?

    Everyone thinks he knows JUnit. I thought so, too. However, a lot has happended since release 4.0! March 14, 2011 3
  6. A presentation about JUnit? Every Java developer knows JUnit. Really?

    Everyone thinks he knows JUnit. I thought so, too. However, a lot has happended since release 4.0! March 14, 2011 3
  7. New Assertion: assertThat(...) New Assert methods: <T> void assertThat(T actual,

    Matcher<T> matcher) <T> void assertThat(String reason, T actual, Matcher<T> matcher) Parameters: reason description displayed when assertion fails (optional) actual actual value matcher Hamcrest matcher that checks the actual value March 14, 2011 6
  8. Increased Readability import static org.hamcrest.CoreMatchers.is; public class BetterReadability { @Test

    public void withoutMatchers() { assertEquals(2, 1 + 1); } @Test public void withMatchers() { assertThat(1 + 1, is(2)); } } No more guessing about order of arguments Often more readable than traditional assertions March 14, 2011 7
  9. Increased Readability import static org.hamcrest.CoreMatchers.is; public class BetterReadability { @Test

    public void withoutMatchers() { assertEquals(2, 1 + 1); } @Test public void withMatchers() { assertThat(1 + 1, is(2)); } } No more guessing about order of arguments Often more readable than traditional assertions March 14, 2011 7
  10. Increased Readability import static org.hamcrest.CoreMatchers.is; public class BetterReadability { @Test

    public void withoutMatchers() { assertEquals(2, 1 + 1); } @Test public void withMatchers() { assertThat(1 + 1, is(2)); } } No more guessing about order of arguments Often more readable than traditional assertions March 14, 2011 7
  11. Increased Readability import static org.hamcrest.CoreMatchers.is; public class BetterReadability { @Test

    public void withoutMatchers() { assertEquals(2, 1 + 1); } @Test public void withMatchers() { assertThat(1 + 1, is(2)); } } No more guessing about order of arguments Often more readable than traditional assertions March 14, 2011 7
  12. Combining Matchers Matchers are easily combined: assertThat(1 + 1, is(not(3)));

    assertThat(1 + 1, both(greaterThan(1)).and(lessThan(3))); March 14, 2011 8
  13. Expressive Failure Messages Traditional assertions without description assertFalse(asList(1, 2, 3).contains(2));

    Result: AssertionError: at de.andrena.junit... New assertion with matcher assertThat(asList(1, 2, 3), not(hasItem(2))); Result: AssertionError: Expected: not a collection containing <2> got: <[1, 2, 3]> March 14, 2011 9
  14. Expressive Failure Messages Traditional assertions without description assertFalse(asList(1, 2, 3).contains(2));

    Result: AssertionError: at de.andrena.junit... New assertion with matcher assertThat(asList(1, 2, 3), not(hasItem(2))); Result: AssertionError: Expected: not a collection containing <2> got: <[1, 2, 3]> March 14, 2011 9
  15. Predefined Matchers Numerous predefined matchers: Core is, not, allOf, anyOf,

    nullValue, . . . Strings containsString, startsWith, endsWith, . . . Collections hasItem, hasItems, isIn, empty, hasSize, . . . JUnit includes only a small fraction: org.hamcrest.CoreMatchers org.junit.matchers.JUnitMatchers Hamcrest offers additional matchers (hamcrest-all.jar). In addition, you can write your own matchers. March 14, 2011 10
  16. Predefined Matchers Numerous predefined matchers: Core is, not, allOf, anyOf,

    nullValue, . . . Strings containsString, startsWith, endsWith, . . . Collections hasItem, hasItems, isIn, empty, hasSize, . . . JUnit includes only a small fraction: org.hamcrest.CoreMatchers org.junit.matchers.JUnitMatchers Hamcrest offers additional matchers (hamcrest-all.jar). In addition, you can write your own matchers. March 14, 2011 10
  17. A Custom Matcher Implementation public class IsEmptyCollection extends TypeSafeMatcher<Collection<?>> {

    @Override protected boolean matchesSafely(Collection<?> collection) { return collection.isEmpty(); } @Override public void describeTo(Description description) { description.appendText("empty"); } @Factory public static Matcher<Collection<?>> empty() { return new IsEmptyCollection(); } } March 14, 2011 11
  18. A Custom Matcher Implementation public class IsEmptyCollection extends TypeSafeMatcher<Collection<?>> {

    @Override protected boolean matchesSafely(Collection<?> collection) { return collection.isEmpty(); } @Override public void describeTo(Description description) { description.appendText("empty"); } @Factory public static Matcher<Collection<?>> empty() { return new IsEmptyCollection(); } } March 14, 2011 11
  19. A Custom Matcher Implementation public class IsEmptyCollection extends TypeSafeMatcher<Collection<?>> {

    @Override protected boolean matchesSafely(Collection<?> collection) { return collection.isEmpty(); } @Override public void describeTo(Description description) { description.appendText("empty"); } @Factory public static Matcher<Collection<?>> empty() { return new IsEmptyCollection(); } } March 14, 2011 11
  20. A Custom Matcher Usage public class CollectionMatchersTest { @Test public

    void isEmpty() { TreeSet<String> set = new TreeSet<String>(); assertThat(set, new IsEmptyCollection()); // direct instantiation assertThat(set, IsEmptyCollection.empty()); // using @Factory method assertThat(set, empty()); // using static import assertThat(set, is(empty())); // syntactic sugar } } March 14, 2011 12
  21. Many Matchers – Many Classes Problem Lots of matcher classes

    with static methods we would like to import Requires manual setup of preferences in IDE . . . for all developers! Solution Generate your own matcher library March 14, 2011 13
  22. Generating a Custom Matcher Library 1. Configuration in XML <matchers>

    <factory class="org.hamcrest.core.Is"/> <factory class="de.andrena.junit.hamcrest.IsEmptyCollection"/> </matchers> 2. Run org.hamcrest.generator.config.XmlConfigurator 3. Combines all @Factory methods into a single generated class: public class Matchers { public static <T> Matcher<? super T> is(T param1) { return org.hamcrest.core.Is.is(param1); } /* ... */ public static Matcher<Collection<?>> empty() { return de.andrena.junit.hamcrest.IsEmptyCollection.empty(); } } March 14, 2011 14
  23. Summary: Matchers Assertions can often be expressed more elegantly Old

    assertion methods are still clearer sometimes Problem Java type system often gets in one’s way Requires boxing for primitive types assertThat(1 + 1, is(2)) Insufficient type inference assertThat(new TreeSet<String>(), Matchers.<String>empty()); March 14, 2011 15
  24. Parameterized Tests Parameterized test runner included since JUnit 4.0 Allows

    to supply parameters to test class constructors Arguments are defined in a public, static method @Parameters annotation Return type: List<Object[]> The runner creates an instance of the test class for each pair of test method and argument definition and executes the test. Useful when testing a calculation with a large number of input values. March 14, 2011 17
  25. Parameterized Tests Example: Fibonacci Numbers @RunWith(Parameterized.class) public class FibonacciParameterizedTest {

    @Parameters public static List<Object[]> data() { return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } }); } private final int input, expected; public FibonacciParameterizedTest(int input, int expected) { this.input = input; this.expected = expected; } @Test public void test() { assertEquals(expected, Fibonacci.compute(input)); } } March 14, 2011 18
  26. Parameterized Tests Example: Fibonacci Numbers @RunWith(Parameterized.class) public class FibonacciParameterizedTest {

    @Parameters public static List<Object[]> data() { return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } }); } private final int input, expected; public FibonacciParameterizedTest(int input, int expected) { this.input = input; this.expected = expected; } @Test public void test() { assertEquals(expected, Fibonacci.compute(input)); } } March 14, 2011 18
  27. Parameterized Tests Example: Fibonacci Numbers @RunWith(Parameterized.class) public class FibonacciParameterizedTest {

    @Parameters public static List<Object[]> data() { return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } }); } private final int input, expected; public FibonacciParameterizedTest(int input, int expected) { this.input = input; this.expected = expected; } @Test public void test() { assertEquals(expected, Fibonacci.compute(input)); } } March 14, 2011 18
  28. Theories Introduced in release 4.4 Parameterized test method with preconditions

    (assumptions) assumeThat() assumeTrue() assumeNotNull() @Theory instead of @Test Input for test methods via @DataPoint(s) annotation March 14, 2011 19
  29. Theories Example: Fibonacci Numbers import static de.andrena.junit.fibonacci.Fibonacci.compute; @RunWith(Theories.class) public class

    FibonacciTheories { @DataPoints public static int[] VALUES = { 0, 1, 2, 3, 4, 5, 6 }; @Theory public void seeds(int n) { // F(0) = 0, F(1) = 1 assumeTrue(n <= 1); assertEquals(n, compute(n)); } @Theory public void recurrence(int n) { // F(n) = F(n − 1) + F(n − 2) for n > 1 assumeTrue(n > 1); assertEquals(compute(n - 1) + compute(n - 2), compute(n)); } } March 14, 2011 20
  30. Theories Example: Fibonacci Numbers import static de.andrena.junit.fibonacci.Fibonacci.compute; @RunWith(Theories.class) public class

    FibonacciTheories { @DataPoints public static int[] VALUES = { 0, 1, 2, 3, 4, 5, 6 }; @Theory public void seeds(int n) { // F(0) = 0, F(1) = 1 assumeTrue(n <= 1); assertEquals(n, compute(n)); } @Theory public void recurrence(int n) { // F(n) = F(n − 1) + F(n − 2) for n > 1 assumeTrue(n > 1); assertEquals(compute(n - 1) + compute(n - 2), compute(n)); } } March 14, 2011 20
  31. Theories Example: Fibonacci Numbers import static de.andrena.junit.fibonacci.Fibonacci.compute; @RunWith(Theories.class) public class

    FibonacciTheories { @DataPoints public static int[] VALUES = { 0, 1, 2, 3, 4, 5, 6 }; @Theory public void seeds(int n) { // F(0) = 0, F(1) = 1 assumeTrue(n <= 1); assertEquals(n, compute(n)); } @Theory public void recurrence(int n) { // F(n) = F(n − 1) + F(n − 2) for n > 1 assumeTrue(n > 1); assertEquals(compute(n - 1) + compute(n - 2), compute(n)); } } March 14, 2011 20
  32. Theories Example: Fibonacci Numbers import static de.andrena.junit.fibonacci.Fibonacci.compute; @RunWith(Theories.class) public class

    FibonacciTheories { @DataPoints public static int[] VALUES = { 0, 1, 2, 3, 4, 5, 6 }; @Theory public void seeds(int n) { // F(0) = 0, F(1) = 1 assumeTrue(n <= 1); assertEquals(n, compute(n)); } @Theory public void recurrence(int n) { // F(n) = F(n − 1) + F(n − 2) for n > 1 assumeTrue(n > 1); assertEquals(compute(n - 1) + compute(n - 2), compute(n)); } } March 14, 2011 20
  33. Theories Example: Fibonacci Numbers import static de.andrena.junit.fibonacci.Fibonacci.compute; @RunWith(Theories.class) public class

    FibonacciTheories { @DataPoints public static int[] VALUES = { 0, 1, 2, 3, 4, 5, 6 }; @Theory public void seeds(int n) { // F(0) = 0, F(1) = 1 assumeTrue(n <= 1); assertEquals(n, compute(n)); } @Theory public void recurrence(int n) { // F(n) = F(n − 1) + F(n − 2) for n > 1 assumeTrue(n > 1); assertEquals(compute(n - 1) + compute(n - 2), compute(n)); } } March 14, 2011 20
  34. Theories Example: Fibonacci Numbers import static de.andrena.junit.fibonacci.Fibonacci.compute; @RunWith(Theories.class) public class

    FibonacciTheories { @DataPoints public static int[] VALUES = { 0, 1, 2, 3, 4, 5, 6 }; @Theory public void seeds(int n) { // F(0) = 0, F(1) = 1 assumeTrue(n <= 1); assertEquals(n, compute(n)); } @Theory public void recurrence(int n) { // F(n) = F(n − 1) + F(n − 2) for n > 1 assumeTrue(n > 1); assertEquals(compute(n - 1) + compute(n - 2), compute(n)); } } March 14, 2011 20
  35. Theories Example: Fibonacci Numbers import static de.andrena.junit.fibonacci.Fibonacci.compute; @RunWith(Theories.class) public class

    FibonacciTheories { @DataPoints public static int[] VALUES = { 0, 1, 2, 3, 4, 5, 6 }; @Theory public void seeds(int n) { // F(0) = 0, F(1) = 1 assumeTrue(n <= 1); assertEquals(n, compute(n)); } @Theory public void recurrence(int n) { // F(n) = F(n − 1) + F(n − 2) for n > 1 assumeTrue(n > 1); assertEquals(compute(n - 1) + compute(n - 2), compute(n)); } } March 14, 2011 20
  36. Theories Example: @DataPoints of different types @RunWith(Theories.class) public class DifferentDataPoints

    { @DataPoint public static String A = "a"; @DataPoint public static String B = "b"; @DataPoints public static int[] NUMBERS = { 1, 2, 3 }; @Theory public void theory1(String string) { /* ("a"), ("b") */ } @Theory public void theory2(int number) { /* (1), (2), (3) */ } @Theory public void theory3(String string, int number) { /* ("a", 1), ("a", 2), ("a", 3), ("b", 1), ("b", 2), ("b", 3) */ } } March 14, 2011 21
  37. Theories Example: @DataPoints of different types @RunWith(Theories.class) public class DifferentDataPoints

    { @DataPoint public static String A = "a"; @DataPoint public static String B = "b"; @DataPoints public static int[] NUMBERS = { 1, 2, 3 }; @Theory public void theory1(String string) { /* ("a"), ("b") */ } @Theory public void theory2(int number) { /* (1), (2), (3) */ } @Theory public void theory3(String string, int number) { /* ("a", 1), ("a", 2), ("a", 3), ("b", 1), ("b", 2), ("b", 3) */ } } March 14, 2011 21
  38. Theories Example: @DataPoints of different types @RunWith(Theories.class) public class DifferentDataPoints

    { @DataPoint public static String A = "a"; @DataPoint public static String B = "b"; @DataPoints public static int[] NUMBERS = { 1, 2, 3 }; @Theory public void theory1(String string) { /* ("a"), ("b") */ } @Theory public void theory2(int number) { /* (1), (2), (3) */ } @Theory public void theory3(String string, int number) { /* ("a", 1), ("a", 2), ("a", 3), ("b", 1), ("b", 2), ("b", 3) */ } } March 14, 2011 21
  39. Theories Example: @DataPoints of different types @RunWith(Theories.class) public class DifferentDataPoints

    { @DataPoint public static String A = "a"; @DataPoint public static String B = "b"; @DataPoints public static int[] NUMBERS = { 1, 2, 3 }; @Theory public void theory1(String string) { /* ("a"), ("b") */ } @Theory public void theory2(int number) { /* (1), (2), (3) */ } @Theory public void theory3(String string, int number) { /* ("a", 1), ("a", 2), ("a", 3), ("b", 1), ("b", 2), ("b", 3) */ } } March 14, 2011 21
  40. Theories Example: @DataPoints of different types @RunWith(Theories.class) public class DifferentDataPoints

    { @DataPoint public static String A = "a"; @DataPoint public static String B = "b"; @DataPoints public static int[] NUMBERS = { 1, 2, 3 }; @Theory public void theory1(String string) { /* ("a"), ("b") */ } @Theory public void theory2(int number) { /* (1), (2), (3) */ } @Theory public void theory3(String string, int number) { /* ("a", 1), ("a", 2), ("a", 3), ("b", 1), ("b", 2), ("b", 3) */ } } March 14, 2011 21
  41. Parameterized Tests vs. Theories @DataPoint definition is more flexible Constants

    or methods Any number of datapoints Different types Test methods are parameterized No boilerplate code (Constructor + instance variables) Different types of parameters possible Theories can do everything Parameterized tests can, but more! March 14, 2011 22
  42. Traditional Tests vs. Theories Traditional tests use examples: Validation of

    behaviour under selected inputs Examples ought to be characteristic (hopefully) A theory generalizes a set of tests: precondition is made explicit assertions apply for all inputs that satisfy preconditions March 14, 2011 23
  43. What is a Rule? Extension mechanism for execution of test

    methods Examples: Execute code before/after every test method Handle failed tests Check additonal criteria after every test . . . March 14, 2011 25
  44. What is a Rule? Extension mechanism for execution of test

    methods Examples: Execute code before/after every test method Handle failed tests Check additonal criteria after every test . . . March 14, 2011 25
  45. Example: TemporaryFolder Without Rules public class TemporaryFolderWithoutRule { private File

    folder; @Before public void createTemporaryFolder() throws Exception { folder = File.createTempFile("myFolder", ""); folder.delete(); folder.mkdir(); } @Test public void test() throws Exception { File file = new File(folder, "test.txt"); file.createNewFile(); assertTrue(file.exists()); } @After public void deleteTemporaryFolder() { recursivelyDelete(folder); // does not fit on current slide... } March 14, 2011 26
  46. Example: TemporaryFolder Without Rules public class TemporaryFolderWithoutRule { private File

    folder; @Before public void createTemporaryFolder() throws Exception { folder = File.createTempFile("myFolder", ""); folder.delete(); folder.mkdir(); } @Test public void test() throws Exception { File file = new File(folder, "test.txt"); file.createNewFile(); assertTrue(file.exists()); } @After public void deleteTemporaryFolder() { recursivelyDelete(folder); // does not fit on current slide... } March 14, 2011 26
  47. Example: TemporaryFolder With Rules public class TemporaryFolderWithRule { @Rule public

    TemporaryFolder folder = new TemporaryFolder(); @Test public void test() throws Exception { File file = folder.newFile("test.txt"); assertTrue(file.exists()); } } March 14, 2011 27
  48. Example: TemporaryFolder With Rules public class TemporaryFolderWithRule { @Rule public

    TemporaryFolder folder = new TemporaryFolder(); @Test public void test() throws Exception { File file = folder.newFile("test.txt"); assertTrue(file.exists()); } } March 14, 2011 27
  49. What is a Rule? Extension mechanism for execution of test

    methods Examples: Execute code before/after every test method Handle failed tests Check additonal criteria after every test . . . March 14, 2011 28
  50. Example: ExpectedException Without Rules public class ExpectedExceptionWithoutRule { int[] threeNumbers

    = { 1, 2, 3 }; @Test(expected = ArrayIndexOutOfBoundsException.class) public void exception() { threeNumbers[3] = 4; } @Test public void exceptionWithMessage() { try { threeNumbers[3] = 4; fail("ArrayIndexOutOfBoundsException expected"); } catch (ArrayIndexOutOfBoundsException expected) { assertEquals("3", expected.getMessage()); } } } March 14, 2011 29
  51. Example: ExpectedException Without Rules public class ExpectedExceptionWithoutRule { int[] threeNumbers

    = { 1, 2, 3 }; @Test(expected = ArrayIndexOutOfBoundsException.class) public void exception() { threeNumbers[3] = 4; } @Test public void exceptionWithMessage() { try { threeNumbers[3] = 4; fail("ArrayIndexOutOfBoundsException expected"); } catch (ArrayIndexOutOfBoundsException expected) { assertEquals("3", expected.getMessage()); } } } March 14, 2011 29
  52. Example: ExpectedException With Rules public class ExpectedExceptionWithRule { int[] threeNumbers

    = { 1, 2, 3 }; @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void exception() { thrown.expect(ArrayIndexOutOfBoundsException.class); threeNumbers[3] = 4; } @Test public void exceptionWithMessage() { thrown.expect(ArrayIndexOutOfBoundsException.class); thrown.expectMessage("3"); threeNumbers[3] = 4; } } March 14, 2011 30
  53. Example: ExpectedException With Rules public class ExpectedExceptionWithRule { int[] threeNumbers

    = { 1, 2, 3 }; @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void exception() { thrown.expect(ArrayIndexOutOfBoundsException.class); threeNumbers[3] = 4; } @Test public void exceptionWithMessage() { thrown.expect(ArrayIndexOutOfBoundsException.class); thrown.expectMessage("3"); threeNumbers[3] = 4; } } March 14, 2011 30
  54. Other Predefined Rules ErrorCollector Collects failing assertions and causes the

    test to fail with an aggregated list of failures at the end. TestName Provides access to the name of the currently executing test method. Timeout Applies the same timeout to all test methods in the test class. March 14, 2011 31
  55. Custom Rule Implementation public class SystemProperty extends ExternalResource { private

    final String key, value; private String oldValue; public SystemProperty(String key, String value) { this.key = key; this.value = value; } @Override protected void before() { oldValue = System.getProperty(key); System.setProperty(key, value); } @Override protected void after() { if (oldValue == null) { System.getProperties().remove(key); } else { System.setProperty(key, oldValue); } } } March 14, 2011 33
  56. Custom Rule Implementation public class SystemProperty extends ExternalResource { private

    final String key, value; private String oldValue; public SystemProperty(String key, String value) { this.key = key; this.value = value; } @Override protected void before() { oldValue = System.getProperty(key); System.setProperty(key, value); } @Override protected void after() { if (oldValue == null) { System.getProperties().remove(key); } else { System.setProperty(key, oldValue); } } } March 14, 2011 33
  57. Custom Rule Usage public class SomeTestUsingSystemProperty { private static final

    String VALUE = "someValue"; private static final String KEY = "someKey"; @Rule public SystemProperty systemProperty = new SystemProperty(KEY, VALUE); @Test public void test() { assertThat(System.getProperty(KEY), is(VALUE)); } } March 14, 2011 34
  58. Custom Rule Usage public class SomeTestUsingSystemProperty { private static final

    String VALUE = "someValue"; private static final String KEY = "someKey"; @Rule public SystemProperty systemProperty = new SystemProperty(KEY, VALUE); @Test public void test() { assertThat(System.getProperty(KEY), is(VALUE)); } } March 14, 2011 34
  59. Advantages of Rules Reusable Allow to encapsulate frequently used code.

    Composable You can use any number of rules in one test class. Delegation over inheritance Help to avoid test class hierarchies. Extendable Writing your own rules is easy. March 14, 2011 35
  60. Dividing Tests into Categories Category is class or interface: public

    interface Slow {} Only for one test: @Category(Slow.class) @Test public void test() {} All tests in a test class: @Category(Slow.class) public class B { @Test public void c() {} } March 14, 2011 37
  61. Executing All Tests in Certain Categories Conventional test suite: @RunWith(Suite.class)

    @SuiteClasses( { A.class, B.class }) public class AllTests {} All tests in category Slow: @RunWith(Categories.class) @IncludeCategory(Slow.class) public class AllSlowTests extends AllTests {} All other tests: @RunWith(Categories.class) @ExcludeCategory(Slow.class) public class AllFastTests extends AllTests {} March 14, 2011 38
  62. What’s next? Updating to more recent JUnit version is easy

    Old tests will still run New tests will benefit from new features Old tests can be simplified little by little Try it out! March 14, 2011 40
  63. What’s next? Updating to more recent JUnit version is easy

    Old tests will still run New tests will benefit from new features Old tests can be simplified little by little Try it out! March 14, 2011 40
  64. Try it out! http://www.junit.org/ https://github.com/marcphilipp/junit-4x-presentation In theory, there is no

    difference between theory and practice. But, in practice, there is. Jan L. A. van de Snepscheut/Yogi Berra Thank you! Mail [email protected] Twitter @marcphilipp Blog http://marcphilipp.tumblr.com/ March 14, 2011 41