Unit 4: Various Testing Frameworks Available in Java

CSE376 — Automated Testing 10 min read

I. Orientation — The Java Automated-Testing Ecosystem

Automated testing uses executable code to verify that software behaves as expected. In Java, test frameworks such as JUnit and TestNG define tests, Maven manages dependencies and execution, Jenkins automates the pipeline, and Gherkin expresses behavior in business-readable scenarios.

Defining characteristics:

  • Repeatability: The same test should produce the same result when executed under identical conditions.
  • Isolation: A unit test normally checks one class or method without depending on databases, networks, or other external systems.
  • Automation: Tools discover tests, execute them, compare actual and expected results, and publish reports.
  • Fast feedback: Developers run focused tests locally, while continuous integration servers run broader suites after code changes.
  • Traceability: Test names, reports, and failure messages connect requirements with verified behavior.
  • Test pyramid:
    • Unit tests: Numerous, fast tests of individual units.
    • Integration tests: Fewer tests of interactions between components.
    • End-to-end tests: A small number of broader, slower workflow tests.

II. Tool Evaluation — Choosing an Appropriate Testing Stack

A. Testing Tools Selection Criteria

Testing tools should be selected according to project requirements, technical compatibility, maintainability, and total operating cost.

  • Testing level: Match the tool to unit, integration, API, UI, performance, security, or acceptance testing; JUnit 5 primarily supports Java unit and integration tests.
  • Technology compatibility: Verify support for the Java version, build system, operating system, application framework, and IDE used by the team.
  • Automation support: Confirm that tests can run non-interactively through commands such as mvn test and return meaningful process exit codes.
  • CI integration: Prefer tools that Jenkins or another CI server can execute and whose XML or HTML reports it can publish.
  • Learning curve: Consider team knowledge, documentation quality, debugging facilities, and readability of test code.
  • Maintainability: Assess test discovery, reusable fixtures, parameterized tests, tagging, parallel execution, and extension mechanisms.
  • Reliability: A suitable tool should minimize nondeterministic or “flaky” results and clearly identify failed assertions.
  • Cost and support: Include licensing, infrastructure, training, maintenance, community activity, and vendor support rather than considering purchase price alone.

III. Jenkins — Continuous Integration and Test Automation

A. Fundamentals of Jenkins

Jenkins is an open-source automation server used to build, test, and deliver software whenever a defined event occurs.

  • Continuous integration: A repository push can trigger compilation and automated tests, allowing defects to be detected soon after introduction.
  • Job: A configured unit of work, such as checking out a Git repository and running mvn test.
  • Pipeline: A version-controlled workflow stored conventionally in a Jenkinsfile.
  • Agent: A machine or container that provides the workspace and executes pipeline stages.
  • Plugins: Extensions integrate Jenkins with Git, Maven, credentials stores, test reports, and notification systems.
  • Test reporting: Jenkins can read Maven-generated JUnit XML files and display test counts, failures, and historical trends.
  • Declarative pipeline example:
GROOVY
pipeline {
    agent any
    stages {
        stage('Test') {
            steps {
                sh 'mvn clean test'
            }
        }
    }
    post {
        always {
            junit 'target/surefire-reports/*.xml'
        }
    }
}
  • Pipeline result: A failed Maven test normally produces a nonzero exit status, causing the Jenkins stage and build to fail.

IV. TestNG — Configurable Java Testing

A. TestNG

TestNG is a Java testing framework inspired by JUnit that emphasizes flexible configuration, grouping, parameterization, dependencies, and parallel execution.

  • Test declaration: Methods annotated with @Test are discovered and executed by TestNG.
  • Lifecycle annotations: @BeforeMethod and @AfterMethod surround each test, while @BeforeClass and @AfterClass operate at class level.
  • Groups: @Test(groups = "smoke") categorizes tests so selected groups can be included or excluded.
  • Data-driven testing: @DataProvider supplies multiple argument sets to one test method.
  • Dependencies: dependsOnMethods controls execution relationships, although excessive dependencies can reduce test isolation.
  • Parallelism: TestNG can run methods, classes, or suites concurrently through its XML configuration.
  • Assertions: Assert.assertEquals(actual, expected) records a failure when the values differ.
  • Suite configuration: A testng.xml file can identify packages, classes, groups, parameters, and thread counts.

V. Maven — Build and Dependency Management

A. Maven

