auto square_add = [] (auto a, auto b) {
return a * a + b;
};
int is[] = { 1, 2, 3 };
auto q = std::accumulate(std::begin(is), std::end(is), 0, square_add);Testing challenges?
template <typename callback_t>
struct tokenizer1 {
callback_t& callback_;
tokenizer1(callback_t& callback) : callback_{callback} {}
void operator()(std::string_view input) const {
const char ws[] = " \t\n";
size_t a{}, b{};
do {
a = input.find_first_not_of(ws, b);
if (a == input.npos) return;
b = input.find_first_of(ws, a);
auto tok = input.substr(a, b == input.npos? b : b - a);
// call callback here!
callback_.string_token(tok);
} while (b != input.npos);
}
};BOOST_AUTO_TEST_CASE(tokenizer_test) {
/* what type goes here?? */ mock_callback;
tokenizer1 test_me{mock_callback};
test_me("hello");
/* how do we check that the callback is called? */
}One way to solve this problem is to write a mock object for each test case. So for example:
BOOST_AUTO_TEST_CASE(tokenizer_test_manual_mock) {
struct mock_callback {
int calls = 0;
void string_token(std::string_view seen) {
++calls;
BOOST_TEST(seen == "hello");
}
} callback;
tokenizer1 tok {callback};
tok("hello");
BOOST_TEST(callback.calls == 1);
}Mocking frameworks exist to make mocking easier. Google Mock is one such framework, and here’s how you would solve the problem using that:
struct tokenizer1_test : ::testing::Test {
struct mock_callback {
MOCK_METHOD1(string_token, void(std::string_view));
};
mock_callback callback_;
tokenizer1<mock_callback> tokenizer_{callback_};
};
TEST_F(tokenizer1_test, hello) {
EXPECT_CALL(callback_, string_token("hello"sv)).Times(1);
tokenizer_("world");
}[ RUN ] tokenizer1_test.hello
unknown file: Failure
Unexpected mock function call - returning directly.
Function call: string_token({ 'w' (119, 0x77), 'o' (111, 0x6F), 'r' (114, 0x72), 'l' (108, 0x6C), 'd' (100, 0x64) })
Google Mock tried the following 1 expectation, but it didn't match:
../tokenizer1_gtest.cpp:16: EXPECT_CALL(callback_, string_token("hello"sv))...
Expected arg #0: is equal to { 'h' (104, 0x68), 'e' (101, 0x65), 'l' (108, 0x6C), 'l' (108, 0x6C), 'o' (111, 0x6F) }
Actual: { 'w' (119, 0x77), 'o' (111, 0x6F), 'r' (114, 0x72), 'l' (108, 0x6C), 'd' (100, 0x64) }
Expected: to be called once
Actual: never called - unsatisfied and active
../tokenizer1_gtest.cpp:16: Failure
Actual function call count doesn't match EXPECT_CALL(callback_, string_token("hello"sv))...
Expected: to be called once
Actual: never called - unsatisfied and active
[ FAILED ] tokenizer1_test.hello (0 ms)
struct tokenizer1_fixture {
struct mock_callback {
std::function<void(std::string_view)> string_token;
};
mock_callback callback_;
tokenizer1<mock_callback> tokenizer_{callback_};
};
BOOST_FIXTURE_TEST_CASE(hello_counted, tokenizer1_fixture) {
int calls = 0;
callback_.string_token = [&] (std::string_view seen) {
++calls;
BOOST_TEST(seen == "hello");
};
tokenizer_(" hello ");
BOOST_TEST(calls == 1);
}No overloading!
struct mock_callback {
std::function<void(std::string_view)> token_string_view_;
std::function<void(int)> token_int_;
void token(std::string_view s) { token_string_view_(s); }
void token(int i) { token_int_(i); }
};No overriding!
struct callback_interface {
virtual void string_token(std::string_view) = 0;
};
struct mock_callback : callback_interface {
std::function<void(std::string_view)> string_token_fn_;
void string_token(std::string_view arg) override { string_token_fn_(arg); }
};Duplication of function signatures
struct {
std::function<void(std::string_view, int, double) foo;
} callback;
callback.foo = [&] (std::string_view s, int i, double d) { /* */ };template <typename decorator_t, typename callable_t>
struct decorated_callable {
decorator_t decorator_;
callable_t callable_;
template <typename ... args_t>
auto operator() (args_t&& ... args) {
decorator_(std::forward<args_t>(args)...);
return callable_(std::forward<args_t>(args)...);
}
};// this code is flawed - for exposition only
struct call_count_checker {
int calls_;
int expected_;
template <typename ... args_t>
void operator() (args_t&&...) {
++calls_;
}
~call_count_checker() {
BOOST_TEST(calls_ == expected_);
}
};template <typename Fn>
auto expect_calls(int expected, Fn fn) {
return decorated_callable<call_count_checker, Fn>{{0, expected}, fn};
}
BOOST_AUTO_TEST_CASE(hello_expect_calls) {
callback_.string_token = expect_calls(1, [] (std::string_view seen) {
BOOST_TEST(seen == "hello");
});
tokenizer_("hello");
}using source_location = std::experimental::source_location;
// nested class within call_count_checker
struct state {
state(source_location&& location, unsigned expected)
: location_{std::move(location)}, expected_{expected}
{}
~state() {
BOOST_TEST(expected_ == calls_,
"Function defined at " << location_.file_name() << ':' << location_.line()
<< " expected " << *expected_ << " calls, " << calls_ << " seen");
}
}
const source_location location_;
const unsigned expected_;
unsigned calls_ {};
};class call_count_checker {
struct state { /* ... */ };
std::shared_ptr<state> state_;
public:
call_count_checker(unsigned expected, source_location&& location
= source_location::current())
: state_{std::make_shared<state>(std::move(location), expected)}
{}
call_count_checker(const call_count_checker&) = default;
call_count_checker(call_count_checker&&) = default;
template <typename ... Args>
void operator() (Args&& ...) const {
state_->calls_ += 1;
}
};BOOST_AUTO_TEST_CASE(hello_expect_calls_fail, *boost::unit_test::expected_failures(1)) {
callback_.string_token = expect_calls(1, [] (std::string_view) {});
tokenizer_("");
}../mocking.hpp(75): error: in "tokenizer1_test/hello_expect_calls_fail": Function defined at ../tokenizer1_test2.cpp:43 expected 1 calls, 0 seen
BOOST_AUTO_TEST_CASE(hello_world_1) {
auto expect_world = expect_calls(1, [&] (std::string_view seen) {
BOOST_TEST(seen == "world");
});
auto expect_hello = expect_calls(1, [&] (std::string_view seen) {
BOOST_TEST(seen == "hello");
callback_.string_token = expect_world;
});
callback_.string_token = expect_hello;
tokenizer_("hello world");
}BOOST_AUTO_TEST_CASE(hello_world_2) {
const char* exp_toks[] = { "hello", "world" };
callback_.string_token = expect_calls(2,
[exp=exp_toks] (std::string_view seen) mutable {
BOOST_TEST(seen == *exp++);
});
tokenizer_("hello world");
}/// calls callback with each unique token
template <typename callback_t>
struct unique_tokenizer {
callback_t& callback_;
void operator()(std::string_view input) const {
struct {
std::unordered_set<std::string_view> tokens_;
void string_token(std::string_view word) {
tokens_.insert(word);
}
} uniqifier;
tokenizer1 tok {uniqifier};
tok(input);
for (const auto& t : uniqifier.counts_) {
callback_.string_token(t);
}
}
};BOOST_AUTO_TEST_CASE(hello_world) {
std::set expected { "hello"sv, "world"sv };
callback_.string_token = expect_calls(2, [&] (std::string_view tok) {
BOOST_TEST(expected.erase(tok) == 1, word << " not expected");
});
tokenizer_("hello world");
}template <typename callback_t>
struct tokenizer2 {
callback_t& callback_;
tokenizer2(callback_t& c) : callback_{c} {}
void operator()(std::string_view input) const {
const char ws[] = " \t\n";
size_t a{}, b{};
do {
a = input.find_first_not_of(ws, b);
if (a == input.npos) return;
b = input.find_first_of(ws, a);
auto tok = input.substr(a, b == input.npos? b : b - a);
if (int i; std::from_chars(tok.begin(), tok.end(), i).ec == std::errc{}) {
callback_.int_token(i);
} else {
callback_.string_token(tok);
}
} while (b != input.npos);
}
};BOOST_AUTO_TEST_CASE(hello_123) {
callback_.string_token = expect_calls(1, [] (std::string_view seen) {
BOOST_TEST(seen == "hello");
});
callback_.int_token = expect_calls(1, [] (int seen) {
BOOST_TEST(seen == 123);
});
tokenizer_("hello 123");
}(Possible diagram goes here)
struct state {
state(source_location&& location, std::optional<unsigned> expected)
: location_{std::move(location)}
, expected_{expected}
{}
~state() {
if (expected_) {
BOOST_TEST(*expected_ == calls_, "Function defined at "
<< location_.file_name() << ':' << location_.line()
<< " expected " << *expected_ << " calls, " << calls_ << " seen");
}
}
const source_location location_;
const std::optional<unsigned> expected_;
unsigned calls_ {};
};template <typename>
struct counted_function;
template <typename Ret, typename ... Args>
struct counted_function<Ret(Args...)>
: decorated_callable<call_count_checker, std::function<Ret(Args...)>>
{
using base_t = decorated_callable<call_count_checker, std::function<Ret(Args...)>>;
using base_t::decorated_callable;
using base_t::operator=;
using base_t::decorator;
auto current_count() const { return decorator().current_count(); }
};struct tokenizer2_fixture {
struct mock_callback {
counted_function<void(std::string_view)> string_token;
counted_function<void(int)> int_token;
};
mock_callback callback_;
tokenizer2<mock_callback> tokenizer_{callback_};
};BOOST_AUTO_TEST_CASE(hello_123_ordered) {
callback_.string_token = expect_calls(1, [] (std::string_view seen) {
BOOST_TEST(seen == "hello");
});
callback_.int_token = expect_calls(1, [&] (int seen) {
BOOST_TEST(callback_.string_token.current_count() == 1);
BOOST_TEST(seen == 123);
});
tokenizer_("hello 123");
}BOOST_AUTO_TEST_CASE(hello_123_variant) {
std::deque<std::variant<std::string_view, int>> exp {{ "hello"sv, 123 }};
auto check_token = expect_calls(size(exp), [&] (auto seen) {
auto* next_exp = std::get_if<decltype(seen)>(&exp.front());
BOOST_REQUIRE(next_exp);
BOOST_TEST(*next_exp == seen);
exp.pop_front();
});
callback_.int_token = check_token;
callback_.string_token = check_token;
tokenizer_("hello 123");
}Come talk to me about
- Overloaded functions
- Mocking out the system clock
- …