Unit Testing

By the end of this lesson you'll be able to write clear, automated unit tests in C++ using the AAA pattern, cover the edge cases where bugs hide, and read tests written in real frameworks like GoogleTest and Catch2 — so you can change code without breaking it.

Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.

Part of the free C++ course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

A unit test is the smoke alarm for one room of your house. You don't wait for the whole building to burn down — you fit a small, cheap sensor in each room that goes off the instant that room has a problem. A unit test is that sensor for one function: it watches a single, isolated piece of behaviour and screams the moment a change breaks it. A houseful of alarms (a test suite) means you can renovate one room — refactor your code — and trust the rest will warn you if you knock something loose.

1. The AAA Pattern

Every good unit test has the same three-part shape, called AAA : Arrange the inputs, Act by calling the thing under test exactly once, then Assert that the result is what you expected. Keeping that order makes a test read top to bottom like a tiny story, so anyone can see what is being checked at a glance. The examples in this lesson use a six-line test harness — a helper called check(...) that prints PASS or FAIL — so the tests actually run here in the editor. Read this worked example first, then run it.

Your turn. The program below is almost complete — fill in the three blanks marked ___ using the hints, then run it and check the output against the block.

2. Naming, Edge Cases & Errors

A test name is documentation: when it fails, the name alone should tell you what broke. A useful convention is thing_condition_expectation — for example safeDivide_byZero_throws . Then aim your tests at the edge cases , the boundary inputs where bugs love to hide: empty containers, zero, negative numbers, and the largest allowed value. To test that bad input throws , wrap the call in try / catch — reaching the catch is the pass.

Now you try. The clamp function has three branches — below the range, inside it, and above it — and a good suite tests each one. Fill in the three blanks so every branch is covered:

3. How Real Frameworks Look

Our check(...) harness is just a teaching tool. Real projects use a battle-tested framework that gives you readable assertion macros, automatic test discovery, and a tidy results report. The two most common in C++ are GoogleTest and Catch2 . You can't run these here (they need their library linked into your build), but notice they are the same AAA pattern you just learned, with nicer syntax.

GoogleTest wraps each test in TEST(Suite, Name) and checks values with macros like EXPECT_EQ :

Catch2 is header-only and even lighter — TEST_CASE("...") names the test in plain English and REQUIRE does the assert:

Most frameworks give you two flavours of every check. The soft one (GoogleTest EXPECT_* , Catch2 CHECK ) records a failure but keeps running the rest of the test, so one run shows you every problem. The hard one ( ASSERT_* / REQUIRE ) stops that test immediately.

Use the hard version when carrying on would crash — for example, after checking a pointer isn't null, before you dereference it. Reach for the soft version everywhere else so a single failing line doesn't hide the next five.

A unit test should test one unit, but real code has dependencies — a clock, a database, a network call. A test double is a stand-in for one of those so your unit can be tested in isolation, fast and repeatably.

A stub returns canned answers: a fake clock whose now() always returns noon, so a test of "is it lunchtime?" is deterministic. A mock goes further and records how it was called, so you can assert "the email sender was called exactly once". In C++ you usually inject a double by coding to an interface (an abstract base class) and passing the fake implementation in during the test.

No blanks this time — just a brief and the harness. Write at least four check(...) calls covering a normal word and the edge cases (empty string, no vowels, all vowels), then print the summary line. Run it and confirm 4 passed, 0 failed .

Practice quiz

What do the three letters in the AAA test pattern stand for?

  • Assert, Act, Arrange
  • Add, Apply, Assert
  • Arrange, Act, Assert
  • Arrange, Assert, Announce

Answer: Arrange, Act, Assert. AAA = Arrange the inputs, Act by calling the unit under test once, then Assert the result is what you expected.

What is a 'unit' in unit testing?

  • The smallest testable piece of behaviour, usually one function or method
  • A whole program
  • A database table
  • A thread

Answer: The smallest testable piece of behaviour, usually one function or method. A unit is the smallest piece of behaviour you can test alone — usually a single function or one method — with no files, network, or other units.

In GoogleTest, what is the difference between EXPECT_EQ and ASSERT_EQ?

  • No difference
  • ASSERT keeps running; EXPECT stops
  • EXPECT is for doubles only
  • EXPECT keeps running the test on failure; ASSERT stops that test

Answer: EXPECT keeps running the test on failure; ASSERT stops that test. EXPECT_* reports a failure but lets the test continue; ASSERT_* stops that test immediately (use it when continuing would crash).

In Catch2, which macro stops the test immediately on failure?

  • CHECK
  • REQUIRE
  • SECTION
  • EXPECT

Answer: REQUIRE. REQUIRE is Catch2's hard assert that stops the test; CHECK is the soft version that reports but keeps going.

A test always passes even when the code is wrong. A likely cause is:

  • Using = instead of ==, e.g. check(x = 5, ...)
  • Using == instead of =
  • Too many test names
  • Comparing ints

Answer: Using = instead of ==, e.g. check(x = 5, ...). check(x = 5, ...) assigns 5 (a truthy value) instead of comparing. Use == to actually test equality: check(x == 5, ...).

Why is comparing two doubles with == in a test risky?

  • == is illegal for doubles
  • Doubles are always equal
  • Floating-point math is inexact, so 0.1 + 0.2 == 0.3 is false
  • It throws an exception

Answer: Floating-point math is inexact, so 0.1 + 0.2 == 0.3 is false. Floating-point results are inexact. Compare within a tolerance (fabs(a - b) < 1e-9) or use EXPECT_NEAR / Approx.

Which inputs are the 'edge cases' where bugs most often hide?

  • Only large random numbers
  • Empty, zero, negative, and boundary values
  • Only the happy path
  • Only string inputs

Answer: Empty, zero, negative, and boundary values. Bugs cluster at boundaries: empty containers, zero, negatives, and the largest allowed value. Aim tests at those.

To test that bad input throws, where should the pass be recorded?

  • After the call, outside any try
  • Only if no exception is thrown
  • In the destructor
  • When the catch block is reached

Answer: When the catch block is reached. Wrap the call in try/catch; reaching the catch is the pass. Put a fail line right after the call so a silent non-throw is caught.

What is a test double (stub or mock)?

  • A test that runs twice
  • A stand-in for a real dependency so a unit can be tested in isolation
  • A duplicate of the production class
  • A second main() function

Answer: A stand-in for a real dependency so a unit can be tested in isolation. A test double replaces a real dependency (clock, DB, network). A stub returns canned answers; a mock also records how it was called.

Writing the failing test BEFORE the code is known as what?

  • Integration testing
  • Fuzzing
  • Test-Driven Development (TDD)
  • Code coverage

Answer: Test-Driven Development (TDD). Writing the test first is TDD. It forces you to design the interface from the caller's view and guarantees the test fails before you make it pass.