Concepts / Testing and Validating Your Code

Testing and Validating Your Code

The purpose of independent testing is not to keep functions separate forever. It is to verify each piece first, so that later debugging can focus on how the pieces interact.

  • Programming

A Smaller Search Space

When a long program fails, the error could be in input validation, a calculation, output formatting, or the way several steps interact. If the program is one continuous block, you may need to trace through the entire sequence to discover which part went wrong. Dividing the program into functions creates smaller units that can be tested separately. This changes debugging from an open-ended search into a systematic process.

Key point

The purpose of independent testing is not to keep functions separate forever. It is to verify each piece first, so that later debugging can focus on how the pieces interact.

Testing One Function

Independent function testing follows a deliberate sequence. First, define the function. Next, choose known test inputs. Call the function with those inputs, observe the output, and compare that output with what you expected. If the actual output is wrong, the function being tested becomes the immediate focus of the investigation. If the output is correct, you can move to the next function with more confidence.

Define functionTest inputsKnown valuesCall functionObserve outputVerify resultExpected versus actualFix functionTest next functionthenuseproducecomparewrongcorrect
How does testing one function at a time help identify which part of a program contains the bug?
Best practice

Document both the expected output and the actual output. Test edge cases as well as ordinary inputs, and fix a discovered bug immediately rather than moving on while the result is still uncertain.

Why Modularity Helps

Testing a complete program at once leaves many possible causes for a failure. The error could originate in any function or in the way the functions interact. Testing each function independently eliminates the first category of possibilities: once a function has been tested and verified, it is less likely to be the source of a later integration failure. The remaining investigation is therefore focused on the interaction between functions.

Long programMany possible locationsTested functionsSmaller remaining searchInput validationFunction AVerifiedCalculationFunction BVerifiedOutput formattingFunction interactionRemaining focuspossible causepossible causepossible causetestedtestedremaining possibilityremaining possibility
What changes in the debugging process when a long program is divided into separate functions?

From Pieces to Program

Worked example

A Generated Testing Sequence

Imagine a program divided into three functions: one for input validation, one for calculation, and one for output formatting. How can you test the program systematically?

  1. 1
    Test input validation

    Choose known inputs and verify that the validation function produces the expected outputs. Record the expected and actual results.

  2. 2
    Test calculation

    Call the calculation function with suitable known inputs and compare its output with the expected result. Fix the function immediately if the result is wrong.

  3. 3
    Test output formatting

    Give the formatting function a known result and check whether its output matches the expectation. Include edge cases where appropriate.

  4. 4
    Integrate the functions

    Assemble the functions into the larger program only after the individual tests provide confidence in each piece.

  5. 5
    Investigate integration failures

    If the complete program now behaves incorrectly, focus first on how the functions interact rather than reopening every individual function as an equally likely suspect.

Result

Independent testing verifies the pieces first and makes an integration failure easier to narrow down.

Input validationTestedCalculationTestedOutput formattingTestedWorking programIntegratedpasses throughpasses resultassembles into
How do individually tested functions connect and combine to form the complete working program?

The sequence matters. Testing the complete program first gives you a failure but does not identify its location. Testing the functions separately creates a record of which pieces have already been checked. After integration, a new failure has a more limited set of possible explanations: the functions may interact incorrectly, or the integrated execution path may not behave as expected.

Mistakes That Hide Bugs

  • Common mistake

    Testing only the complete program

    What it looks like

    A final output is wrong, but no individual function has been tested.

    Why it fails

    The failure could come from any function or from their interaction, so the search space remains large.

    Do this instead

    Test each function with known inputs before assembling the complete program.

  • Common mistake

    Skipping edge cases

    What it looks like

    Only ordinary inputs are used during a function test.

    Why it fails

    A function may appear correct for ordinary inputs while behaving incorrectly at an edge case.

    Do this instead

    Include edge cases when choosing test inputs.

  • Common mistake

    Moving on after finding an incorrect result

    What it looks like

    An actual output differs from the expected output, but testing continues with the next function.

    Why it fails

    The uncorrected result makes later tests harder to interpret and weakens confidence in the assembled program.

    Do this instead

    Document the difference, fix the bug immediately, and then test again.

  • Common mistake

    Checking only the final result

    What it looks like

    A program produces an unexpected result, but the intermediate logical steps were never checked.

    Why it fails

    You cannot easily tell which step failed.

    Do this instead

    Verify the output of each function independently.

Practice the Workflow

EASY

A long program has three logical parts: input validation, calculation, and output formatting. The final output is incorrect. Describe the order in which you would test these parts and explain what information each test would give you.

Hints
  • Start with one function rather than the complete program.
  • For each function, choose known inputs and write down the expected output before observing the actual output.
  • If an individual function is correct but the integrated program fails, consider the interaction between functions.

Key Takeaways

  • Dividing a long program into functions makes it possible to test and debug smaller parts independently.
  • Independent testing consists of defining a function, choosing inputs, calling it, observing its output, and verifying the expected result.
  • Testing edge cases and recording expected versus actual outputs improves the quality of validation.
  • After individual functions are verified, integration bugs are more likely to involve interactions between functions rather than the functions themselves.
  • Fixing problems immediately creates a tight feedback loop and narrows the search for remaining bugs.

Related concepts

More concepts starting with T