Junit-In-Action

Part 1. Junit

1. JUnit jump-start

1.1 Proving that a program works

Framework Definition

Unit Test Difinition

API Contract Definition

The notion of an API contract arises from the practice of Design by Contract,popularized by the Eiffel programming language (http://archive.eiffel.com/doc/manuals/technology/contract).

1.2.1 Understanding unit testing frameworks

Unit testing has several best practices that frameworks should follow. Three rules that (in my experience) all unit testing frameworks should follow:

  1. Each unit test should run independently of all other unit tests.
  2. The framework should detect and report errors test by test.
  3. It should be easy to define which unit tests will run.

1.4 Testing with JUnit

JUnit annotations to provide resource initialization and cleanup methods:
@BeforeEach, @BeforeAll, @AfterEach, and @AfterAll (starting from version 5);
and @Before, @BeforeClass, @After, and @AfterClass (up to version 4)

Summary

This chapter has covered the following:
 Why every developer should perform some type of test to see if code actually
works. Developers who use automatic unit tests can repeat these tests on
demand to ensure that new code works and does not break existing tests.
 Writing simple unit tests, which are not difficult to create without JUnit.
 As tests are added and become more complex, writing and maintaining tests
becomes more difficult.
 Introduction to JUnit as a unit testing framework that makes it easier to create,
run, and revise unit tests.
 Stepping through a simple JUnit test.

2. Exploring core JUnit

2.1 Core annotations

These are the most important concepts:

  • A test class may be a top-level class, a static member class, or an inner class annotated as @Nested that contains one or more test methods.
  • Test classes cannot be abstract and must have a single constructor.
  • The constructor must have no arguments, or arguments that can be dynamically resolved at runtime through
  • dependency injection.
  • A test class is allowed to be package-private as a minimum requirement for visibility. It is no longer required that test classes be public, as was the case up to JUnit 4.x.
  • A test method is an instance method that is annotated with @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, or @TestTemplate.
  • A life cycle method is a method that is annotated with @BeforeAll, @AfterAll,@BeforeEach, or @AfterEach.

2.1.1 The @DisplayName annotation

2.1.2 The @Disabled annotation

It signals that the annotated test class or test method is disabled and should not be executed.

2.2 Nested tests

The typical use case is when two classes are tightly coupled, and it’s logical to provide direct
access from the inner class to all instance variables of the outer class.

2.3 Tagged tests

If you are familiar with JUnit 4, tagged tests are replacements for JUnit 4 categories.

2.4 Assertions

To perform test validation, you use the assert methods provided by the JUnit
Assertions class.

After the heading parameter from the assertAll method, we provide the rest of
the arguments as a collection of executables—a shorter, more convenient way to assert
that supplied executables do not throw exceptions.

The assertAll method will always check all the assertions that are provided to it, even
if some of them fail—if any of the executables fail, the remaining ones will still be run.

The advantage of using lambda expressions as arguments for assertion methods is that
all of them are lazily created, resulting in improved performance.

2.5 Assumptions

Sometimes tests fail due to an external environment configuration or a date or time
zone issue that we cannot control. We can prevent our tests from being executed
under inappropriate conditions.

2.6 Dependency injection in JUnit 5

JUnit 5 allows test constructors and methods to have parameters, but they
need to be resolved through dependency injection.

2.6.1 TestInfoParameterResolver

2.6.2 TestReporterParameterResolver

2.6.3 RepetitionInfoParameterResolver

If a parameter in a method annotated with @RepeatedTest, @BeforeEach, or
@AfterEach is of type RepetitionInfo, RepetitionInfoParameterResolver
supplies an instance of this type. Then RepetitionInfo gets information about the
current repetition and the total number of repetitions for a test annotated with
@RepeatedTest.

2.7 Repeated test

2.8 Parameterized tests

Parameterized tests allow a test to run multiple times with different arguments. The
great benefit is that we can write a single test to be performed using arguments that
check various input data.

@ValueSource lets us specify a single array of literal values.

@EnumSource enables us to use enum instances.

@CsvSource to express argument lists as comma-separated values (CSV)

@CsvFileSource allows us to use CSV files from the classpath.

2.9 Dynamic tests

JUnit 5 introduces a dynamic new programming model that can generate tests at runtime. We write a factory method, and at runtime, it creates a series of tests to be executed. Such a factory method must be annotated with @TestFactory.

A dynamic test has a different life cycle than a standard test annotated with @Test.
The methods annotated with @BeforeEach and @AfterEach are executed for the
@TestFactory method but not for each dynamic test; other than these methods,
there are no life cycle callbacks for individual dynamic tests. The behavior of
@BeforeAll and @AfterAll remains the same; they are executed before all tests
and at the end of all tests.

2.10 Using Hamcrest matchers

As we write more unit tests and assertions, we inevitably find that some assertions
are big and hard to read.The goal is to simplify the assertion made in the test method.

此图像的alt属性为空;文件名为image-14-1024x525.png

The Hamcrest library
Hamcrest is not a testing framework itself, but it helps us declaratively specify simple
matching rules. These matching rules can be used in many situations, but they are
particularly helpful for unit testing.

Summary

  • The core JUnit 5 classes related to assertions and assumptions.
  • Using JUnit 5 methods and annotations: the methods from the assertions and assumptions classes, and annotations like @Test, @DisplayName, and @Disabled
  • The life cycle of a JUnit 5 test, and controlling it through the @BeforeEach, @AfterEach, @BeforeAll, and @AfterAll annotations
  • Applying JUnit 5 capabilities to create nested tests and tagged tests (annotation:@Tag)
  • Implementing dependency injection with the help of test constructors and methods with parameters
  • Applying dependency injection by using different parameter resolvers (TestInfoParameterResolver, TestReporterParameterResolver)
  • Implementing repeated tests (annotation: @RepeatedTest) as another application of dependency injection
  • Parameterized tests, a very flexible tool for testing that consumes different data sets and dynamic tests created at runtime (annotations: @ParameterizedTest, @TestFactory)
  • Using Hamcrest matchers to simplify assertions