Diary of JUnit Learning
Author:Roser Han
Location:NUAA
文章目录
Before Reading
You can get the source code from my github repository:
https://github.com/LitMonkey/Java-JUnit-Study-Diary
And welcome to visit my blog:
http://www.litmonkey.cn:81/
Writing Tests
Test Classes and Methods
Test Class: any top-level class, static
member class, or @Nested
class that contains at least one test method.
Test classes must not be abstract and must have a single constructor.
Test Method: any instance
method that is directly annotated or meta-annotated with @Test
, @RepeatedTest
, @ParameterizedTest
, @TestFactory
, or @TestTemplate
.
Lifecycle Method: any method that is directly annotated or meta-annotated with @BeforeAll
, @AfterAll
, @BeforeEach
, or @AfterEach
.
Test methods and life cycle methods may be declared locally within the current class, inherited from superclasses, or inherited from interface.
In addition, test methods and lifecycle methods must not be abstract and must not return a value.
Here is a example of the usage of Test Method
and Lifecycle Method
:
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
public class StandardTests {
@BeforeAll
static void initAll(){
}
@BeforeEach
void init(){
}
@Test
void succeedingTest(){
}
@Test
void failingTest(){
fail("a failing test");
}
@Test
@Disabled("for demonstration purposes")
void skippedTest(){
}
@Test
void abortedTest(){
assumeTrue("abc".contains("Z"));
fail("test should have been aborted");
}
@AfterEach
void tearDown(){
}
@AfterAll
static void tearDownAll(){
}
}
Display Names
Test classes and test methods can declare custom display names via @DisplayName
----with spaces, specila characters, and even emojis----that will displayed in test reports and by test runner and IDEs.
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@DisplayName("A special test case")
public class DisplayNameDemo {
@Test
@DisplayName("Custom test name containint spaces")
void testWithDisplayNameContainingSpaces(){
}
@Test
@DisplayName("ヾ(≧▽≦*)o")
void testWithDisplayNameContainingSpecialCharacters(){
}
@Test
@DisplayName("😂")
void testWithDisplayNameContainingEmoji(){
}
}
The name will display as the following image:
The display name for a test class or method is determined according to the following precedence rules:
- value of the
@DisplayName
annotation, if present - by calling the
DisplayNameGenerator
specified in the@DisplayNameGeneration
annotation, if present - by calling the default
DisplayNameGenrator
configured via the configuration parameter, if present - by calling
org.junit.jupiter.api.DisplayNameGenerator.Standard
Assertions
All JUnit Jupiter assertions are static
methods in the org.junit.jupiter.api.Assertions
class.
Let’s see the details of assertions.
Frequently-used Method Summary
Assertion | Description |
---|---|
fail(String message) | Fail the test with the given failure message |
assertTrue(boolean condition) | Assert that the supplied condition is true |
assertFalse(boolean condition) | Assert that the supplied condition is false |
assertNull(Object actual, [String message]) | Assert that actual is null Fails with the supplied failure message |
assertNotNull(Object actual, [String message]) | Assert that actual is not null Fails with the supplied failure message |
assertEquals(expected, actual) | This assertion is the most frequently-used one. Assert that expected and actual are equal |
assertArrayEquals(TypeName[] expeted, TypeName[] actual) | Assert that expected and actual array are equal(content) |
assertNotEquals(expect, actual) | Assert that expected and actual are not equal |
assertSame(Object expected, Object actual) | Assert that expected and actual refer to the same object |
assertNotSame(Object expected, Object actual) | Assert that expected and actual refer to the different object |
assertAll(excutables) throws MutipleFailuresError | Assert that all supplied executables do not throw exceptions |
assertThrows(exceptedType, excutable) | Assert that excution of the supplied excutable throws an exception of the expectedTyle and return the exception |
Third-party Assertion Libraries
Developers are free to use the assertion library of their choice.
For example, the combination of matchers and a fluent API can be used to make assertions more descriptive and readable.
But before using third-party assertion libraries, let’s learn more about the protogenetic JUnit at first.
Assumptions
Assumptions is a collection of utility methods that support conditional test execution based on assumptions.
Assumptions are typically used whenever it does not make sense to continue execution of a given test method — for example, if the test depends on something that does not exist in the current runtime environment.
Assumption | Description |
---|---|
assumeTrue(assumption) | Validate the given assumption |
assumeFalse(assumption) | Validate the given assumption |
assumingThat(assumption, executable) | Excute the supplied Excutable, but only if the supplied assumption is valid. if the assumption is invalid, this method does nothing |
Disabling Tests
@Disabled
is used to signal that the annotated test class or test method is currently disabled and should not be executed.
Disabled
may optionally be declared with a reason to document why the annotated test class ro test method is disabled.
When applied at the class level, all test methods within that class are automatically disabled as well.
Wen applied at the method level, the presence of this annotation does not prevent the test class from being instantiated. Rather, it prevents the execution of the test method and method-level lifecycle callbacks such as @BeforeEach
methods, @AfterEach
methods, and corresponding extension APIs.
Conditional Test Execution
Script-based Conditions
Here we talk about only one type of Conditional Test Execution
----Script-based Conditions.
JUnit Jupiter provides the ability to either enable or disable a container or test depending on the evaluation of a script configured via the @EnabledIf
or @DisabledIf
annotation.
@Test // Static JavaScript expression.
@EnabledIf("2 * 3 == 6")
void willBeExecuted() {
// ...
}
@RepeatedTest(10) // Dynamic JavaScript expression.
@DisabledIf("Math.random() < 0.314159")
void mightNotBeExecuted() {
// ...
}
@Test // Regular expression testing bound system property.
@DisabledIf("/32/.test(systemProperty.get('os.arch'))")
void disabledOn32BitArchitectures() {
assertFalse(System.getProperty("os.arch").contains("32"));
}
@Test
@EnabledIf("'CI' == systemEnvironment.get('ENV')")
void onlyOnCiServer() {
assertTrue("CI".equals(System.getenv("ENV")));
}
@Test // Multi-line script, custom engine name and custom reason.
@EnabledIf(value = {
"load('nashorn:mozilla_compat.js')",
"importPackage(java.time)",
"",
"var today = LocalDate.now()",
"var tomorrow = today.plusDays(1)",
"tomorrow.isAfter(today)"
},
engine = "nashorn",
reason = "Self-fulfilling: {result}")
void theDayAfterTomorrow() {
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plusDays(1);
assertTrue(tomorrow.isAfter(today));
}
Tagging
Test classes and methods can be tagged via the @Tag
annotation.
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@Tag("fast")
@Tag("model")
class TaggingDemo {
@Test
@Tag("taxes")
void testingTaxCalculation() {
}
}
Parameterized Tests
Parameterized tests make it possible to run a test multiple times with different arguments. They are declared just like regular @Test
methods but use the @ParaterizedTest
annotation instead. In addition, you must declare at least one source that will provide the arguments for each invocation and then consume the arguments in the test method.
The following example demonstrates a parameterized test that uses the @ValueSource
annotation to specify a String
array as the source of arguments.
@ParameterizedTest
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
void palindromes(String candidate) {
assertTrue(StringUtils.isPalindrome(candidate));
}
To execute the test, there are some changes of the code above:
Sources of Arguments
JUnit Jupiter provides quite a few source annotations.
@ValueSource
@ValueSource
is one of the simplest possible sources. It lets you specify a single array of literal values and can only be used for providing a single argument per parameterized test invocation.
The following types of literal values are supported by @ValueSource
.
short
byte
int
long
float
double
char
boolean
java.lang.String
java.lang.Class
Null and Empty Sources
In order to check corner cases and verify proper behavior of our software when it is supplied bad input, it can be useful to have null
and empty values supplied to our parameterized tests. The following annotations serve as sources of null
and empty values for parameterized tests that accept a single argument.
@NullSource
: provides a singlenull
argument to the annotated@ParameterizedTest
method.@NullSource
cannot be used for a parameter that has a primitive type.
@EmptySource
: provides a single empty argument to the annotated@ParameterizedTest
method for parameters of the following types:java.lang.String
,java.util.List
,java.util.Set
,java.util.Map
, primitive arrays (e.g.,int[]
,char[][]
, etc.), object arrays (e.g.,String[]
,Integer[][]
, etc.).- Subtypes of the supported types are not supported.
@NullAndEmptySource
: a composed annotation that combines the functionality of@NullSource
and@EmptySource
.
You can also combine @NullSource
, @EmptySource
, and @ValueSource
to test a wider range of null
, empty, and blank input. The following example demonstrates how to achieve this for strings.
@ParameterizedTest
@NullSource
@EmptySource
@ValueSource(strings = { " ", " ", "\t", "\n" })
void nullEmptyAndBlankStrings(String text) {
assertTrue(text == null || text.trim().isEmpty());
}
Making use of the composed @NullAndEmptySource
annotation simplifies the above as follows.
@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = { " ", " ", "\t", "\n" })
void nullEmptyAndBlankStrings(String text) {
assertTrue(text == null || text.trim().isEmpty());
}
@CsvSource
@CsvSource
allows you to express argument lists as comma-separated values (i.e., String
literals).
@ParameterizedTest
@CsvSource({
"apple, 1",
"banana, 2",
"'lemon, lime', 0xF1"
})
void testWithCsvSource(String fruit, int rank) {
assertNotNull(fruit);
assertNotEquals(0, rank);
}
@CsvSource
uses a single quote '
as its quote character. See the 'lemon, lime'
value in the example above and in the table below. An empty, quoted value ''
results in an empty String
unless the emptyValue
attribute is set; whereas, an entirely empty value is interpreted as a null
reference.
An ArgumentConversionException
is raised if the target type of a null
reference is a primitive type.
Example Input | Resulting Argument List |
---|---|
@CsvSource({ "apple, banana" }) | "apple" , "banana" |
@CsvSource({ "apple, 'lemon, lime'" }) | "apple" , "lemon, lime" |
@CsvSource({ "apple, ''" }) | "apple" , "" |
@CsvSource({ "apple, " }) | "apple" , null |
@CsvFileSource
@CsvFileSource
lets you use CSV files from the classpath. Each line from a CSV file results in one invocation of the parameterized test.
@ParameterizedTest
@CsvFileSource(resources = "/two-column.csv", numLinesToSkip = 1)
void testWithCsvFileSource(String country, int reference) {
assertNotNull(country);
assertNotEquals(0, reference);
}
Summery
JUnit Jupiter is more simple than JUnit 4, and the syntax is more concise.
Anyway, the study of JUnit Jupiter is not easy because most references on the Internet or in library are created for JUnit 4. This essay is base on the document of JUnit 5, so there are many similarities between this essay and the document.
At last, this article is writing for myself.