ProgramGuru

Test C Programs with Cases, Warnings and Assertions

Test C Programs with Cases, Warnings and Assertions

A pass-checking function works for 45, but an off-by-one error could still reject exactly 40. Tests should distinguish the rule’s boundary, not just exercise its easiest input.

A useful test compares an observed result with an independently stated requirement. Normal cases, boundaries and valid failure paths expose different kinds of mistakes.

  1. Explain

    Choose cases that distinguish >= 40 from > 40.

  2. Trace

    Add checks for the valid endpoint marks zero and one hundred.

  3. Apply

    Choose boundary cases rather than testing only the happy path.

Test C Programs: follow the data

  1. Start

    A pass-checking function works for 45, but an off-by-one error could still reject exactly 40. Tests should distinguish the rule’s boundary, not just exercise its easiest input.

  2. Operation

    A useful test compares an observed result with an independently stated requirement. Normal cases, boundaries and valid failure paths expose different kinds of mistakes.

  3. Result

    Threshold checks passed

A guided model of this example. The illustration explains the process; run the complete C program to check its output.

Understand the operation

For a valid mark between zero and one hundred, check 39, 40 and 41. The middle case identifies whether the threshold is inclusive. Add the endpoints zero and one hundred to cover the valid range.

assert checks an expression when assertions are enabled. It is a development check, not input validation: defining NDEBUG can remove the check. Never hide essential work inside an assertion expression.

Read the complete C program

This complete C17 program uses a small school example. Read declarations before the statements that use their values. Each printf format describes how the corresponding value is written to standard output.

C17 · complete program

#include <stdio.h>
#include <assert.h>
static int passed(int mark) { return mark >= 40; }
int main(void) {
  assert(!passed(39));
  assert(passed(40));
  assert(passed(41));
  puts("Threshold checks passed");
  return 0;
}

Expected output

Threshold checks passed
Run in C compiler →

Follow the values through the code

C17 · code used in this explanation

#include <stdio.h>
#include <assert.h>
static int passed(int mark) { return mark >= 40; }
int main(void) {
  assert(!passed(39));
  assert(passed(40));
  assert(passed(41));
  puts("Threshold checks passed");
  return 0;
}

The first assertion expects a below-threshold mark not to pass.

The next two verify the exact threshold and a value above it. The message is printed after all active assertions succeed.

Follow the worked calculation

  1. Read the starting values

    The first assertion expects a below-threshold mark not to pass.

  2. Perform the operation

    The next two verify the exact threshold and a value above it. The message is printed after all active assertions succeed.

  3. Check the observable result

    Threshold checks passed

    Threshold checks passed
A guided model of this example. The illustration explains the process; run the complete C program to check its output.

Try a small variation

C17 · code used in this explanation

#include <stdio.h>
#include <assert.h>
static int passed(int mark) { return mark >= 40; }
int main(void) {
  assert(!passed(39));
  assert(passed(40));
  assert(passed(41));
  puts("Threshold checks passed");
  return 0;
}

Add checks for the valid endpoint marks zero and one hundred.

Worked answer

The additional assertions test two valid endpoints. They do not replace the boundary case at forty, which is the case that distinguishes the common off-by-one implementations.

C17 · complete program

#include <stdio.h>
#include <assert.h>
static int passed(int mark) { return mark >= 40; }
int main(void) {
  assert(!passed(0));
  assert(!passed(39));
  assert(passed(40));
  assert(passed(100));
  puts("Endpoint checks passed");
  return 0;
}

Expected output

Endpoint checks passed
Run in C compiler →

Recognize the important boundaries

Tests cover the cases you execute; they do not prove all inputs safe. Compiler diagnostics and memory/undefined-behavior sanitizers provide complementary evidence.

Validate external input with ordinary control flow. An assertion may be disabled and must not be used as the only protection against an invalid index.

Check your understanding

Choose the answer, then compare the reasoning for every option.

Which single valid mark best detects using > 40 instead of >= 40?
Choose one answer. You can try again.

Answer and explanation

40

Correct. Only the exact threshold distinguishes those two conditions.

Use the idea independently

A useful test compares an observed result with an independently stated requirement. Normal cases, boundaries and valid failure paths expose different kinds of mistakes.

Continue with the practice-program index.

Language reference: WG14 C11 committee draft. These examples use the C17-compatible subset of the explained language features.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: C Input and Output: Read Bounded Text and Validate Numbers →