1. An overview of the

1.1. What is JUnit 5?

  • Unlike previous JUnit versions, JUnit 5 consists of several different modules from three different subprojects.

    JUnit 5 = JUnit platform+ JUnit Jupiter + JUnit Vintage

  • The JUnit platform can be used as a base launch testing framework on the JVM. It also defines the APIS that TestEngine uses to develop test frameworks that run on the platform. In addition, the platform provides a console launcher for starting the platform from the command line and a JUnit 4-based runner, TestEngine, for running any platform on a platform in a JUnit 4-based environment

  • JUnit Jupiter is a combination of the new programming model and extension model for writing tests and extensions in JUnit 5. The Jupiter subproject provides a TestEngine capability to run Jupite-based tests on the platform.

  • JUnit Vintage provides a TestEngine capability to run JUnit 3 – and JUnit 4-based tests on the platform.

1.4.2. JUnit 5 function

  • Write tests in JUnit Jupiter

  • Migrate from JUnit 4 to JUnit Jupiter

  • Run the test

  • JUnit Jupiter’s extended model

2. Write test cases

2.1 First test case

  • The following example provides a brief introduction to writing tests in JUnit Jupiter

    import static org.junit.jupiter.api.Assertions.assertEquals;
    
    import example.util.Calculator;
    
    import org.junit.jupiter.api.Test;
    
    class MyFirstJUnitJupiterTests {
    
        private final Calculator calculator = new Calculator();
    
        @Test
        void addition(a) {
            assertEquals(2, calculator.add(1.1)); }}Copy the code

2.2 annotations

JUnit Jupiter supports the following annotations for configuring the testing and extension framework.

annotations describe
@Test The presentation method is the test method. With JUnit 4@TestUnlike annotations, this one does not declare any attributes, because the test extension in JUnit Jupiter operates on its own proprietary annotations. unlessrewriteThese methods would otherwise beBe * * to inherit.
@ParameterizedTest The way to express it isParametric test. unlessrewriteThese methods would otherwise beBe * * to inherit.
@RepeatedTest The way to express it isRepeat the testTest template. unlessrewriteThese methods would otherwise beBe * * to inherit.
@TestFactory The presentation method is used forDynamic testingTest factory. unlessrewriteThese methods would otherwise beBe * * to inherit.
@TestTemplate The way to express it isThe test casetheTemplates, test casesDesigned to be based on registeredThe providerReturns the number of call contexts to call multiple times. unlessrewriteThese methods would otherwise beBe * * to inherit.
@TestMethodOrder Used to configure for annotated test classesTest the order in which methods are executed; Similar to JUnit 4@FixMethodOrder. So the comment isinherited.
@TestInstance Used to configure for annotated test classesTest the instance life cycle. So the comment isinherited.
@DisplayName Declare customizations of test classes or test methodsThe display name. Such comments are notinherited.
@DisplayNameGeneration Declare customizations of test classesDisplay name generator. So the comment isinherited.
@BeforeEach The method representing the annotation should be executedbefore Each of the @Test.@RepeatedTest.@ParameterizedTestOr,@TestFactoryMethod in the current class; Similar to JUnit 4@Before. unlessrewriteThese methods would otherwise beBe * * to inherit.
@AfterEach The method representing the annotation should be executedafter each @Test.@RepeatedTest.@ParameterizedTestOr,@TestFactoryMethod in the current class; Similar to JUnit 4@After. unlessrewriteThese methods would otherwise beBe * * to inherit.
@BeforeAll The method representing the annotation should be executedbefore all @Test.@RepeatedTest.@ParameterizedTest, and@TestFactoryMethod in the current class; Similar to JUnit 4@BeforeClass. Such methods areinherited(Unless they arehiddenorcover) and must beinheritance(unlessstaticUse “per class”Test the instance life cycle).
@AfterAll The method representing the annotation should be executedafter All of the @Test.@RepeatedTest.@ParameterizedTest, and@TestFactoryMethod in the current class; Similar to JUnit 4@AfterClass. Such methods areinherited(Unless they arehiddenorcover) and must beinheritance(unlessstaticUse “per class”Test the instance life cycle).
@Nested Indicates that an annotated class is non-staticNested test classes.@BeforeAlland@AfterAllThe method cannot be used directly@NestedTest classes unless “per level”Test instance lifecycleBe used. Such comments are notinherited.
@Tag Used to declare at the class or method levelThe label used for filtering tests; Similar to test groups in TestNG or categories in JUnit 4. Such annotations are at the class levelinheritance, not at the method levelinheritance.
@Disabled Used fordisableTest classes or test methods; Similar to JUnit 4@Ignore. Such comments are notinherited.
@Timeout Fail the test, test factory, test template, or lifecycle method if execution exceeds the given duration. So the comment isinherited.
@ExtendWith Used fordeclarativetoRegister extensions. So the comment isinherited.
@RegisterExtension Used to pass fields toprogrammingwayRegister extensions. Unless beCover,Otherwise these fields will beinheritance.
@TempDir Is provided through field injection or parameter injection in a lifecycle method or test methodThe temporary directory; Located in theorg.junit.jupiter.api.ioIn the packaging.

