C++
Testing C++ solutions
We use googletest as a testing framework for C++ coding exercises. We also provide googlemock for more convenience methods to aid in testing.
Hello, World!
Before we go into details, let's see how you can use googletest to test the most basic C++ task: writing a function that returns "Hello, World!".
This is what a student might write:
#include <string>
std::string helloMessage() {
return "Hello, World!";
}
And this is an example of what you could write to evaluate the student's code:
#include "gtest/gtest.h"
std::string helloMessage();
namespace {
class TestHello : public ::testing::Test {
};
TEST_F(TestHello, Message) {
EXPECT_EQ("Hello, World!", helloMessage()) << "Your function should return: Hello, World!";
}
} // namespace
Let's examine the above evaluation code.
First, we must include the testing framework header, "gtest/gtest.h"
.
Then we can either declare the interface that the student needs to implement, or we can provide it in a header file as part of the initial student files, which we would include in the evaluation file. We chose to declare it for this example.
Although optional, it's a good practice to write your testing code inside a namespace.
::testing::Test
Then we need to define a suite of unit tests as a class that inherits from ::testing::Test
.
This groups together multiple unit tests and allows implementing common functionality that you want executed before and after each test, via the constructor and destructor respectively.
class TestSuite : public ::testing::Test {
protected:
TestSuite() {
// You can do set-up work for each test here.
}
virtual ~TestSuite() {
// You can do clean-up work that doesn't throw exceptions here.
}
// Objects declared here can be used by all tests from this test suite.
};
TEST_F
Inside the class that you defined you can now write multiple unit tests by calling the following macro:
TEST_F(ClassName, UnitTestName) {
/* Implementation. */
}
The unit tests that you define are the only functions that will be executed. You should not implement a main()
function, and neither should your students.
Assertions
Assertions are the statements that actually verify that the student's code behaves as expected. The most common pattern is to call student's methods and assert that their return value is the one that you expect. If it is not, then the test will fail and you have the opportunity to provide a custom message to the student for each failed assertion, so that she can improve.
These are the most common ones:
EXPECT_EQ(expectedValue, actualValue);
EXPECT_TRUE(condition);
EXPECT_FALSE(condition);
If any of the expectations above aren't met, the test is marked as failed and feedback is given to the student. But the test continues execution until the end. If you instead decide that it's not worth continuing the test, you can stop execution as soon as a failure is recorded by using these alternative assertion macros:
ASSERT_EQ(obj1, obj2);
ASSERT_TRUE(condition);
ASSERT_FALSE(condition);
A good practice is to provide a custom feedback message to the student for every failed assertion. You can do this by using the <<
operator on the assertion macros, just like you would use it with std::cout
.
There are more advanced assertion macros that allow you to test
Matchers (googlemock)
Google Mock is a complex framework that gives you more power when writing tests.
For testing simple student programs, you will not need to use mocks, or most of what this library provides. But there is one thing that you may want to use: matchers.
They make it super easy to assert various data types, like:
For example, with gmock's ASSERT_THAT
macro and testing::ElementsAreArray
function, the resulting message will indicate which specific array elements caused the test to fail.
To use Google Mock you just need to #include "gmock/gmock.h"
in your evaluation file.
Standard output
In the previous example we had the students return Hello, World!
, but actually a more common task is to have them print the same string to the standard output.
#include <iostream>
void say_hello() {
std::cout << "Hello, World!";
}
How do we test this specific function?
#include <string>
#include <utility>
#include "gtest/gtest.h"
#include "helpers/iohelper.h"
void say_hello();
namespace {
class TestHello : public ::testing::Test {
};
TEST_F(TestHello, Message) {
std::pair<std::string, std::string> output = CAPTURE_OUTPUT(say_hello());
std::string stdout = output.first;
EXPECT_EQ("Hello, World!", stdout) << "Your function should return: Hello, World!";
}
} // namespace
We implemented 2 macros that allow you to:
capture everything that students print to standard output and standard error
provide a string for standard input to read from
You can just include helpers/iohelper.h
to get access to them.
#include <string>
#include <utility>
#include "helpers/iohelper.h"
std::pair<std::string, std::string> CAPTURE_OUTPUT(statement);
bool INJECT_INPUT(const std::string &content, statement);
CAPTURE_OUTPUT
returns a pair of strings: the first one is the standard output and the second is the standard error.
statement
can be any C++ statement. You can call a function with arguments like this:
CAPTURE_OUTPUT(sum(2, 3));
or even with if
statements:
CAPTURE_OUTPUT(
if (true) {
say_hello();
}
);
or multiple statements separated by ;
:
std::string line1;
std::string line2;
INJECT_INPUT(std::string("abc\ndef"),
std::cin >> line1;
std::cin >> line2;
);
EXPECT_EQ("abc", line1);
EXPECT_EQ("def", line2);
Technical specs
gcc >= 5.4.0
C++14
Example 1: FizzBuzz
Given:
An integer number n
Your task is to:
Write a function that returns an array with the numbers from 1 to n with the following restrictions:
for multiples of 3 store "Fizz" instead of the number
for multiples of 5 store "Buzz" instead of the number
for numbers which are multiples of both 3 and 5 store "FizzBuzz"
Example:
fizzbuzz(15) == {
"1", "2", "Fizz", "4", "Buzz",
"Fizz", "7", "8", "Fizz", "Buzz",
"11", "Fizz", "13", "14", "FizzBuzz"
}
Here's how a student might solve the problem:
#include <algorithm>
#include <vector>
std::vector<std::string> fizzbuzz(int n) {
std::vector<int> range(n);
std::iota(range.begin(), range.end(), 1);
std::vector<std::string> values;
values.resize(range.size());
auto fizzbuzz = [](int i) -> std::string {
if ((i%15) == 0) return "FizzBuzz";
if ((i%5) == 0) return "Buzz";
if ((i%3) == 0) return "Fizz";
return std::to_string(i);
};
std::transform(range.begin(), range.end(), values.begin(), fizzbuzz);
return values;
}
Here's how you might test the student's solution:
#include "gmock/gmock.h"
#include "gtest/gtest.h"
std::vector<std::string> fizzbuzz(int);
namespace {
class TestFizzBuzz : public ::testing::Test {
};
TEST_F(TestFizzBuzz, One) {
std::vector<std::string> result = fizzbuzz(1);
ASSERT_THAT(result, testing::ElementsAre("1"));
}
TEST_F(TestFizzBuzz, Fifteen) {
std::vector<std::string> result = fizzbuzz(15);
ASSERT_THAT(result, testing::ElementsAreArray({
"1", "2", "Fizz", "4", "Buzz",
"Fizz", "7", "8", "Fizz", "Buzz",
"11", "Fizz", "13", "14", "FizzBuzz"
}));
}
} // namespace
Example 2: Title Case
Given:
A sentence with words separated by spaces
Your task is to:
Write a function that returns a copy of this sentence where all the words:
start with a capital letter
the rest of the letters are lower case
Note:
Your function should not modify the sentence given as argument.
Example:
normalize("This is SO MUCH FUN! ") == "This Is So Much Fun! "
Here's how a student might solve the problem:
#include <ctype.h>
#include <string>
int skip_whitespace(const std::string &sentence, size_t idx) {
while (idx < sentence.length() && sentence[idx] == ' ') {
idx += 1;
}
return idx;
}
int normalize_word(std::string &sentence, size_t idx) {
if (idx < sentence.length() && sentence[idx] != ' ') {
sentence[idx] = toupper(sentence[idx]);
idx += 1;
}
while (idx < sentence.length() && sentence[idx] != ' ') {
sentence[idx] = tolower(sentence[idx]);
idx += 1;
}
return idx;
}
std::string normalize(const std::string &sentence) {
std::string copy = sentence;
size_t idx = 0;
while (idx < copy.length()) {
idx = skip_whitespace(copy, idx);
idx = normalize_word(copy, idx);
}
return copy;
}
Here's how you might test the student's solution:
#include <string>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
std::string normalize(const std::string &);
namespace {
class TestNormalize : public ::testing::Test {
};
TEST_F(TestNormalize, Character) {
EXPECT_EQ("A", normalize("a")) << "Your function should convert a single character.";
EXPECT_EQ("A", normalize("A")) << "Your function should convert a single character.";
}
TEST_F(TestNormalize, NoMutate) {
std::string sentence = "a";
normalize(sentence);
EXPECT_EQ("a", sentence) << "Your function should not mutate the argument.";
}
TEST_F(TestNormalize, Word) {
EXPECT_EQ("Abc", normalize("abc")) << "Your function should convert a single word.";
EXPECT_EQ("Abc", normalize("ABC")) << "Your function should convert a single word.";
EXPECT_EQ("Abc", normalize("aBC")) << "Your function should convert a single word.";
EXPECT_EQ("Abc", normalize("Abc")) << "Your function should convert a single word.";
}
TEST_F(TestNormalize, Sentence) {
EXPECT_EQ("Abc Def Ghi", normalize("abc def ghi")) << "Your function should convert a sentence.";
}
TEST_F(TestNormalize, MultipleWhitespace) {
EXPECT_EQ(" Abc Def Ghi ", normalize(" abc def ghi ")) << "Your function should keep multiple whitespaces.";
}
} // namespace
Last updated