When developing or modifying 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, which are also used to check the program code for errors. Developers use unit tests to test functions, interfaces, user interaction, and data transfer and processing in the implemented modules.
Component testing is part of the development process and is 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 errors always happen when a job is done. The art of programming is to identify, localize and fix possible errors in the code in time, still during the development process and before going live. For this, a sophisticated and systematic testing methodology of unit tests is essential.
Testing a program involves checking whether the program meets the requirements that were compiled before the start of development in the form of a requirements catalog and a detailed specification. The testing strategy is to find errors in the implemented software. If no errors occurred during a test run, it does not mean that the software is error-free. This is because errors could remain latent in the specific test scenario used.
In a complete test, it is important to cover all flow branches, conditions and paths. Test success does not consist in finding no errors, but in identifying all possible errors by testing the software correctly, systematically and completely. This requires repeated testing focused on finding bugs.
There are several types of tests
- Unit tests (module tests, component tests)
- System tests (tests of a system or subsystem)
- Integration tests (tests of the entire system with all integrated software components and subsystems)
- Single tests (to test individual functions/scenarios)
- Regression tests (repetition of previously executed tests to check that the software works correctly after software changes)
- Developer tests, load tests, acceptance tests, security tests, acceptance tests, etc.
Tests are executed either automatically or manually. Generally, all tests are divided into two categories:
- Blackbox Testing – testing based only on requirements, without drawing on knowledge of the program structure and code.
- Whitebox Testing – test scenarios and test cases are based on the knowledge of the implementation details, e.g. in developer tests of a programmer
JUnit Testing: 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) and others. 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 as open source in version JUnit 5. In the context of white-box testing of source code, JUnit is used for unit/module/component testing.
In contrast to a complete complex system, algorithms at unit level are usually clear and have a lower complexity. Since the methods of a Java program have clearly defined interfaces, they can be tested to a large extent completely with relatively few test cases. This enables JUnit tests to be integrated into the agile software development process, unit tests to be designed even before functional development on the basis of the requirements (extreme programming, XP) and applied in parallel with 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 in 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 matching test class that tests the behavior or methods of that class.
With JUnit 3.x, you derive your tests from a JUnit TestCase class and implement only the test methods. With JUnit 4.x and 5.x (from JDK1.5) you don’t need to extend a JUnit test and use Java Annotations instead. Here are some important annotations:
- @Test – annotation of a test method (public void)
- @Before – will be executed before each test method (in the preamble, to initialize the test; public void)
- @After – executed after each test method (in the post-run, 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 – executed once per test class when all tests are terminated, e.g. to free resources allocated with @BeforeClass (public static void)
- @Ignore – used together with @Test to ignore the test, e.g. if it is not implemented yet.
The JUnit TestRunner executes the test and logs the results. A test can either be a single TestCase or run together with other TestCases in a TestSuite.
Analogous to naming objects in your Java code, you should use a consistent naming convention when naming the elements of the JUnit test. A JUnit test project, its classes and methods have a direct relationship to the project under test, its 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 names to name test elements of the code and keep the reference to the original elements. For example:
- Original project: MyProject –> JUnit test project: MyProject.Test
- Original class: MyClass –> Test class: MyClassTest
- Original method: MyMethod –> Test method: MyMethod_UseCaseID_test
- original package: MyPackage –> test package: MyPackageTest
Since JUnit 4.x, the actions for initialization, test execution and cleanup are identified by annotations and the names can be freely chosen. However, although the JUnit tests follow the package structure, 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 bastion in the fight against software bugs. Behind it are system, integration, acceptance and other tests. With JUnit the developers and/or other testers examine the correct, i.e. error-free and requirement-conformal implementation of individual modules of the Java code. Any non-trivial function or method could contain errors and should be tested with JUnit.
Both a new and a changed Java code is tested in the LifeCycle with JUnit. When designing, planning, implementing and even before the actual test run with JUnit, weigh the effort and benefit of unit testing. Safety and mission critical software systems (aerospace, medical, military, rail and road traffic, etc.) place special quality demands on the stability, reliability and safety of the software. Therefore, you should test “everything” in such a safety-critical Java program if possible – even objects and methods that seem “trivial” and “error-free” to you. The consequences of an overlooked or untested error in the code can be devastating.
You should check pre- and post-conditions and execute both positive cases (normal cases, special cases) and negative cases (negative tests, exceptions). You should provoke exceptions in order to test the stability of the software. Query expected exceptions by try-catch statement and abort with fail() in case of unexpected situation.
Each new or changed requirement is followed by a new test. You should write the test before implementing the requirement.
Features and characteristics 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 compare the return value (actual value) of the method under test 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 either be “successful” (green) or “unsuccessful” (red). If a TestCase fails once and returns 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 error and repeat the JUnit test.
The library junit.jar must be available for 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 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 most popular alternatives to JUnit. How can you use JUnit when developing?
JUnit is already integrated in your Java IDE like NetBeans or Eclipse. JUnit tests are best suited for automatic and repeated unit tests (regression tests). You need to clearly define the initial and final state, the expected result and the test methods. You should make sure that the 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 (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 interfaces as well as the structuring and clarity of the architecture.
The goal of any software testing is to identify software bugs preemptively, before the software is handed over to operations. JUnit enables an efficient development-accompanying quality assurance both with the development of a new Java software and with later changes, optimizations and extensions of the code in the life cycle of the project. With the JUnit framework and the created test methods (TestCases, TestSuites) specified in the code, the unit tests can be executed not only by the author of the Java program but also by another tester. This approach supports the well-known dual control principle and makes it possible not only to automatically execute the existing unit tests 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 testing tool of any professional Java development. JUnit supports agile unit testing according to the concept of Test-driven Development (TDD) and is seamlessly integrated into the development process using the JUnit framework and IDE. JUnit tests increase software quality and become an integral part of quality assurance in the lifecycle of a Java development project.