When developing or changing software, it is important to ensure that the program code was created exactly according to the requirements specification and works correctly in all possible situations and cases (use cases). This is done through various tests with which the program code is also checked for errors. Developers use unit tests to test functions, interfaces, user interaction, and data transmission and processing in the implemented modules.
The component tests are part of the development process and are an integral part of quality assurance. Both the professionalism of the developers and the test methods and tools used play an important role in the quality of the code. Human mistakes always happen when a job is being done. The art of programming consists of identifying, localizing, and correcting possible errors in the code in good time, still during the development process, and before going live. A sophisticated and systematic component test methodology is essential for this.
When testing a program, it’s checked whether the program meets the requirements that were compiled in the form of a requirements catalog and a detailed specification before development began. The test strategy is to find errors in the implemented software. If no errors occurred during a test run, this does not mean that the software is error-free. Because errors could remain hidden in the specific test scenario used.
For a full test, it’s important to cover all branches, conditions, and paths. The test success does not consist in the fact that no errors were found, but in the identification of all possible errors through correct, systematic, and complete testing of the software. This requires repeated tests aimed at searching for errors.
There are different types of tests:
- Unit tests (module tests, component tests)
- System tests (tests of a system or subsystem)
- Integration tests (tests of the overall system with all integrated software components and subsystems)
- Individual tests (to check individual functions / scenarios)
- Regression testing (repeating the tests previously performed to check that the software works correctly after software changes)
- Developer tests, load tests, acceptance tests, security tests, etc.
Tests are run either automatically or manually. In general, all tests are divided into two categories:
- Black box testing – testing only on the basis of requirements, without drawing on knowledge of the program structure and the program code
- White box testing – test scenarios and test cases are based on the knowledge of the implementation details, such as in developer tests of a programmer
JUnit tests: what is it?
JUnit (Java Unit) is a widely used framework that has become the standard tool for automated unit testing of classes and methods in Java programs. The concept of JUnit is based on the framework SUnit, which was developed for SmallTalk. In the meantime, there are a number of similar frameworks and extensions that have adopted the concept of JUnit and enable unit tests in other programming languages: NUnit (.Net), CppUnit (C++), DbUnit (databases), PHPUnit (PHP), HTTPUnit (web development), etc. Such frameworks for unit testing are commonly referred to as “xUnits”.
The JUnit framework is constantly being further developed by the JUnit project and is currently available in version JUnit 5 as open source. In the context of white-box tests of the source code, JUnit is used for unit/module/component tests.
Unlike a complete complex system, algorithms at the unit level are usually clear and less complex. Since the methods of a Java program have clearly defined interfaces, they can be largely fully tested with relatively few test cases. This enables JUnit tests to be integrated into the agile software development process, to design unit tests based on the requirements before the functional development (extreme programming, XP), and to apply them in parallel with the functional development (quality assurance).
The module-specific use cases can be limited to typical, representative samples such as “normal samples” (normal values), “extreme samples” (maximum/minimum/limit values), and “negative samples” (invalid values), which drastically reduces the number of test cases required. If interaction with external components is required for a unit test, this interface is simply simulated.
How do JUnit tests work?
The JUnit framework is integrated into all common IDEs like Eclipse, NetBeans, IntelliJ, and BlueJ and build tools like Gradle, Maven, and Ant. For each class in the project, you create a suitable test class with which the behavior or the methods of the respective class are tested.
With JUnit 3.x you derive your tests from a JUnit TestCase class and only implement the test methods. With JUnit 4.x and 5.x (from JDK1.5), you do not have to extend a JUnit test and use Java annotations instead. Here are some important annotations:
- @Test – Identification of a test method (public void)
- @Before – is executed before each test method (in advance, to initialize the test; public void)
- @After – is executed after each test method (afterwards, to clear the test; public void)
- @BeforeClass – is executed once per test class before the first test method, e.g. for initializations (public static void)
- @AfterClass – is executed once per test class when all tests are ended, e.g. release the resources allocated with @BeforeClass (public static void)
- @Ignore – used in conjunction with @Test to ignore the test, e.g. is not yet implemented
The JUnit TestRunner executes the test and records the results. A test can either be a single TestCase or run together with other TestCases in a TestSuite.
Similar to the naming for objects in your Java code, you should also use a uniform naming convention when naming the elements of the JUnit test. A JUnit test project, its classes and methods are directly related to the project to be tested, the classes and methods. To keep this relationship clear and consistent, you should follow the same naming convention in your projects. It is best to add the postfix “Test” to the original name in order to name test elements of the code and to maintain the reference to the original elements. For example:
Originalproject: MyProject -> JUnit-Testproject: MyProject.Test
Originalclass: MyClass -> Testclass: MyClassTest
Originalmethod: MyMethod -> Testmethod: MyMethod_UseCaseID_test
Originalpackage: MyPackage -> Testpackage: MyPackageTest
Since JUnit 4.x, the actions for initialization, test execution, and cleanup are identified by annotations and the names can be freely chosen. Although the JUnit tests follow the package structure, however, they have their own root “Test” in the file system, so that the build runs independently of the TestCases of the TestSuite.
What do you test with JUnit?
JUnit tests are the first defense in the fight against software bugs. Behind this are system, integration, acceptance, and other tests. With JUnit, the developers or other testers check the correct, i.e. Error-free and requirements-compliant implementation of individual modules of the Java code. Any non-trivial function or method could contain errors and should be tested with JUnit.
Both new and changed Java code is tested in the LifeCycle with JUnit. During conception, planning, implementation, and even before the actual test run with JUnit, evaluate the effort and benefits of the unit test. Safety and mission-critical software systems (aerospace, medicine, military, rail and road traffic, etc.) place special quality requirements on the stability, reliability, and security of the software. Therefore, you should test “everything” as possible in such a safety-critical Java program – including objects and methods that appear to you to be “trivial” and “error-free”. Because the consequences of an error that is ignored or not tested in the code can be devastating.
You should check preconditions and postconditions and execute both positive cases (normal cases, special cases) and negative cases (negative tests, exceptions). You should specifically provoke exceptions in order to check the stability of the software. Query expected exceptions with a try-catch statement and cancel with fail() in the event of an unexpected situation.
Each new or changed requirement is followed by a new test. You should write the test before implementing the requirement.
Properties and special features of JUnit tests
For each method to be tested, you create at least one test method in which the results of the class/method to be tested are validated. Conditions are defined that compares the return value (actual value) of the method to be tested with the expected value (target value). Alternatively, the result can be checked for success (true). The test result is evaluated with the JUnit method assertEquals() or assertTrue().
A test can be either “successful” (green) or “unsuccessful” (red). If a TestCase fails and delivers an unexpected result, this means either a bug in the tested Java code or an error in the implemented JUnit test method. In both cases, you should correct the mistake and repeat the JUnit test.
The junit.jar library must be available to the IDE. You can add JUnit 5 to a Java project in Eclipse as follows:
Project> Properties> Java Build Path > Libraries > Add Library … > JUnit 5 > Finish
To run a test, you can right-click the test class and select the item “Run As > JUnit Test” in the context menu. Eclipse opens the JUnit view and shows success (green; failures: 0) or failure (red; failures:> 0).
Alternatives to JUnit Test
NUnit, TestNG, Arquillian, Mockito, and Cucumber are the best known popular alternatives to the JUnit.
How can you use JUnit when developing?
JUnit is already integrated into your Java IDE such as NetBeans or Eclipse. JUnit tests are best suited for automatic and repeated unit tests (regression tests). You have to clearly define the initial and final state, the expected result, and the test methods. You should make sure that the degree of coverage of the unit tests is high (ideally complete) and that all-important use cases and constellations are tested. Once a test has been developed, it is adapted to the changed requirements and not removed.
Starting with individual classes and methods, test hierarchies can be built up to the entire Java project. The project test can be integrated into the daily master build to ensure the current status of the actual development project and the JUnit project. The test-first approach of Extreme Programming with JUnit improves the definition and validation of the interfaces as well as the structuring and clarity of the architecture.
The goal of every software test is to identify software bugs preventively, even before the software is put into operation. JUnit enables efficient quality assurance during development, both when developing new Java software and when changing, optimizing, and expanding the code during the project’s life cycle. With the JUnit Framework and the test methods (TestCases, TestSuites) specified in the code, the unit tests can not only be carried out by the author of the Java program but also by another tester. This approach supports the well-known four-eyes principle and enables the existing unit tests not only to be carried out automatically during an external code review (audit) but also to have the test results automatically evaluated with JUnit.
JUnit is considered the most proven framework for test automation and serves as a standard test tool for every professional Java development. JUnit supports agile unit tests based on the test-driven development (TDD) concept and is seamlessly integrated into the development process with the help of the JUnit framework and IDE. JUnit tests increase software quality and become an integral part of quality assurance in the life cycle of a Java development project.