Skip to content
Home / Fundamentals

Patterns in Unit Testing

Unit testing is a software testing method in which individual units or components of a software application are tested in isolation from the rest of the application. This helps to ensure that each unit is working correctly and meets its specified requirements. There are several patterns that can be used to organize unit tests, including the AAA (Arrange-Act-Assert) pattern, the Given-When-Then pattern, the Four-Phase Test pattern, the F.I.R.S.T. (Fast, Independent, Repeatable, Self-Validating, Timely) pattern, and the "Test a Single Concept in Isolation" pattern. These patterns provide a structure for organizing and writing unit tests, making it easier to understand the purpose and intent of each test. Ultimately, the choice of pattern will depend on the needs of the specific project and the preferences of the team.

AAA (Arrange-Act-Assert) pattern

This pattern involves three steps for each unit test:

  • Arrange: Set up the necessary preconditions and inputs for the test.
  • Act: Perform the action being tested.
  • Assert: Verify that the result of the action is as expected.
def test_get_customer_name():
    # Arrange
    customer_id = 123
    expected_name = "John Smith"
    customer_database = {
        123: "John Smith",
        456: "Jane Doe"
    }

    # Act
    name = get_customer_name(customer_id, customer_database)

    # Assert
    assert name == expected_name

Given-When-Then pattern

This pattern is similar to the AAA pattern, but it uses more descriptive language to describe the steps of the test.

Given: Describe the preconditions and inputs for the test. When: Describe the action being performed. Then: Describe the expected result of the action.

def test_get_customer_name():
    # Given
    customer_id = 123
    expected_name = "John Smith"
    customer_database = {
        123: "John Smith",
        456: "Jane Doe"
    }

    # When
    name = get_customer_name(customer_id, customer_database)

    # Then
    assert name == expected_name

Four-Phase Test pattern: This pattern involves four steps for each unit test:

  • Setup: Set up the necessary preconditions and inputs for the test.
  • Exercise: Perform the action being tested.
  • Verify: Check that the action has the expected results.
  • Teardown: Clean up any resources used in the test.
def test_get_customer_name():
    # Setup
    customer_id = 123
    expected_name = "John Smith"
    customer_database = {
        123: "John Smith",
        456: "Jane Doe"
    }

    # Exercise
    name = get_customer_name(customer_id, customer_database)

    # Verify
    assert name == expected_name

    # Teardown
    # (no teardown needed in this example)

F.I.R.S.T. (Fast, Independent, Repeatable, Self-Validating, Timely) pattern

This pattern is designed to ensure that unit tests are fast, independent, repeatable, self-validating, and timely.

  • Fast: Tests should run quickly, so they can be run frequently without slowing down development.
  • Independent: Tests should not depend on the state of other tests or the system under test, so they can be run in any order.
  • Repeatable: Tests should produce the same results every time they are run, regardless of the environment or system state.
  • Self-validating: Tests should have a clear pass or fail result, without the need for manual inspection.
  • Timely: Tests should be written at the same time as the code they are testing, to ensure they are relevant and up-to-date.
def test_get_customer_name_is_fast():
    # Test that the get_customer_name function is fast (runs in under 1 second)
    customer_id = 123
    expected_name = "John Smith"
    customer_database = {
        123: "John Smith",
        456: "Jane Doe"
    }
    name = get_customer_name(customer_id, customer_database)
    assert name == expected_name

def test_get_customer_name_is_independent():
    # Test that the get_customer_name function is independent (does not depend on state from other tests)
    customer_id = 123
    expected_name = "John Smith"
    customer_database = {
        123: "John Smith",
        456: "Jane Doe"
    }
    name = get_customer_name(customer_id, customer_database)
    assert name == expected_name

def test_get_customer_name_is_repeatable():
    # Test that the get_customer_name function is repeatable (produces the same result every time)
    customer_id = 123
    expected_name = "John Smith"
    customer_database = {
        123: "John Smith",
        456: "Jane Doe"
    }
    name = get_customer_name(customer_id, customer_database)
    assert name == expected_name

def test_get_customer_name_is_self_validating():
    # Test that the get_customer_name function is self-validating (has a clear pass/fail result)
    customer_id = 123
    expected_name = "John Smith"
    customer_database = {
        123: "John Smith",
        456: "Jane Doe"
    }
    name = get_customer_name(customer_id, customer_database)
    assert name == expected_name

def test_get_customer_name_is_timely():
    # Test that the get_customer_name function is timely (written at the same time as the code being tested)
    customer_id = 123
    expected_name = "John Smith"
    customer_database = {
        123: "John Smith",
        456: "Jane Doe"
    }
    name = get_customer_name(customer_id, customer_database)
    assert name == expected_name

"Test a Single Concept in Isolation" pattern

This pattern involves focusing each test on a single concept or feature, to ensure that it is tested in isolation from other parts of the system. This can help to identify issues more quickly and make it easier to understand the cause of any failures.

def test_get_customer_name_returns_correct_name_for_valid_id():
    # Test that the get_customer_name function returns the correct name for a valid customer ID
    customer_id = 123
    expected_name = "John Smith"
    customer_database = {
        123: "John Smith",
        456: "Jane Doe"
    }
    name = get_customer_name(customer_id, customer_database)
    assert name == expected_name

def test_get_customer_name_returns_none_for_invalid_id():
    # Test that the get_customer_name function returns None for an invalid customer ID
    customer_id = 999
    expected_name = None
    customer_database = {
        123: "John Smith",
        456: "Jane Doe"
    }
    name = get_customer_name(customer_id, customer_database)
    assert name == expected_name

def test_get_customer_name_returns_none_for_empty_database():
    # Test that the get_customer_name function returns None for an empty customer database
    customer_id = 123
    expected_name = None
    customer_database = {}
    name = get_customer_name(customer_id, customer_database)
    assert name == expected_name

"Test First" pattern

This pattern involves writing tests before writing the code that will be tested. This can help to ensure that the code is designed with testability in mind and that tests are written for the correct behavior.