2.2.1 Meta-comments and combined comments

The JUnit Jupiter annotation can be used as a meta-annotation. This means that you can define your own composite annotations, which automatically inherit the semantics of their meta-annotations.

For example, instead of copying and pasting the entire @tag (“fast”) code base (see Tags and Filters), you can create custom composed comments named @fast below. @fast can then be used as an alternative to the product @tag (” Fast “).

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.junit.jupiter.api.Tag;

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
public @interface Fast {
}
Copy the code
2.2.2.1.1 Filter labels
  • The label cannot benullOr forempty.
  • atrimThe tag cannot contain Spaces.
  • atrimLabels must not contain ISO control characters.
  • atrimThe label must not contain any of the followingReserved characters.
    • .:The comma
    • (:Left parenthesis
    • ):Right parenthesis
    • &:The shop name
    • |:A vertical bar
    • !:Exclamation mark

2.2.2. Test categories and methods

Test class: Any top-level class, static component class, or @nested class that contains at least one test method.

The test class cannot be abstract and must have a single constructor.

Test method: that is, any instance method @test, @REPEATedTest, @parameterizedTest, @TestFactory, or @TestTemplate that is directly annotated or meta-annotated.

Lifecycle methods: that is, any method annotated directly or with meta-annotations @beforeall, @afterall, @beforeeach, or @aftereach.

Test methods and lifecycle methods can be declared locally in the current test class, inherited from a superclass or from an interface (see “Test Interfaces and Default Methods”). In addition, test methods and lifecycle methods must not be abstract and return values.

Are test classes, test methods, and lifecycle methods requiredpublicBut they mustDon’tisprivate.

The following Test classes demonstrate the use of the @test method and all supported lifecycle methods. For more information about runtime semantics, see wrapping behavior for Test execution order and callbacks.

The standardized tests are as follows

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;

class StandardTests {

    @BeforeAll
    static void initAll(a) {}@BeforeEach
    void init(a) {}@Test
    void succeedingTest(a) {}@Test
    void failingTest(a) {
        fail("a failing test");
    }

    @Test
    @Disabled("for demonstration purposes")
    void skippedTest(a) {
        // not executed
    }

    @Test
    void abortedTest(a) {
        assumeTrue("abc".contains("Z"));
        fail("test should have been aborted");
    }

    @AfterEach
    void tearDown(a) {}@AfterAll
    static void tearDownAll(a) {}}Copy the code

