views:

173

answers:

5

How would you unit test do_int_to_string_conversion?

#include <string>
#include <iostream>

void do_int_to_string_conversion(int i, std::string& s) {
    switch(i) {
    case 1:
        s="1";
        break;
    case 2:
        s="2";
        break;
    default:
        s ="Nix";
    }
}

int main(int argc, char** argv){
    std::string little_s;

    do_int_to_string_conversion(1, little_s);
    do_int_to_string_conversion(2, little_s);
    do_int_to_string_conversion(3, little_s);

}
A: 

You can use something like Expect to pass it some input and verify that its output is what it should be.

WhirlWind
+8  A: 

I assume this is just an example. Why can't you assert on the value of little_s after each call?

do_int_to_string_conversion(1, little_s);
assert_are_equal("1", little_s);
Matt Greer
Excellent idea.
David
+3  A: 

If you really need to ensure that the output has been written, you need to break your dependency on std::cout and use another std::ostream during tests.

This might be as simple as a global variable:

#if PRODUCTION
std::ostream my_output = std::cout;
#else
std::ostream my_output = std::ostringstream;
#endif

void setup()
{
    my_output = std::ostringstream;
}

void print_hello()
{
    my_output << "hello";
}

void test_hello_was_printed()
{
    print_hello();
    ASSERT("hello" == my_output.str());
}

Or something similar to that effect.

Mark Rushakoff
This is also a good idea and one I didn't consider. The right to std::cout is superfluous [my fault for including it].
David
+3  A: 

I'd change do_int_to_string_conversion so that it does just one thing (convert the in to a string).

void do_int_to_string_conversion(int i, std::string& s) {
    switch(i) { ... }
}

This has no side effects, so you can write a simple unit test that verifies the output (s).

If I needed a function that printed the result of the conversion, I'd put that in a separate function, and I'd parameterize the output stream.

void output_int(int i, ostream &stream) {
    std::string s;
    do_int_to_string_conversion(i, s);
    stream << s;
}

To unit test that, I'd pass in a std::stringstream object and check the result.

Adrian McCarthy
@Adrian: +1 -- you beat to it.
Jerry Coffin
+10  A: 

Instead of worrying about how to test the function as it stands, I'd redesign the function to work a bit more sensibly, and test the re-designed version instead.

Right now, the function seems to have three separate (and only slightly related) responsibilities: do a conversion, modify an externally supplied string, and write some data to a stream. The stream to which it writes (std::cout) is also hard-coded -- a problem waiting to happen (e.g., conversion to a GUI environment is likely to be non-trivial).

I'd start by 1) splitting it up into logical functions, and 2) supplying the stream as a parameter.

std::string convert_int(int val) {
    switch (val) { 
       case 1: return "1";
       case 2: return "2";
       default: return "Nix";
   }
}

std::ostream &write_string(std::ostream &os, std::string const &s) { 
    return os << s;
}

I haven't included anything to (specifically) modify an externally supplied string -- obviously you can assign the return value from convert_int as you see fit, and the value of the string that was passed in wasn't being used anyway.

Truthfully, write_string is a good candidate to be eliminated entirely, but since you had that basic kind of capability, we'll retain it for the moment. Testing these is relatively simple -- for convert_int, we look at the string it returns, and compare to what we expect. For write_string, we can pass a stringstream instead of a normal ostream -- then we can use .str() to get that result as a string, and (again) compare to what we expect.

Jerry Coffin
The string in this case is a proxy for something bigger, and external to the stuff that I am working on. You asked in the comments of the last question that I asked that I include a small, compilable representative example of the problem. This is what this code snippet is.
David
@David: okay, that's not really a major problem. For that matter, modifying the object (of whatever type) that was passed by reference isn't necessarily a problem. You still want to design the function to do *one* thing though. If it modifies an object, create a "clean" object, have it do its modification, then check the result -- though if its primary purpose is to modify an object, you might want to think about whether it should be a member of that object.
Jerry Coffin
I asked this question to another friend of mine, and he had a similar response to you guys. Including the write to std::cout was bad on my part because it confused things. Still working on my question-asking-skills. The string in this case is a proxy for big-memory-mapped-data-structure that lives at the interface with what we are working on and pile of old fortran. The int indexes the list of possible things to write to that location.
David
@David: okay, that makes the question considerably more understandable. If you don't already have a class to act as a front-end for the memory-mapped structure, I'd start by creating one. Then create a mock-object with the same interface, but easy access to results of operations, just for testing.
Jerry Coffin
@Jerry: You said, "If you don't already have a class to act as a front-end for the memory-mapped structure, I'd start by creating one." This is the subject of a bitter religious war at the moment.
David
A memory-mapped structure (MMS) is a naked object. It itself has no behavior. By creating a class that **has-a** relationship to MMS it will be easier to test. Instead of testing for specific types, you will be testing behavior. In your example you want to test that an integer is converted to its string/text representation. That is behavior.
Gutzofter
@David: If it's a subject of heated debate, perhaps it would make sense to ask another question more specifically about its pros and cons (possibly with a more detailed description of that part of the situation). I think that format will work better than a long string of comments, and make an interesting discussion easier for others to find.
Jerry Coffin