ChatGPT解决这个技术问题 Extra ChatGPT

JUnit confusion: use 'extends TestCase' or '@Test'?

I've found the proper use (or at least the documentation) of JUnit very confusing. This question serves both as a future reference and as a real question.

If I've understood correctly, there are two main approaches to create and run a JUnit test:

Approach A (JUnit 3-style): create a class that extends TestCase, and start test methods with the word test. When running the class as a JUnit Test (in Eclipse), all methods starting with the word test are automatically run.

import junit.framework.TestCase;

public class DummyTestA extends TestCase {

    public void testSum() {
        int a = 5;
        int b = 10;
        int result = a + b;
        assertEquals(15, result);
    }
}

Approach B (JUnit 4-style): create a 'normal' class and prepend a @Test annotation to the method. Note that you do NOT have to start the method with the word test.

import org.junit.*;
import static org.junit.Assert.*;

public class DummyTestB {

    @Test
    public void Sum() {
        int a = 5;
        int b = 10;
        int result = a + b;
        assertEquals(15, result);
    }
}

Mixing the two seems not to be a good idea, see e.g. this stackoverflow question:

Now, my questions(s):

What is the preferred approach, or when would you use one instead of the other? Approach B allows for testing for exceptions by extending the @Test annotation like in @Test(expected = ArithmeticException.class). But how do you test for exceptions when using approach A? When using approach A, you can group a number of test classes in a test suite like this: TestSuite suite = new TestSuite("All tests"); suite.addTestSuite(DummyTestA.class); suite.addTestSuite(DummyTestAbis.class); But this can't be used with approach B (since each testclass should subclass TestCase). What is the proper way to group tests for approach B?

Edit: I've added the JUnit versions to both approaches

I've seen extends TestCase and then each test also annotated with @Test just to confuse things. :)

M
Marcono1234

The distinction is rather easy:

extending TestCase is the way unit tests were written in JUnit 3 (of course it's still supported in JUnit 4)

using the @Test annotation is the way introduced by JUnit 4

Generally you should choose the annotation path, unless compatibility with JUnit 3 (and/or a Java version earlier than Java 5) is needed. The new way has several advantages:

The @Test annotation is more explicit and is easier to support in tools (for example it's easy to search for all tests this way)

Multiple methods can be annotated with @Before/@BeforeClass and @After/@AfterClass providing more flexibility

Support for @Rule annotations on things like ExpectedException

Support for the @Ignored annotation

Support for alternative test runners using @RunWith

To test for expected exceptions in a JUnit 3 TestCase you'd have to make the text explicit.

public void testMyException() {
  try {
    objectUnderTest.myMethod(EVIL_ARGUMENT);
    fail("myMethod did not throw an Exception!");
  } catch (MyException e) {
    // ok!
    // check for properties of exception here, if desired
  }
}

JUnit 5 introduced yet another API change, but still uses annotations. The new @Test annotation is org.junit.jupiter.api.Test (the "old" JUnit 4 one was org.junit.Test), but it works pretty much the same as the JUnit 4 one.


Helpful and thorough answer, but I don't fully understand "check for message of exception". Checking against a hardcoded string is going to be a maintenance nightmare. You must have meant "check for the properties of your specific exception type".
@thSoft: it's not often that it's used, but occasionally I want to make sure that the exception method mentions the offending field, for example. Then a simple assertTrue(e.getMessage().contains("foo")) could be useful.
Even in JUnit4 this is an important idiom when you have to check the message or some other property of the exception (such as the cause). The expected method only checks for type.
@Yishai: that's true, but most of the time I'm already content if the method throws the correct type of Exception on problematic input.
For that reason, JUnit 5 made a breaking change in exception testing. assertThrows() is fantastic :-)
G
Grimmy

I have a preference for JUnit 4 (Annotation approach) because I find it more flexible.

If you want to build test suite in JUnit 4, you have to create a class grouping all tests like this:

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;


@RunWith(Suite.class)
@SuiteClasses({
    Test1.class,
    Test2.class,
    Test3.class,
    Test4.class
})public class TestSuite
{
 /* empty class */
}

Y
Yishai

There is an unanswered part to your question, and that is "What is the proper way to group tests for approach B?"

The official answer is that you annotate a class with an @RunWith(Suite.class) and then use the @Suite.SuiteClasses annotation to list the classes. This is how the JUnit developers do it (listing every class in a suite manually). In many ways this approach is an improvement, in that it is trivial and intuitive to add before suite and after suite behaviors (just add an @BeforeClass and @AfterClass method to the the class annotated with the @RunWith - much better than the old TestFixture).

However, it does have a step backwards, in that annotations don't allow you to dynamically create the list of classes, and working around that problem gets a bit ugly. You have to subclass the Suite class and dynamically create the array of classes in the subclass and pass it to the Suite constructor, but this is an incomplete solution in that other subclasses of Suite (such as Categories) don't work with it and essentially do not support dynamic Test class collection.


+1 for this. After embarking on a task to write a dynamic solution to adding Tests to a TestSuite, I've had to extend TestCase in each of my Tests. This in turn has broken previously working unit Tests which used JUnit4 annotations to define expected exceptions. My search for a way to dynamically populate a Test Suite has led me to this thread, and specifically your answer, which I believe is one of the few remaining desirable reasons to continue with JUnit 3.
h
husayt

You should use JUnit 4. It's better.

Much frameworks have started to deprecate the JUnit 3.8 support.

This is from the Spring 3.0 reference documentation:

[Warning] Legacy JUnit 3.8 class hierarchy is deprecated

In general, you should always try to use the latest stable release of a framework when you start something new.


b
black666

The "preferred" approach would be to use annotations which have been introduced since Junit 4. They make a lot of things easier (see your second question) You can use a simple try/catch block for that:


public void testForException() {
    try {
        Integer.parseInt("just a string");
        fail("Exception should have been thrown");
    } catch (final Exception e) {
        // expected
    }
}