2.2.4. Display the name

  • Test classes and test methods can declare custom display names @DisplayName with Spaces, special characters, and even emojis, which will be displayed in the test report and displayed by the test runner and IDE.

    import org.junit.jupiter.api.DisplayName;
    import org.junit.jupiter.api.Test;
    
    @DisplayName("A special test case")
    class DisplayNameDemo {
    
        @Test
        @DisplayName("Custom test name containing spaces")
        void testWithDisplayNameContainingSpaces(a) {}@Test
        @ DisplayName (" ╯ ╯ ° / °) ")
        void testWithDisplayNameContainingSpecialCharacters(a) {}@Test
        @ DisplayName (" 😱 ")
        void testWithDisplayNameContainingEmoji(a) {}}Copy the code

2.3. Claims

  • JUnit Jupiter ships with many of the assertion methods that JUnit 4 has, and adds some that are well suited for use with Java 8 lambda. All JUnit Jupiter Assertions are static methods in the class org. JUnit. Jupiter. API. Assertions.

    import static java.time.Duration.ofMillis;
    import static java.time.Duration.ofMinutes;
    import static org.junit.jupiter.api.Assertions.assertAll;
    import static org.junit.jupiter.api.Assertions.assertEquals;
    import static org.junit.jupiter.api.Assertions.assertNotNull;
    import static org.junit.jupiter.api.Assertions.assertThrows;
    import static org.junit.jupiter.api.Assertions.assertTimeout;
    import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
    import static org.junit.jupiter.api.Assertions.assertTrue;
    
    import java.util.concurrent.CountDownLatch;
    
    import example.domain.Person;
    import example.util.Calculator;
    
    import org.junit.jupiter.api.Test;
    
    class AssertionsDemo {
    
        private final Calculator calculator = new Calculator();
    
        private final Person person = new Person("Jane"."Doe");
    
        @Test
        void standardAssertions(a) {
            assertEquals(2, calculator.add(1.1));
            assertEquals(4, calculator.multiply(2.2),
                    "The optional failure message is now the last parameter");
            assertTrue('a' < 'b', () - >"Assertion messages can be lazily evaluated -- "
                    + "to avoid constructing complex messages unnecessarily.");
        }
    
        @Test
        void groupedAssertions(a) {
            // In a grouped assertion all assertions are executed, and all
            // failures will be reported together.
            assertAll("person",
                () -> assertEquals("Jane", person.getFirstName()),
                () -> assertEquals("Doe", person.getLastName())
            );
        }
    
        @Test
        void dependentAssertions(a) {
            // Within a code block, if an assertion fails the
            // subsequent code in the same block will be skipped.
            assertAll("properties",
                () -> {
                    String firstName = person.getFirstName();
                    assertNotNull(firstName);
    
                    // Executed only if the previous assertion is valid.
                    assertAll("first name",
                        () -> assertTrue(firstName.startsWith("J")),
                        () -> assertTrue(firstName.endsWith("e"))
                    );
                },
                () -> {
                    // Grouped assertion, so processed independently
                    // of results of first name assertions.
                    String lastName = person.getLastName();
                    assertNotNull(lastName);
    
                    // Executed only if the previous assertion is valid.
                    assertAll("last name",
                        () -> assertTrue(lastName.startsWith("D")),
                        () -> assertTrue(lastName.endsWith("e"))); }); }@Test
        void exceptionTesting(a) {
            Exception exception = assertThrows(ArithmeticException.class, () ->
                calculator.divide(1.0));
            assertEquals("/ by zero", exception.getMessage());
        }
    
        @Test
        void timeoutNotExceeded(a) {
            // The following assertion succeeds.
            assertTimeout(ofMinutes(2), () - > {// Perform task that takes less than 2 minutes.
            });
        }
    
        @Test
        void timeoutNotExceededWithResult(a) {
            // The following assertion succeeds, and returns the supplied object.
            String actualResult = assertTimeout(ofMinutes(2), () - > {return "a result";
            });
            assertEquals("a result", actualResult);
        }
    
        @Test
        void timeoutNotExceededWithMethod(a) {
            // The following assertion invokes a method reference and returns an object.
            String actualGreeting = assertTimeout(ofMinutes(2), AssertionsDemo::greeting);
            assertEquals("Hello, World!", actualGreeting);
        }
    
        @Test
        void timeoutExceeded(a) {
            // The following assertion fails with an error message similar to:
            // execution exceeded timeout of 10 ms by 91 ms
            assertTimeout(ofMillis(10), () - > {// Simulate task that takes more than 10 ms.
                Thread.sleep(100);
            });
        }
    
        @Test
        void timeoutExceededWithPreemptiveTermination(a) {
            // The following assertion fails with an error message similar to:
            // execution timed out after 10 ms
            assertTimeoutPreemptively(ofMillis(10), () - > {// Simulate task that takes more than 10 ms.
                new CountDownLatch(1).await();
            });
        }
    
        private static String greeting(a) {
            return "Hello, World!"; }}Copy the code

