Testing and debugging

From Tux
Jump to: navigation, search

Testing and debugging are two critical components involved in writing computer programs.

Debugging refers to the process of fixing errors in a program after it has been written. The two primary types of errors are:

  • Syntax errors that prevent the program from compiling
  • Logic errors that do not prevent compiling, but cause erroneous or unexpected output

Testing refers to the process by which a program is determined to be functioning properly and free from logic errors.

Debugging

Syntax errors

Syntax errors are typically the easiest to fix. Common instances of syntax errors would be:

  • Forgetting a semicolon at the end of a statement or a typo.
cout << "Hello!" << endl     // Error: no semicolon at the end
cout << "Welcome." << end;   // Error: should be endl instead of end
  • Passing a value of one type to a function that expects a value of another type.
int fcn(int x)
{
        return x + 5;
}

int main()
{
        int y = fcn("Hi");    // Error: function expects an int

        return 0;
}
  • Not including the appropriate header file when using an object or function from it.
#include <iostream>

int main()
{
        cout << pow(3.0, 2.0) << endl;  // Error: did not #include <cmath> and did not have using namespace std;

        return 0;
}

Syntax debugging

When a program will not compile because of syntax errors, the compiler typically does a good job of telling you exactly what line and column the error occurred in as well as the specific type of error. Note that if there are multiple syntax errors, the compiler may only catch some of them. As errors are fixed and the user attempts to compile again, the additional errors will be caught by the compiler and these must also be fixed.

See below for some compiler messages and how to interpret them:

#include <iostream>

using namespace std;

int main()
{
        cout << "Hello!" << endl
        cout << "Welcome." << end;
}
Compiler messages:
test.cpp: In function ‘int main()’:
test.cpp:8:2: error: expected ‘;’ before ‘cout’
  cout << "Welcome." << end;
  ^

Note that even though there are two errors, only the first is caught. To fix this error the semicolon needs to be placed before the code in line 8. Really where it should go is at the end of line 7. Upon fixing this error and compiling again, the following error occurs:

test.cpp: In function ‘int main()’:
test.cpp:8:24: error: ‘end’ was not declared in this scope
  cout << "Welcome." << end;
                        ^

Again, the compiler notices there is no such thing as end and points it out. Either there should be an identifier (i.e. a variable) called end or the programmer mistyped and meant endl. Upon fixing this error the program will compile.

Getting experience with syntax error messages from the compiler and fixing them is essential for a programmer.

Logic errors

Logic errors are typically the hardest to fix. Common logic errors arise from:

  • Loops being off by one
  • Using = instead of ==, or vice versa
  • Attempting to modify an element of an array or dereferenced pointer that is out of bounds -- this type of error usually results in a segmentation fault
  • Deep/infinite recursion, leading to a stack overflow which may or may not manifest itself as a segmentation fault

Two common ways of fixing logic errors are:

  • Adding output (e.g. cout) statements throughout the code to display where the code is currently executing and/or what the values of variables are
  • Using a debugger such as gdb

Testing

In Linux, you can use the `diff` command along with I/O redirection to test and debug your programs by comparing their actual output to the expected output. Here’s a step-by-step guide to help you get started.

For more information on Diff: https://linuxize.com/post/diff-command-in-linux/

For more information on I/O Redirection: https://faculty.cs.niu.edu/~mcmahon/CS241/Notes/Unix_Reference/io_redirection.html

Example Files

1. main.cpp

#include <iostream>
int main()
{
    int inum = 0;
    double dnum = 0.0; 
    std::cin >> inum >> dnum;
    std::cout << "HelloWORLD\n";
    std::cout << "UNLV\n";
    std::cout << inum << " " << dnum << std::endl;
    
    return 0;
}

2. input.txt

2616
3.14

3. output_expected.txt

Hello, World!
UNLV
26 3.14

Testing Steps

1. Compile the Program - To compile the main.cpp file, use the following command in your terminal:

g++ main.cpp

2. Run the Program with Input and Output Redirection - Execute the compiled program by redirecting input from `input.txt` and output to `output_actual.txt` with this command:

./a.out < input.txt > output_actual.txt

3. Compare Output Files - To compare the actual output with the expected output, use the diff command:

diff -y output_expected.txt output_actual.txt

This will display a side-by-side comparison of the two files, highlighting any differences.

Hello, World!                |    HelloWORLD
UNLV                              UNLV
26 3.14                      |    2616 3.14

In this output:

  • The left column shows the contents of `output_expected.txt`.
  • The right column shows the contents of `output_actual.txt`.
  • The vertical bar (`|`) indicates where the files differ.

By following these steps. you can effectively verify that your program produces the correct output and identify any discrepancies between the expected and actual results.