← Module 4: Software Engineering Project
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.
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.
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:
- Red: write a failing test.
- Green: write the simplest code that passes.
- 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
- Compare software development methodologies, including waterfall, agile and scrum, and identify when each is appropriate
A focused answer to the HSC Software Engineering Module 4 dot point on development methodologies. Waterfall, agile, scrum, kanban, when each is appropriate, the worked example, and the traps markers look for.
- Set up continuous integration and deployment pipelines that build, test and release software automatically
A focused answer to the HSC Software Engineering Module 4 dot point on CI/CD. Build, test, deploy automation, GitHub Actions, the worked pipeline example, and the traps markers look for.
- Apply code review and quality practices, including peer review, style guides, linters and static analysis
A focused answer to the HSC Software Engineering Module 4 dot point on code review. Pull request reviews, style guides, linters, static analysis, the worked example, and the traps markers look for.