2.4. Test execution sequence

By default, the test methods are sorted using deterministic but intentionally unobvious algorithms. This ensures that subsequent runs of the test suite execute the test methods in the same order, allowing for repeatable builds.

  • Although true unit tests should generally not depend on the order in which they are executed, it is sometimes necessary to enforce a particular test method execution order, for example, when writing integration tests or functional tests, test order is important, especially in combination with @testInstance (Lifecycle.PER_CLASS).

    • To control the order in which test methods are executed, annotate your test class or test interface,@TestMethodOrderAnd specify what is requiredMethodOrdererThe implementation. You can implement your own customizationMethodOrdererOr use the following built-inMethodOrdererOne implementation.
    • DisplayName: according to the display name of the test methodThe letterThe orderSort them (seeShows the name generation priority rule)
    • MethodName: press according to the name and formal parameter list of the test methodAlphanumeric orderSort them.
    • OrderAnnotation: tests methods based on values specified through annotationsdigitalThe sorting@Order.

The following example demonstrates how to ensure that the test methods are executed in the Order specified by the @order annotation.

import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;

@TestMethodOrder(OrderAnnotation.class)
class OrderedTestsDemo {

    @Test
    @Order(1)
    void nullValues(a) {
        // perform assertions against null values
    }

    @Test
    @Order(2)
    void emptyValues(a) {
        // perform assertions against empty values
    }

