Example of How to Test Your codeΒΆ

The following has some basic idea for testing simple functions. For more information about testing, you may want to read about test driven development, unit testing, white-box testing, blackbox testing, and regression testing.

// testing.cpp

//
// Suggestions for testing code:
//
// - Write pre and post conditions first, if you can. This helps make it clear
//   what your function is supposed to do.
//
// - If you aren't sure what the pre and post conditions are, write a few
//   simple tests using assert. Actually write them down in code inside a
//   function so that they will be checked automatically.
//
//   The process of figuring out examples is often quite useful, and can help
//   you write the pre and post conditions.
//
// - Before you write any significant code for your function's body, write
//   a few more good tests. Try to test some common cases that you expect
//   to use your function for.
//
// - In addition to testing a few common cases, "extreme" values often make
//   good tests, e.g.
//
//   - numbers: -1, 0, 1, min value, max value, etc.
//   - strings: empty string, single character string, all characters the
//              same, important characters at the being/end of the string,
//              etc. ...
//
// - Another handy rule of thumb for writing test cases is to include tests
//   that causes your code to execute every line at least once. Obviously, you
//   can't write such tests until you've implemented the body of your
//   function, so these sorts of tests would need to be added after the
//   function is implemented.
//
// - Try to have a reason for every test that explains why you included it. If
//   two or more tests are there for the same reason, you can probably get rid
//   of some.
//
// - Finally, implement your function! When it's done, run your tests cases
//   on it, and fix any errors.
//
// - Every time you make any change to your function, it's possible that you
//   could have unintentionally introduced an error. Thus, you re-run your
//   test cases after *every* change you make to your code.
//

#include <iostream>
#include "cmpt_error.h"
#include <cassert>
#include <string>

using namespace std;

// Pre-condition:
//    none
// Post-condition:
//    Returns i such that s[i] == c; or, if c is nowhere
//    in s, returns -1.
int find_char(char c, const string& s) {
    for(int i = 0; i < s.size(); ++i) {
        if (c == s[i]) {
            return i;
        }
    }
    return -1;
}

void find_char_test() {
    assert(find_char('a', "") == -1);
    assert(find_char('a', "a") == 0);
    assert(find_char('b', "a") == -1);
    assert(find_char('a', "ba") == 1);
    assert(find_char('b', "ba") == 0);
    assert(find_char('a', "aa") == 0);
    assert(find_char('a', "123456") == -1);
    cout << "... find_char_test passed\n";
}

// Pre-condition:
//    from and to are non-empty
//    from and to are the same length
//    from has no duplicate characters
//    to has no duplicate characters
// Post-condition:
//    Returns a new string that is the same as target,
//    except every character in from that appears in target
//    is replaced with the corresponding character in to.
string translate(const string& target,
                 const string& from, const string& to) {
    string result(target);
    for(int i = 0; i < target.size(); ++i) {
        int loc = find_char(result[i], from);
        if (loc != -1) {
            result[i] = to[loc];
        }
    }
    return result;
}

void translate_test() {
    assert(translate("abba", "a", "b") == "bbbb");
    assert(translate("abba", "ab", "ba") == "baab");
    assert(translate("x", "ab", "ba") == "x");
    assert(translate("", "4", "7") == "");
    assert(translate("abc123", "31ca3b", "31ca3b") == "abc123");
    assert(translate("abba", "a", "a") == "abba");
    assert(translate("this is a test", " ", "_") == "this_is_a_test");

    cout << "... translate_test passed\n";
}

//
// First compiling version of translate; fails first test!
//
// string translate(const string& target,
//                  const string& from, const string& to) {
//     string result(target);
//     for(int i = 0; i < target.size(); ++i) {
//         int loc = find_char(result[i], from);
//         if (loc != -1) {
//             result[i] = to[i];  // error: to[i] should be to[loc]
//         }
//     }
//     return result;
// }
//

// Pre-condition:
//    from and to are non-empty
//    from and to are the same length
//    from has no duplicate characters
//    to has no duplicate characters
// Post-condition:
//    Returns a new string that is the same as target,
//    except every character in from that appears in target
//    is replaced with the corresponding character in to.
string translate2(const string& target,
                  const string& from, const string& to) {
    string result(target);
    for(int i = 0; i < target.size(); ++i) {
        int loc = from.find(result[i]);
        if (loc != string::npos) {
            result[i] = to[loc];
        }
    }
    return result;
}

void translate2_test() {
    assert(translate2("abba", "a", "b") == "bbbb");
    assert(translate2("abba", "ab", "ba") == "baab");
    assert(translate2("x", "ab", "ba") == "x");
    assert(translate2("", "4", "7") == "");
    assert(translate2("abc123", "31ca3b", "31ca3b") == "abc123");
    assert(translate2("abba", "a", "a") == "abba");
    assert(translate2("this is a test", " ", "_") == "this_is_a_test");

    cout << "... translate2_test passed\n";
}

//
// There's an error here: from.find returns string::npos when it doens't find
// the character it's searching for. But this code checks to see if it is -1.
// The test cases we borrowed from translate_test don't catch this.
//
// But it seems like they should: there are a number of tests where a a
// character in the target string doesn't appear in the from string.
//
// These two lines of codes help show what's going on:
//
//    cout << "string::npos = " << string::npos << "\n";
//    cout << (string::npos == -1) << "\n";
//
// On my computer this prints:
//
//    string::npos = 4294967295
//    1
//
// The 1 means that the expression string::npos == -1 is true, i.e.
// string::npos would seem to have the value -1. But is -1 not printed in the
// previous line? The answer is that find returns a value of type size_t, and
// size_t is an unsigned int type. When -1 is cast to a size_t, it is
// apparently equal to 4294967295.
//
// There doesn't appear to be anything in the C++ documentation that
// guarantess string::npos == -1 is true, so we may just have gotten lucky.
// It's possible that other implementations of the string class use a
// different value for string::npos, and so our code will not work correctly
// there. So, to make it more robust, we should replace -1 with string::npos
// in translate2.
//
// string translate2(const string& target,
//                   const string& from, const string& to) {
//     string result(target);
//     for(int i = 0; i < target.size(); ++i) {
//         int loc = from.find(result[i]);
//         if (loc != -1) {
//             result[i] = to[loc];
//         }
//     }
//     return result;
// }

int main() {
    find_char_test();
    translate_test();
    translate2_test();

    cout << "All tests passed.\n";
}