Maven is a Java build tool that uses a Project Object Model, stored in pom.xml, to define dependencies, plugins, and lifecycle operations.

  • Standard layout: Production code belongs in src/main/java, while test code belongs in src/test/java.
  • Dependency management: Maven downloads declared test libraries and their required transitive dependencies from repositories.
  • Scope: <scope>test</scope> makes JUnit available during test compilation and execution without packaging it with the application.
  • Lifecycle: mvn test performs earlier phases such as compilation and then runs tests.
  • Surefire plugin: Maven Surefire normally executes unit tests and writes reports under target/surefire-reports.
  • Failsafe plugin: Maven Failsafe is commonly used for integration tests during the integration-test and verify phases.
  • JUnit 5 dependency:
XML
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.2</version>
    <scope>test</scope>
</dependency>
  • Reproducibility: Versioned dependencies and plugins allow local machines and Jenkins agents to use the same test components.

VI. JUnit — Java Unit-Testing Framework

A. Introduction to JUnit Framework

JUnit is a framework for defining, executing, and reporting automated tests for Java code, with each test expressing expected observable behavior.

  • Test case: A method containing setup, an action, and assertions about the result.
  • Fixture: The objects and input data required to place the system under test in a known state.
  • Assertion: A comparison that fails the test when an expectation is not satisfied.
  • Test runner: The framework component that discovers test classes, invokes lifecycle methods, and records outcomes.
  • Independence: Tests should not rely on execution order or state left by another test.
  • Result states: A test may pass, fail through an assertion, abort because an assumption is unmet, or fail with an unexpected exception.

B. JUnit 5

JUnit 5 is a modular generation of JUnit composed of the Platform, Jupiter, and Vintage components.

  • JUnit Platform: Provides the launcher and test-engine interface used by IDEs, Maven, and build servers.
  • JUnit Jupiter: Supplies the JUnit 5 programming model, including @Test, lifecycle annotations, and assertions.
  • JUnit Vintage: Runs JUnit 3 and JUnit 4 tests when the Vintage engine is installed.
  • Parameterized tests: @ParameterizedTest executes one test with values from sources such as @ValueSource.
  • Display names: @DisplayName("rejects a negative balance") provides readable report text.
  • Organization: @Nested, @Tag, and package selection support structured and filtered test execution.
  • Extensions: @ExtendWith registers reusable behavior such as dependency injection or temporary resource management.

C. Define a Test in JUnit

A JUnit test is a discoverable method annotated with @Test that invokes production code and verifies its behavior with assertions.

  • Structure: Arrange inputs and fixtures, Act by calling the target method, and Assert the expected outcome.
  • Method form: In JUnit Jupiter, a test method is usually package-private, returns void, and need not be public.
  • Assertions: Common methods include assertEquals, assertTrue, assertNull, assertThrows, and assertAll.
  • Exception verification:
JAVA
@Test
void divideByZeroThrowsException() {
    assertThrows(ArithmeticException.class, () -> calculator.divide(8, 0));
}
  • Failure meaning: The test passes only if calculator.divide(8, 0) throws ArithmeticException.

D. JUnit Naming Conventions

JUnit naming conventions make test intent visible even though most names are recommendations rather than framework requirements.

  • Class names: Use forms such as CalculatorTest for unit tests and PaymentServiceIntegrationTest for integration tests.
  • Method names: Describe behavior, for example divideByZeroThrowsException or withdrawRejectsInsufficientFunds.
  • Behavior pattern: Names may follow method_condition_expectedResult, such as divide_zeroDivisor_throwsException.
  • Readability: Prefer domain behavior over vague names such as test1 or checkMethod.
  • Discovery distinction: Modern JUnit discovery relies on annotations; naming patterns may still matter to Maven plugin configuration.
  • Consistency: A project should adopt one naming style so failures remain easy to scan in IDE and CI reports.

E. JUnit Test Suites

A JUnit test suite groups selected tests so they can be launched together as one logical testing unit.

  • Suite API: JUnit Platform Suite annotations require the junit-platform-suite dependency.
  • Package selection: @SelectPackages("com.example.payment") includes tests found in a package.
  • Class selection: @SelectClasses({CalculatorTest.class, TaxTest.class}) names explicit test classes.
  • Tag filtering: @IncludeTags("smoke") runs only tests carrying the matching @Tag.
  • Suite example:
