3

I have a set of inputs and their corresponding expected outputs for a function.

The test is currently written as follows:

TEST_CASE("test reverse_and_double")
{
    struct { string input, string expected_output } tests[] = {
        { "abcd", "dcba" },
        { "hello", "olleh" },
        //...
    };

    for(auto &t : tests) {
        string output = my_reverse(t.input);  // function under test
        REQUIRE(output.size() == t.expected_output.size())
        CHECK(std::equal(output.begin(), output.end(), t.expected_output.begin()));
        //... many lines of CHECK & REQUIRE here...
    }
}

Now, in theory, unit-tests should not have (complex) mechanisms in order to be readable. GENERATE() is the way Catch offers in order to use the same CHECKs for different inputs. So, I tried it and I removed the forloop:

TEST_CASE("test reverse_and_double")
{
    struct { string input, string expected_output } t = GENERATE(
        { "abcd", "dcba" },
        { "hello", "olleh" },
        //...
    );

    string output = my_reverse(t.input);  // function under test
    REQUIRE(output.size() == t.expected_output.size())
    CHECK(std::equal(output.begin(), output.end(), t.expected_output.begin()));
    //... many lines of CHECK & REQUIRE here...
    }
}

However, there still is a 'mechanism' buried in the struct of inputs vs expected outputs. Many people will have different ways of writing this. I suppose this is a common pattern in unit-tests. Does Catch offer a build-in construct to express such situations in a consistent way?

Grim Fandango
  • 2,296
  • 1
  • 19
  • 27
  • 3
    My unit test rule is: arrange, act, assert. No loops. No if/switch. What you are looking for is called a **parameterized test** (or closely related **data generators**), which means the test runner feeds data to the test which does the simple arrange, act, assert. That's Catch2 feature request [850](https://github.com/catchorg/Catch2/issues/850). I am a fan of Catch2, but I've moved to Doctest, wherein you can do [parameterized tests](https://github.com/doctest/doctest/blob/master/doc/markdown/parameterized-tests.md). – Eljay Apr 10 '23 at 14:43
  • 1
    Why not simply `REQUIRE(output == t.expected_output)`? – Some programmer dude Apr 10 '23 at 14:44
  • @Someprogrammerdude, you are right -- I just wanted to show that there are lots of CHECK/REQUIRE calls. Sorry! – Grim Fandango Apr 10 '23 at 15:36
  • 1
    You can provide MCVE using this: https://godbolt.org/z/8GEGTK6TM – Marek R Apr 10 '23 at 15:42

1 Answers1

1

Yes it is possible, but documentation lacks off good examples, in this case you need feed table<...> generator to GENERATE macro:

std::string my_reverse(std::string_view s)
{
    return {s.rbegin(), s.rend()};
}

TEST_CASE("test reverse_and_double")
{
    auto [input, expected_output] = GENERATE(table<std::string, std::string>({
        { "abcd", "dcba" },
        { "hello", "olleh" },
    }));

    CAPTURE(input);
    REQUIRE(my_reverse(input) == expected_output);
}

https://godbolt.org/z/GbrPxdbrb

I had problems understanding documentation and had to inspect unit test and other projects source code on github to fill gaps.

Note I used CAPTURE so it is clear which argument was used when test fails.

Marek R
  • 32,568
  • 6
  • 55
  • 140