    @Test
    @Order(3)
    void validValues(a) {
        // perform assertions against valid values}}Copy the code

2.5. Nested tests

@nested tests give test writers more functionality to express relationships between sets of tests. This is an exhaustive example.

Nested test suites for testing the stack

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.EmptyStackException;
import java.util.Stack;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

@DisplayName("A stack")
class TestingAStackDemo {

    Stack<Object> stack;

    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew(a) {
        new Stack<>();
    }

    @Nested
    @DisplayName("when new")
    class WhenNew {

        @BeforeEach
        void createNewStack(a) {
            stack = new Stack<>();
        }

        @Test
        @DisplayName("is empty")
        void isEmpty(a) {
            assertTrue(stack.isEmpty());
        }

        @Test
        @DisplayName("throws EmptyStackException when popped")
        void throwsExceptionWhenPopped(a) {
            assertThrows(EmptyStackException.class, stack::pop);
        }

        @Test
        @DisplayName("throws EmptyStackException when peeked")
        void throwsExceptionWhenPeeked(a) {
            assertThrows(EmptyStackException.class, stack::peek);
        }

        @Nested
        @DisplayName("after pushing an element")
        class AfterPushing {

            String anElement = "an element";

            @BeforeEach
            void pushAnElement(a) {
                stack.push(anElement);
            }

            @Test
            @DisplayName("it is no longer empty")
            void isNotEmpty(a) {
                assertFalse(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when popped and is empty")
            void returnElementWhenPopped(a) {
                assertEquals(anElement, stack.pop());
                assertTrue(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when peeked but remains not empty")
            void returnElementWhenPeeked(a) { assertEquals(anElement, stack.peek()); assertFalse(stack.isEmpty()); }}}}Copy the code

2.6. Parameterized testing

Parameterized testing allows you to run tests multiple times with different parameters. They are declared the same as the regular @test method, but annotated with @parameterizedTest. In addition, you must declare at least one source that will provide parameters for each call and then use those parameters in the test method.

The following example demonstrates a parameterized test that uses the @Valuesource annotation to specify a String array as a parameter source.

@ParameterizedTest
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
void palindromes(String candidate) {
    assertTrue(StringUtils.isPalindrome(candidate));
}
Copy the code

2.7. Repeat the test

JUnit Jupiter can repeat a specified number of tests by annotating the method @REPEATedTest and specifying the total number of repeats required. Each repeated Test behaves like the regular @test method and supports exactly the same lifecycle callbacks and extensions.

The following example shows how to declare a test repeatedTest() named test, which is automatically repeated 10 times.

In addition to specifying the number of repeats, you can customize the display name @REPEATedTest for each repeat configuration through the name attribute of the annotation. In addition, the display name can be a combination of static text and dynamic placeholders. Currently the following placeholders are supported.

  • DisplayName: display@RepeatedTestMethod names
  • {currentRepetition}: Indicates the current repeat count
  • {totalRepetitions}: Total number of repetitions

The default display names given repetitions are generated according to the following pattern: “Repetition {currentRepetition} of {totalRepetitions}”. Thus, the single repeat display name of the previous repeatedTest() example was: repetition 1 of 10, repetition 2 of 10, etc. If you want @REPEATedTest to include the display name of the method in each repeat name, you can define your own custom schema or use the predefined REPEATedTest.long_display_NAME schema. “DisplayName :: repetition {currentRepetition} of {totalRepetitions}” ‘repeatedTest() :: repetition repetition {repetition repetition} of {totalRepetitions}” “repetiedtest () : repetition 1 of 10“repeatedTest() :: repetition 2 of 10

To retrieve information about the total number of current repetitions and repetitions of programming, a developer can choose to have instance RepetitionInfo injected with the @REPEATedTest, @beforeeach or @Aftereach methods.

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.logging.Logger;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
import org.junit.jupiter.api.TestInfo;

class RepeatedTestsDemo {

    private Logger logger = // ...

    @BeforeEach
    void beforeEach(TestInfo testInfo, RepetitionInfo repetitionInfo) {
        int currentRepetition = repetitionInfo.getCurrentRepetition();
        int totalRepetitions = repetitionInfo.getTotalRepetitions();
        String methodName = testInfo.getTestMethod().get().getName();
        logger.info(String.format("About to execute repetition %d of %d for %s".//
            currentRepetition, totalRepetitions, methodName));
    }

    @RepeatedTest(10)
    void repeatedTest(a) {
        // ...
    }

    @RepeatedTest(5)
    void repeatedTestWithRepetitionInfo(RepetitionInfo repetitionInfo) {
        assertEquals(5, repetitionInfo.getTotalRepetitions());
    }

    @RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}")
    @DisplayName("Repeat!" )
    void customDisplayName(TestInfo testInfo) {
        assertEquals("Repeat! 1/1", testInfo.getDisplayName());
    }

    @RepeatedTest(value = 1, name = RepeatedTest.LONG_DISPLAY_NAME)
    @DisplayName("Details..." )
    void customDisplayNameWithLongPattern(TestInfo testInfo) {
        assertEquals("Details... :: repetition 1 of 1", testInfo.getDisplayName());
    }

    @RepeatedTest(value = 5, name = "Wiederholung {currentRepetition} von {totalRepetitions}")
    void repeatedTestInGerman(a) {
        // ...}}Copy the code

3. Run the tests

IDEA 2017.3 or later is recommended because these new versions of IDEA will automatically download the following JARS based on the API versions used in the project: , Junit-platform-launcher, Junit-Jupit-Engine, and Junit-vintage engine.

Maven dependencies

<! -- Only needed to run tests in a version of IntelliJ IDEA that bundles older versions --> <dependency> < the groupId > org. Junit. Platform < / groupId > < artifactId > junit - platform - the launcher < / artifactId > < version > 1.7.0 < / version > <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId> junit-jupit-engine </artifactId> <version>5.7.0</version> <scope>test</scope> </dependency> <dependency> < the groupId > org. Junit. Vintage < / groupId > < artifactId > junit - vintage - engine < / artifactId > < version > 5.7.0 < / version > <scope>test</scope> </dependency>Copy the code

4. The dependency graph

5. Unit test the AIR principles

Good unit tests must follow the AIR principles: Automatic, Independent, Repeatable.

Automatic

Unit tests should be fully automated and non-interactive. Test cases are usually executed regularly, and execution must be fully automated to make sense. A test whose output needs to be checked manually is not a good unit test. Do not use System.out for human validation in unit tests, you must use assert.

Independent people are Independent.

Keep unit tests independent. To ensure that unit tests are stable, reliable and easy to maintain, unit test cases should never call each other or depend on the order in which they are executed.

Repeatable(Repeatable)

Unit tests are repeatable and cannot be influenced by external circumstances. Because unit tests are usually put into continuous integration, they are executed every time code is checked in. If the single test is dependent on the external environment (network, service, middleware, etc.), continuous integration mechanism may be unavailable.

To be context-free, you need to design your code to inject SUT dependencies and use a DI framework like Spring to inject a local (in-memory) implementation or Mock implementation during testing.

Unit test granularity

For unit tests, make sure the test granularity is small enough to help pinpoint problems. Single-test granularity is at most class level and generally method level. Only small test granularity can locate the error position as soon as possible. Mono-testing is not responsible for checking interaction logic across classes or systems, which is the domain of integration testing.

Unit test scope

Incremental code for core business, core application, and core modules ensures unit tests pass. The new code should supplement the unit test in time. If the new code affects the original unit test, please correct it in time.

Other specification

1, the unit test code must be written in the following project directory SRC /test/ Java, not in the business code directory.

2. Basic goal of unit test: statement coverage reaches 70%; The core modules should have 100% statement coverage and 100% branch coverage.

3. The DAO layer, Manager layer and reusable Service mentioned in the application layer of the engineering specification should all be unit tested.

4. Write unit test code in accordance with BCDE principles to ensure the delivery quality of the tested modules.

9. Write unit test code in accordance with BCDE principles to ensure the delivery quality of the tested modules.

  • Border: Border value test, including loop boundary, special value, special time point, data order, etc.;
  • Correct: Typing correctly and getting the desired result;
  • Design: Writing unit tests in conjunction with Design documents
  • Error: Force the input of Error information (such as invalid data, abnormal flow, and non-service allowed input) and obtain the expected result.

5, for the database related query, update, delete and other operations, can not assume the existence of data in the database, or directly operate the database to insert data, please use procedures to insert or import data to prepare data.

6, and database related unit tests, you can set the automatic rollback mechanism, do not cause dirty data to the database. Or have an explicit prefix or suffix identification for the data generated by the unit tests.

7. It is recommended to do necessary refactoring for the untestable code to make the code testable and avoid writing non-standard test code to meet the test requirements.

8. During the design review phase, developers and testers need to determine the scope of unit tests, preferably covering all test cases.

9. Unit test, as a means of quality assurance, is not recommended to supplement unit test cases after the project is released. It is recommended to complete unit test before the project is tested.

10. To facilitate unit testing, business code should avoid the following:

  • There is too much to do in the constructor
  • There are too many global variables and static methods
  • There are too many external dependencies
  • Too many conditional statements exist. Note: It is recommended to use guard statement, policy mode, state mode and other methods to reconstruct multi-layer conditional statements

11. Don’t misunderstand unit tests as follows:

  • That’s what students do
  • Unit test code is redundant. The overall function of the system is strongly related to the normal testing of each unit component
  • Unit test code doesn’t need to be maintained, and after a year and a half, unit tests are almost obsolete
  • Unit tests are not dialectically related to online failures. Okay