JAVA
@Suite
@SelectPackages("com.example")
@IncludeTags("smoke")
class SmokeTestSuite {
}
  • Use: Suites support release checks and subsystem runs, but ordinary build-wide discovery often requires no explicit suite class.

F. Example JUnit Test

A focused JUnit test demonstrates fixture creation, method execution, and verification of one behavior.

JAVA
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class CalculatorTest {
    private Calculator calculator;

    @BeforeEach
    void setUp() {
        calculator = new Calculator();
    }

    @Test
    void addTwoPositiveNumbersReturnsSum() {
        int actual = calculator.add(2, 3);
        assertEquals(5, actual, "2 + 3 should equal 5");
    }
}
  • Arrange: setUp() creates a fresh Calculator before each test.
  • Act: calculator.add(2, 3) produces the actual value.
  • Assert: assertEquals(5, actual, ...) compares expected value 5 with the observed result.
  • Isolation: Recreating the fixture prevents mutable state from leaking between test methods.

G. JUnit Code Constructs

JUnit code constructs provide annotations and assertions for lifecycle control, grouping, conditional execution, and validation.

  • Lifecycle:
    • @BeforeEach and @AfterEach execute around every test.
    • @BeforeAll and @AfterAll execute once per test class and are normally static.
  • Core annotations: @Test declares a test, @Disabled skips it, and @RepeatedTest(3) repeats it three times.
  • Assertions: assertEquals(expected, actual), assertSame, assertFalse, and assertIterableEquals check specific conditions.
  • Grouped assertions: assertAll evaluates several assertions and reports all failures together.
  • Timeouts: assertTimeout verifies that an operation completes within a specified Duration.
  • Assumptions: assumeTrue(condition) aborts a test when an environmental prerequisite is absent.
  • Dynamic tests: @TestFactory produces test cases at runtime as DynamicTest objects.

VII. Gherkin — Executable Behavior Specifications

A. Gherkin

Gherkin is a structured language used by behavior-driven development tools such as Cucumber to describe features through examples understandable to technical and nontechnical participants.

  • Feature: Names a capability, such as Feature: Account withdrawal.
  • Scenario: Describes one concrete behavior or business rule.
  • Given: Establishes initial context, such as an account balance.
  • When: Identifies the event or action being performed.
  • Then: States the expected observable outcome.
  • And/But: Extends a preceding step without changing its semantic category.
  • Scenario Outline: Runs a scenario repeatedly with rows from an Examples table.
  • Concrete specification:
GHERKIN
Feature: Calculator addition

  Scenario: Add two positive numbers
    Given the calculator is available
    When I add 2 and 3
    Then the result should be 5
  • Step definitions: Java methods connect these sentences to automation code; Gherkin alone describes behavior but does not execute application logic.

VIII. JUnit Development Environment — IDE Support and Setup

A. Eclipse Support for JUnit

Eclipse integrates JUnit execution, debugging, navigation, and result reporting into the Java development environment.

  • Running tests: A class, method, package, or project can be launched through Run As → JUnit Test.
  • JUnit view: Eclipse displays executed tests, elapsed time, failures, errors, and stack traces.
  • Status indicators: Green represents a successful run, while red indicates at least one failure or error.
  • Failure navigation: Selecting a stack-trace entry opens the corresponding source line.
  • Debugging: Debug As → JUnit Test runs tests with breakpoints, variable inspection, and step controls.
  • Rerunning: The JUnit view can repeat the entire run or only failed tests.
  • Build integration: Maven projects imported into Eclipse obtain JUnit libraries from pom.xml, keeping IDE and command-line configurations aligned.

B. Installation of JUnit

JUnit should normally be installed as a versioned build dependency rather than as a manually copied JAR file.

  1. Maven installation: Add the org.junit.jupiter:junit-jupiter test dependency to pom.xml, then refresh the Maven project.
  2. Gradle installation: Declare testImplementation("org.junit.jupiter:junit-jupiter:<version>") and enable the JUnit Platform:
GROOVY
dependencies {
    testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
}

test {
    useJUnitPlatform()
}
  • Eclipse option: For a non-build-tool project, Build Path → Add Libraries → JUnit can add the IDE-provided library.
  • Verification: Create a class under the test source folder with an @Test method and run it through Eclipse or mvn test.
  • Version control: Commit pom.xml or build.gradle, but do not commit Maven’s target directory or downloaded dependency files.