Software Testing
AS — Unit 1: Fundamentals of Computer Science
Purpose of Testing
Software testing is the process of executing a program with the deliberate intention of finding errors. Testing is a critical stage in the software development life cycle and is essential for producing reliable, high-quality software.
Why Testing is Important
- Identify defects before the software is released to users, reducing the cost of fixing errors later.
- Verify that the software meets the functional requirements specified during analysis and design.
- Validate that the software is fit for purpose and meets the needs of the end user.
- Improve quality and reliability, giving stakeholders confidence in the system.
- Prevent failures in live environments that could cause data loss, financial loss, or safety hazards.
- Ensure robustness – the software should handle unexpected inputs and situations gracefully without crashing.
Verification checks whether the software has been built correctly according to the specification (“Are we building the product right?”). Validation checks whether the software meets the actual needs of the user (“Are we building the right product?”).
Types of Testing
There are two main approaches to testing, distinguished by whether the tester can see the internal code.
Black-Box Testing
- The tester does not have access to the internal code or structure of the program.
- Testing is based entirely on the inputs and expected outputs defined in the specification.
- The program is treated as a “black box” – the tester only sees what goes in and what comes out.
- Also called functional testing because it tests whether the program functions correctly according to its specification.
When it is used:
- During acceptance testing by end users who do not know the code.
- When testing against the requirements specification.
- When testing external interfaces or APIs.
Advantages:
- No programming knowledge is required.
- Tests are based on user requirements, so they validate the system from the user’s perspective.
- Tester is unbiased – they do not know the code, so they are less likely to make assumptions.
Disadvantages:
- May miss errors in code paths that are not covered by the test cases.
- Cannot test internal logic or code quality.
- Difficult to design comprehensive test cases without knowing the code structure.
White-Box Testing
- The tester has full access to the internal code and structure of the program.
- Test cases are designed to exercise specific code paths, branches, and logic within the program.
- Also called structural testing or glass-box testing.
When it is used:
- During unit testing by the programmer who wrote the code.
- When trying to ensure all branches and paths through the code are tested.
- When looking for specific types of defects such as logic errors.
Advantages:
- Can ensure all code paths are tested (path coverage).
- Can identify dead code (code that is never executed).
- Can test internal logic and boundary conditions within the code.
Disadvantages:
- Requires programming knowledge and access to the source code.
- Tester may be biased – they may test the code the way they think it should work.
- Does not check whether the program meets user requirements (only that the code works as written).
Comparison
| Feature | Black-Box Testing | White-Box Testing |
|---|---|---|
| Code access | No | Yes |
| Based on | Specification/requirements | Internal code structure |
| Performed by | Testers / end users | Programmers / developers |
| Also known as | Functional testing | Structural / glass-box testing |
| Focus | What the program does | How the program does it |
| Tests paths through code | No | Yes |
Remember: Black box = can’t see inside = testing based on specification. White box = can see inside = testing based on code. A thorough testing strategy uses both approaches.
Levels of Testing
Testing is not a single activity but a series of tests performed at different stages of development. Each level builds on the previous one.
Unit Testing
- Tests individual components or modules of a program in isolation.
- Usually performed by the programmer who wrote the code.
- Each function, procedure, or module is tested independently with its own test data.
- Uses white-box testing techniques because the developer has access to the code.
- Goal: Verify that each unit of the software performs as designed.
Example: Testing a calculateVAT() function on its own with various input values to check it returns the correct results.
Integration Testing
- Tests how individual modules work together when they are combined.
- Checks that data is passed correctly between modules and that interfaces work as expected.
- May reveal issues with parameter passing, data formats, or timing.
- Goal: Expose faults in the interaction between integrated units.
Example: Testing that the calculateVAT() function works correctly when called by the generateInvoice() module and that the returned value is used correctly.
System Testing
- Tests the complete, integrated software system as a whole.
- Checks that the entire system meets all the specified requirements (functional and non-functional).
- Includes testing performance, security, usability, and compatibility.
- Goal: Evaluate the system’s compliance with its specified requirements.
Example: Testing the entire invoicing system end-to-end – from entering order details to generating and printing the final invoice.
Acceptance Testing
- The final level of testing, performed to determine whether the system is ready for release.
- Usually carried out by the end users or client, not the developers.
- Tests the system against the original user requirements and business needs.
- If the system passes acceptance testing, it is approved for deployment.
- Goal: Confirm that the system meets the business requirements and is acceptable for delivery.
Example: The client uses the invoicing system with real data for a trial period and confirms that it meets their needs.
Summary of Testing Levels
| Level | Who performs it | What is tested | When |
|---|---|---|---|
| Unit | Programmer | Individual modules/functions | During development |
| Integration | Development team | Modules working together | After unit testing |
| System | Testing team | Complete system | After integration testing |
| Acceptance | End user / client | System against user requirements | Before deployment |
Remember the order: Unit -> Integration -> System -> Acceptance. Think of it as building up from the smallest part (a single function) to the entire system being signed off by the client. Each level of testing can only begin once the previous level is complete.
Alpha and Beta Testing
These are two stages of testing that occur before a product is released to the general public.
Alpha Testing
- Performed in-house by the development team or a dedicated testing team within the organisation.
- Takes place in a controlled environment (the developer’s site).
- Aims to find bugs and issues before the software is given to external users.
- Both white-box and black-box techniques may be used.
- The software may still be incomplete or unstable at this stage.
Beta Testing
- Performed by a selected group of external users (beta testers) outside the development organisation.
- Takes place in the user’s own environment with real hardware and real usage patterns.
- Users report bugs, provide feedback on usability, and suggest improvements.
- The software is feature-complete but may still contain bugs.
- Feedback from beta testing is used to make final fixes and improvements before the official release.
Comparison
| Feature | Alpha Testing | Beta Testing |
|---|---|---|
| Who | Internal staff / developers | External users / public volunteers |
| Where | Developer’s site (controlled environment) | User’s site (real-world environment) |
| When | Before beta testing | After alpha testing, before final release |
| Purpose | Find bugs in a controlled setting | Find bugs in real-world conditions, gather user feedback |
| Software state | May be incomplete | Feature-complete but may have bugs |
Alpha testing is testing done in-house before release. Beta testing is testing done by external users in a real-world environment before the final release.
Test Data
To test a program thoroughly, you need to select appropriate test data. Test data should be carefully chosen to check all possible paths through the program and to identify potential errors. There are three main categories.
Normal (Valid) Data
- Data that is within the expected range and of the correct format.
- The system should accept this data and process it correctly.
- Tests that the program works under normal operating conditions.
Boundary (Extreme) Data
- Data that is at the very edges of the valid range.
- This is where errors most commonly occur (off-by-one errors).
- You should test values at the boundary itself and values just inside and just outside the boundary.
Erroneous (Invalid) Data
- Data that is outside the expected range, of the wrong type, or otherwise invalid.
- The system should reject this data gracefully without crashing.
- Tests the robustness of input validation.
Example: Age Input (Valid Range 18–65)
| Test Data | Type | Expected Result | Reason |
|---|---|---|---|
| 25 | Normal | Accepted | Within valid range |
| 40 | Normal | Accepted | Within valid range |
| 18 | Boundary | Accepted | At lower boundary (minimum valid) |
| 17 | Boundary | Rejected | Just below lower boundary |
| 65 | Boundary | Accepted | At upper boundary (maximum valid) |
| 66 | Boundary | Rejected | Just above upper boundary |
| -5 | Erroneous | Rejected | Negative number, outside range |
| 200 | Erroneous | Rejected | Far outside valid range |
| “abc” | Erroneous | Rejected | Wrong data type (string, not integer) |
| 18.5 | Erroneous | Rejected | Real number, not integer |
| ”” (empty) | Erroneous | Rejected | No data entered |
When creating test data in the exam, always include at least one example of each type: normal, boundary, and erroneous. For boundary data, test the value at the boundary AND the values either side of it (e.g. for a range of 1–100, test 0, 1, 100, and 101). Some mark schemes consider boundary data that falls just outside the range as erroneous – include both to be safe.
Test Plans and Test Tables
A test plan is a document that describes the testing strategy for a piece of software. It includes a test table which lists every individual test to be performed.
Structure of a Test Table
A test table typically includes the following columns:
| Column | Purpose |
|---|---|
| Test Number | A unique identifier for each test |
| Description | What is being tested and why |
| Test Data | The specific input data to use |
| Test Data Type | Normal, boundary, or erroneous |
| Expected Result | What the program should do with this input |
| Actual Result | What the program actually did (filled in after testing) |
| Pass/Fail | Whether the actual result matched the expected result |
Example Test Table: Password Validation
The password must be 8–20 characters long and contain at least one digit.
| Test # | Description | Test Data | Type | Expected Result | Actual Result | Pass/Fail |
|---|---|---|---|---|---|---|
| 1 | Valid password with digit | “Hello123” | Normal | Accepted | ||
| 2 | Valid password, exactly 8 chars | “Pass1234” | Boundary | Accepted | ||
| 3 | Valid password, exactly 20 chars | “Abcdefghij1234567890” | Boundary | Accepted | ||
| 4 | Too short (7 chars) | “Pass123” | Boundary | Rejected - “Too short” | ||
| 5 | Too long (21 chars) | “Abcdefghij12345678901” | Boundary | Rejected - “Too long” | ||
| 6 | No digit included | “Password” | Erroneous | Rejected - “Must contain a digit” | ||
| 7 | Empty string | ”” | Erroneous | Rejected - “Too short” | ||
| 8 | Only digits | “12345678” | Normal | Accepted |
A test plan outlines the testing strategy and contains a test table that specifies each individual test case, including the test data, expected results, and actual results.
Debugging Techniques
Debugging is the process of finding and fixing errors (bugs) in a program. Several techniques are used to locate and diagnose errors.
Trace Tables
A trace table is a technique for manually tracking the values of variables as a program executes. You create a table with a column for each variable and a row for each step of execution.
Example program:
x = 3
y = 5
total = 0
for i in range(1, 4):
total = total + x
x = x + y
Dim x As Integer = 3
Dim y As Integer = 5
Dim total As Integer = 0
For i As Integer = 1 To 3
total = total + x
x = x + y
Next
Trace table:
| Step | i | x | y | total |
|---|---|---|---|---|
| Init | - | 3 | 5 | 0 |
| 1 | 1 | 3 | 5 | 3 |
| 2 | 1 | 8 | 5 | 3 |
| 3 | 2 | 8 | 5 | 11 |
| 4 | 2 | 13 | 5 | 11 |
| 5 | 3 | 13 | 5 | 24 |
| 6 | 3 | 18 | 5 | 24 |
Breakpoints
- A breakpoint is a marker set at a specific line in the code where execution will pause.
- When the program reaches a breakpoint, it stops, allowing the programmer to inspect the current state of all variables.
- The programmer can then step through the code line by line to observe how variables change.
- Available in most Integrated Development Environments (IDEs).
Watch Variables
- A watch allows the programmer to monitor the value of a specific variable during execution.
- The programmer selects variables to “watch”, and the IDE displays their current values in real-time as the program runs.
- Useful for tracking variables that are expected to change at certain points.
- Can also be set to break execution when a watched variable reaches a particular value.
Stepping
- Stepping means executing the program one line at a time, allowing the programmer to observe the effect of each statement.
- Step into – enters a subroutine call to step through it line by line.
- Step over – executes a subroutine call as a single step without entering it.
- Step out – runs the rest of the current subroutine and pauses at the calling line.
In the exam, you may be asked to complete a trace table for a given piece of code. Work through the code line by line, updating the table after each statement. Be especially careful with loops – make sure you track the loop counter correctly and know when the loop terminates.
Dry Running / Desk Checking
Dry running (also called desk checking) is the process of manually working through a program on paper, without using a computer, to check that it produces the correct results.
How to Dry Run a Program
- Write out the variable names as column headings in a trace table.
- Work through the code line by line, updating the variable values in the table.
- For each decision (IF statement), evaluate the condition and follow the correct branch.
- For each loop, track the loop counter and repeat until the exit condition is met.
- Compare the final output with the expected output.
Benefits of Dry Running
- Can identify logic errors before the program is even entered into a computer.
- Helps programmers understand the flow of execution through complex code.
- Useful during exams where you may not have access to a computer.
- Forces careful, systematic analysis of the algorithm.
Example: Dry Run
numbers = [4, 7, 2, 9, 1]
largest = numbers[0]
for i in range(1, len(numbers)):
if numbers[i] > largest:
largest = numbers[i]
print(largest)
Dim numbers() As Integer = {4, 7, 2, 9, 1}
Dim largest As Integer = numbers(0)
For i As Integer = 1 To numbers.Length - 1
If numbers(i) > largest Then
largest = numbers(i)
End If
Next
Console.WriteLine(largest)
Trace table:
| i | numbers(i) | numbers(i) > largest? | largest |
|---|---|---|---|
| - | - | - | 4 |
| 1 | 7 | 7 > 4? Yes | 7 |
| 2 | 2 | 2 > 7? No | 7 |
| 3 | 9 | 9 > 7? Yes | 9 |
| 4 | 1 | 1 > 9? No | 9 |
Output: 9
Dry running (desk checking) is manually working through a program on paper, line by line, to verify that it produces the correct output. A trace table is a tabular record of variable values at each step of execution.
Error Types
Errors in programs fall into three main categories. Understanding the differences is essential for effective debugging.
Syntax Errors
A syntax error occurs when the code violates the rules (grammar) of the programming language. The program will not compile or run until syntax errors are fixed.
# Syntax errors in Python
print("Hello" # Missing closing bracket
if x == 5 # Missing colon
prit("Five") # Misspelled keyword (not caught as syntax but as NameError)
for i in range(10) # Missing colon
' Syntax errors in VB.NET
Console.WriteLine("Hello" ' Missing closing bracket
If x = 5 ' Missing Then keyword
Console.WrteLine("Five") ' Misspelled method name
For i As Integer = 1 To 10 ' Missing Next at the end
Characteristics:
- Detected by the compiler/interpreter before the program runs.
- Usually easy to find because the error message indicates the line and type of error.
- The program cannot execute until all syntax errors are corrected.
Logic Errors
A logic error occurs when the program runs without crashing but produces incorrect results. The code is syntactically valid but the algorithm is wrong.
# Logic error: using + instead of *
def calculate_area(length, width):
return length + width # Should be length * width
# Logic error: wrong comparison operator
def is_adult(age):
return age > 18 # Should be age >= 18 (18-year-olds are adults)
# Logic error: off-by-one error in loop
total = 0
for i in range(1, 10): # Misses 10; should be range(1, 11)
total = total + i
' Logic error: using + instead of *
Function CalculateArea(length As Double, width As Double) As Double
Return length + width ' Should be length * width
End Function
' Logic error: wrong comparison operator
Function IsAdult(age As Integer) As Boolean
Return age > 18 ' Should be age >= 18
End Function
' Logic error: off-by-one error in loop
Dim total As Integer = 0
For i As Integer = 1 To 9 ' Misses 10; should be 1 To 10
total = total + i
Next
Characteristics:
- NOT detected by the compiler/interpreter – the program runs without error messages.
- The program produces incorrect or unexpected output.
- The hardest type of error to find because there are no error messages pointing to the problem.
- Found through testing (using test data and comparing actual output to expected output) or dry running.
Runtime Errors
A runtime error occurs during program execution, causing the program to crash or terminate abnormally. The code is syntactically correct but encounters a problem that cannot be handled during execution.
# Runtime errors in Python
numbers = [1, 2, 3]
print(numbers[5]) # IndexError: list index out of range
result = 10 / 0 # ZeroDivisionError: division by zero
age = int("hello") # ValueError: invalid literal for int()
file = open("nonexistent.txt") # FileNotFoundError
' Runtime errors in VB.NET
Dim numbers() As Integer = {1, 2, 3}
Console.WriteLine(numbers(5)) ' IndexOutOfRangeException
Dim result As Integer = 10 \ 0 ' DivideByZeroException
Dim age As Integer = CInt("hello") ' InvalidCastException / FormatException
Dim file As String = IO.File.ReadAllText("nonexistent.txt") ' FileNotFoundException
Characteristics:
- NOT detected at compile time – the program starts running but crashes during execution.
- An error message is displayed indicating the type and location of the error.
- Often caused by unexpected input, missing resources, or edge cases.
- Can be prevented using input validation, error handling (try/catch), and defensive programming.
Comparison of Error Types
| Feature | Syntax Error | Logic Error | Runtime Error |
|---|---|---|---|
| When detected | Compile/interpret time | During testing or use | During execution |
| Program runs? | No | Yes | Yes, but crashes |
| Error message? | Yes (from compiler) | No | Yes (when it crashes) |
| Difficulty to find | Easy | Hard | Medium |
| Example | Missing bracket | Wrong formula | Division by zero |
| How to fix | Read error message, fix syntax | Test with data, use trace table | Add validation, use try/catch |
The exam often gives a piece of code and asks you to identify the type of error. Remember: if the code would not run at all, it is a syntax error. If it runs but gives the wrong answer, it is a logic error. If it runs but crashes at some point during execution, it is a runtime error. Be prepared to identify the specific error and explain how to fix it.