Search Knowledge

© 2026 LIBREUNI PROJECT

Unit Testing Principles

Unit Testing Principles

Unit testing is a software testing method where individual units or components of a software are tested. The purpose is to validate that each unit of the software code performs as expected. A unit is the smallest testable part of any software; it usually has one or a few inputs and usually a single output.

The Importance of Unit Testing

  • Early Defect Detection: Bugs caught during unit testing are significantly cheaper to fix than those found in production.
  • Refactoring Confidence: Unit tests act as a safety net. You can clean up or optimize code knowing that the tests will catch any regressions.
  • Documentation: Tests serve as executable documentation, showing how an API or class is intended to be used.

The FIRST Properties

Good unit tests follow the FIRST acronym:

  1. Fast: Tests should run quickly so that developers can run them frequently.
  2. Independent: Tests should not depend on each other or on a specific run order.
  3. Repeatable: A test should yield the same result every time it is run in any environment.
  4. Self-Validating: Tests should have a clear binary output (pass or fail) without requiring manual interpretation.
  5. Thorough/Timely: Tests should cover all boundary conditions and be written ideally at the same time as the code.

The AAA Pattern

A common structure for unit tests is Arrange, Act, Assert:

  • Arrange: Set up the test conditions and inputs.
  • Act: Invoke the method or function under test.
  • Assert: Verify that the output or state change matches expectations.

Test Doubles (Mocks and Stubs)

When a unit has dependencies (like a database or a web service), we use test doubles to isolate the unit:

  • Stubs: Provide canned answers to calls made during the test.
  • Mocks: Record expectations about how they are called and can be verified.

Measuring Test Quality

Code Coverage

Code coverage measures the percentage of code executed during the test suite. Common metrics include:

  • Statement Coverage: Which lines of code were executed?
  • Branch Coverage: Were all branches (e.g., if/else paths) taken?
  • Path Coverage: Were all possible execution paths through the function tested?

Crucial Note: 100% code coverage does not mean 100% of the logic is correct or that the code is well-tested. It only means the code was executed.

Mutation Testing

Mutation testing is a more advanced technique where the testing tool automatically makes small changes (mutations) to the production code (e.g., changing a > to a <). If the test suite still passes after a mutation, the test is “weak” because it failed to catch the regression.

Example: Unit Testing with Python (PyTest)

Consider a simple calculator class:

# calculator.py
class Calculator:
    def add(self, a, b):
        return a + b

    def divide(self, a, b):
        if b == 0:
            raise ValueError("Cannot divide by zero")
        return a / b

The corresponding unit test using pytest:

# test_calculator.py
import pytest
from calculator import Calculator

def test_add():
    # Arrange
    calc = Calculator()
    
    # Act
    result = calc.add(2, 3)
    
    # Assert
    assert result == 5

def test_divide():
    calc = Calculator()
    assert calc.divide(10, 2) == 5

def test_divide_by_zero():
    calc = Calculator()
    with pytest.raises(ValueError, match="Cannot divide by zero"):
        calc.divide(10, 0)

Java Example (JUnit 5)

import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;

class CalculatorTest {

    @Test
    void testAdd() {
        Calculator calc = new Calculator();
        assertEquals(5, calc.add(2, 3), "2 + 3 should equal 5");
    }

    @Test
    void testException() {
        Calculator calc = new Calculator();
        assertThrows(IllegalArgumentException.class, () -> {
            calc.divide(1, 0);
        });
    }
}

By adhering to these principles, unit tests become a powerful tool for ensuring code quality and long-term maintainability.