Module 4: Software Engineering Project

NSWSoftware EngineeringSyllabus dot point

Inquiry Question 1: How are large-scale software solutions developed and managed?

Describe testing strategies, including unit testing, integration testing, system testing and user acceptance testing

A focused answer to the HSC Software Engineering Module 4 dot point on testing. Unit, integration, system, UAT, the test pyramid, test-driven development, the worked Python example, and the traps markers look for.

Generated by Claude OpusReviewed by Better Tuition Academy6 min answer

Have a quick question? Jump to the Q&A page

What this dot point is asking

NESA wants you to distinguish the testing strategies that operate at different scales of a system, identify their purpose, and give a concrete example of each.

The answer

The test pyramid

The conventional model: many cheap tests at the base, fewer expensive tests at the top.

The test pyramid A pyramid with four horizontal bands. The base is the widest, labelled unit tests with many fast tests. Above it integration tests, then end-to-end tests, with user acceptance testing as the narrowest band at the top. An arrow on the right side indicates that cost and time per test increase upward. UAT end-to-end integration unit tests slower fewer faster many ms seconds tens of s minutes cost and time per test increase upward

Unit testing

Test one function or class in isolation. Dependencies (database, network, file system) are replaced with mocks or stubs.

# code under test
def calculate_gst(price):
    return round(price - price / 1.1, 2)

# unit test
import pytest

def test_calculate_gst_basic():
    assert calculate_gst(11.0) == 1.0

def test_calculate_gst_zero():
    assert calculate_gst(0.0) == 0.0

def test_calculate_gst_rounds_to_two_decimals():
    assert calculate_gst(10.99) == 1.0

Properties: fast, deterministic, run on every commit, locate bugs precisely.

Integration testing

Test how components work together, including real or test-instance external services (database, message queue).

def test_create_order_integration(test_db):
    # Real test database, populated with a test user
    response = client.post(
        "/api/orders",
        json={"product_id": 7, "qty": 2},
        headers={"Authorization": "Bearer test-token"},
    )
    assert response.status_code == 201
    order = test_db.execute("SELECT * FROM orders WHERE id = ?", (response.json["id"],)).fetchone()
    assert order is not None
    assert order["product_id"] == 7
    items = test_db.execute("SELECT * FROM order_items WHERE order_id = ?", (order["id"],)).fetchall()
    assert len(items) == 1

Properties: slower than unit tests (seconds), catch issues that arise at boundaries (SQL errors, contract mismatches, transaction handling).

System (end-to-end) testing

Test the whole application from outside, typically through the UI or public API, against a deployed environment.

import { test, expect } from "@playwright/test";

test("user can complete a purchase", async ({ page }) => {
  await page.goto("/");
  await page.getByRole("button", { name: "Sign in" }).click();
  await page.getByLabel("Email").fill("test@example.com");
  await page.getByLabel("Password").fill("test-password");
  await page.getByRole("button", { name: "Log in" }).click();

  await page.getByRole("link", { name: "Mechanical keyboard" }).click();
  await page.getByRole("button", { name: "Add to cart" }).click();
  await page.getByRole("link", { name: "Checkout" }).click();
  await page.getByRole("button", { name: "Pay now" }).click();

  await expect(page.getByText("Thank you for your order")).toBeVisible();
});

Properties: slow (tens of seconds per test), flakey (real browser, real network), catch issues no other layer can.

User acceptance testing (UAT)

The product is exercised by real users (or business stakeholders standing in for them) against acceptance criteria from the original brief. Driven by humans, not automation.

A typical UAT scenario:

  • Acceptance criteria: "A merchandiser can add a new promotional banner to the home page that disappears after the promotion end date."
  • Tester: the head of merchandising.
  • Pass criteria: they can complete the task without developer help, and the banner behaves as documented.

UAT happens after development, before release. Confirms the system meets the business needs, not just the technical specification.

Test-driven development (TDD)

Write the test first, watch it fail, write the code to make it pass, then refactor. Cycle:

  1. Red: write a failing test.
  2. Green: write the simplest code that passes.
  3. Refactor: clean up the code while tests stay green.

TDD produces a comprehensive test suite as a side effect, encourages small focused units, and surfaces design issues early.

Other test types

  • Regression testing: rerun existing tests after a change to confirm nothing was broken. Usually automated.
  • Performance testing: measure response time and throughput under load.
  • Security testing: SAST, DAST, penetration testing (see secure-development-lifecycle).
  • Smoke testing: a quick check after deployment that the basics work.
  • Property-based testing: generate random inputs and assert properties (rather than checking specific cases).

Tooling

Language Unit Integration E2E
Python pytest pytest with fixtures Playwright, Selenium
JavaScript Vitest, Jest Vitest, Jest Playwright, Cypress
Java JUnit JUnit + Testcontainers Selenium

Continuous testing

Tests run on every commit in CI. Failed tests block merging. This is what makes continuous integration work.

Past exam questions, worked

Real questions from past NESA papers on this dot point, with our answer explainer.

2024 HSC6 marksDistinguish between unit, integration, system and user acceptance testing. Give an example of each in the context of an online shopping site.
Show worked answer →

Unit testing tests one function or class in isolation. Dependencies are mocked or stubbed. Fast, runs in milliseconds. Example: a calculate_gst(price) function is tested to return 0.10 for an input of 1.10 (10 percent GST). The test does not touch the database.

Integration testing tests how units work together. Components and external services (database, message queue) are real or close-to-real. Example: the checkout handler is tested against a real test database to confirm an order row, an order_items row, and a payment row are all written correctly when a checkout request comes in.

System testing tests the application as a whole, end-to-end, in an environment that resembles production. Example: a Playwright test opens the home page, logs in, adds an item to the cart, completes checkout and confirms the confirmation email is received - all through a real browser against a deployed staging copy.

User acceptance testing (UAT) is the human-driven test that the product meets business needs. Real users (or business stakeholders standing in for them) exercise the system against acceptance criteria. Example: the merchandising manager checks that the new "shop by occasion" navigation works as expected, finds the right products, and matches the agreed brief.

The four layers form a pyramid: many unit tests at the base, fewer integration tests above, fewer system tests above that, and selective UAT at the top. Markers reward all four levels named correctly, the right scope at each level (isolation, components together, end-to-end, business), and a concrete shopping-site example for each.

Related dot points