trainings/CreatingReliableSoftwareCpp/Presentation/testing_gtest.md

15 KiB

GTEST

  • TEST(...) Create single test
  • TEST_F(...) Use fixtures for testing
  • TEST_P(...) Use package of parameters for all tests
  • EXPECT_* Check condition
  • ASSERT_* Check condition and stop test if fails.
  • EXPECT_PRED* Allow to create own predicate, and other format of display errors
  • testing::FloatLE and testing::DoubleLE can be use for EXPECT_PRED_FORMAT* for floating-point comparison
  • GTEST_SKIP() can be use for skipping the test
  • EXPECT_EXIT can test signall which end program, like: SIGKILL
  • EXPECT_THROW will catch and check exceptions

TEST

To create a simple test we can use TEST macro

class Calculator {
public:
    int add(int lsh, int rhs) { return lhs + rhs; }
};

TEST(CalculatorTest, ShouldAddTwoValues) {
    Calculator calc;
    EXPECT_EQ(calc.add(10, 20), 10 + 20);
}

TEST_F

If we need to set up some structures before creating a test, we shouldn't do this every test, but instead create a test fixture that sets everything up during construction. It also provides useful methods which we can use in every test. This fixture is created first, then a test starts.

class DownloaderTest : public testing::Test {
public:
    DownloaderTest() {
        _connection = std::make_unique<TcpConnection>();
        _connection->set_certificates(_certificateMock);
        _downloader = std::make_unqiue<UrlDownloader>(_connection.get());
    }

    CertificateMock _certificateMock;
    std::unqiue_ptr<Connection> _connection;
    std::unique_ptr<Downloader> _downloader;
};

TEST_F(DownloaderTest, shouldDownloadImage) {
    EXPECT_CALL(_certificateMock, validate).WillOnce(Return(true));
    EXPECT_TRUE(_downloader.downloadImage("exampleImg"));
}

TEST_P (1)

Whenever we need to run the same test but with a different set of parameters we should use TEST_P

class Firewall {
public:
    virtual ~Firewall() = default;

    virtual bool block(const std::string& ipAddress) = 0;
};

class WhiteListFirewall : public Firewall {
public:
    void addToWhitelist(const std::string& ipAddress) {
        whiteList_.push_back(ipAddress);
    }

    bool block(const std::string& ipAddress) override {
        return std::find(cbegin(whiteList_), cend(whiteList_), ipAddress) == std::cend(whiteList_);
    }

private:
    std::vector<std::string> whiteList_;
};


TEST_P (2)

class TestFirewall : public testing::TestWithParam<std::pair<std::string, bool>> {
};

TEST_P(TestFirewall, ShouldFilterAdresses) {
    auto firewall = std::make_unique<WhiteListFirewall>();
    firewall->addToWhitelist("192.201.1.12");
    firewall->addToWhitelist("192.122.1.12");

    const auto& [address, isBlock] = GetParam();
    EXPECT_EQ(firewall->block(address), isBlock);
}

INSTANTIATE_TEST_SUITE_P(TestFirewallParameters,
                         TestFirewall,
                         testing::Values(
                             std::pair<std::string, bool>{"192.178.1.12", true},
                             std::pair<std::string, bool>{"192.144.1.12", true},
                             std::pair<std::string, bool>{"192.188.1.12", true},
                             std::pair<std::string, bool>{"192.201.1.12", false},
                             std::pair<std::string, bool>{"192.122.1.12", false}));

TEST_P (3)

[----------] 5 tests from TestFirewallParameters/TestFirewall
[ RUN      ] TestFirewallParameters/TestFirewall.ShouldFilterAdresses/0
[       OK ] TestFirewallParameters/TestFirewall.ShouldFilterAdresses/0 (0 ms)
[ RUN      ] TestFirewallParameters/TestFirewall.ShouldFilterAdresses/1
[       OK ] TestFirewallParameters/TestFirewall.ShouldFilterAdresses/1 (0 ms)
[ RUN      ] TestFirewallParameters/TestFirewall.ShouldFilterAdresses/2
[       OK ] TestFirewallParameters/TestFirewall.ShouldFilterAdresses/2 (0 ms)
[ RUN      ] TestFirewallParameters/TestFirewall.ShouldFilterAdresses/3
[       OK ] TestFirewallParameters/TestFirewall.ShouldFilterAdresses/3 (0 ms)
[ RUN      ] TestFirewallParameters/TestFirewall.ShouldFilterAdresses/4
[       OK ] TestFirewallParameters/TestFirewall.ShouldFilterAdresses/4 (0 ms)
[----------] 5 tests from TestFirewallParameters/TestFirewall (0 ms total)

TEST_P - inherit from other test

class BlackListFirewall : public Firewall {
public:
    void addToBlacklist(const std::string& ipAddress) {
        blackList_.push_back(ipAddress);
    }

    bool block(const std::string& ipAddress) override {
        return std::find(cbegin(blackList_), cend(blackList_), ipAddress) != std::cend(blackList_);
    }

private:
    std::vector<std::string> blackList_;
};

class BaseTestBlackListFirewall : public testing::Test {
public:
    void SetUp() override {
        firewall_ = std::make_unique<BlackListFirewall>();

        firewall_->addToBlacklist("192.201.1.12");
        firewall_->addToBlacklist("192.122.1.12");
        firewall_->addToBlacklist("192.178.1.12");
        firewall_->addToBlacklist("192.144.1.12");
        firewall_->addToBlacklist("192.188.1.12");
    }

    BlackListFirewall* firewall() const { return firewall_.get(); }

private:
    std::unique_ptr<BlackListFirewall> firewall_;
};

testing::WithParamInterface

class TestBlackListFirewall : public BaseTestBlackListFirewall,
                              public testing::WithParamInterface<std::string> {
};

TEST_P(TestBlackListFirewall, ShouldBlock) {
    EXPECT_TRUE(firewall()->block(GetParam()));
}

INSTANTIATE_TEST_SUITE_P(TestBlackListFirewallPackage,
                         TestBlackListFirewall,
                         testing::Values(
                             "192.178.1.12",
                             "192.144.1.12",
                             "192.188.1.12",
                             "192.201.1.12",
                             "192.122.1.12"));
[----------] 5 tests from TestBlackListFirewallPackage/TestBlackListFirewall
[ RUN      ] TestBlackListFirewallPackage/TestBlackListFirewall.ShouldBlock/0
[       OK ] TestBlackListFirewallPackage/TestBlackListFirewall.ShouldBlock/0 (0 ms)
[ RUN      ] TestBlackListFirewallPackage/TestBlackListFirewall.ShouldBlock/1
[       OK ] TestBlackListFirewallPackage/TestBlackListFirewall.ShouldBlock/1 (0 ms)
[ RUN      ] TestBlackListFirewallPackage/TestBlackListFirewall.ShouldBlock/2
[       OK ] TestBlackListFirewallPackage/TestBlackListFirewall.ShouldBlock/2 (0 ms)
[ RUN      ] TestBlackListFirewallPackage/TestBlackListFirewall.ShouldBlock/3
[       OK ] TestBlackListFirewallPackage/TestBlackListFirewall.ShouldBlock/3 (0 ms)
[ RUN      ] TestBlackListFirewallPackage/TestBlackListFirewall.ShouldBlock/4
[       OK ] TestBlackListFirewallPackage/TestBlackListFirewall.ShouldBlock/4 (0 ms)
[----------] 5 tests from TestBlackListFirewallPackage/TestBlackListFirewall (0 ms total)

EXPECT_PRED* - own error message

testing::AssertionResult isBlocked(Firewall* firewall, const std::string& addr, bool block) {
    if (firewall->block(addr) == block) {
        return testing::AssertionSuccess();
    }

    return testing::AssertionFailure() << addr << (block ? " should be blocked" : " shouldn't be blocked");
}

TEST_F(BaseTestBlackListFirewall, ShouldFilter) {
    EXPECT_TRUE(isBlocked(firewall(), "192.188.1.12", true));
    EXPECT_TRUE(isBlocked(firewall(), "192.188.1.12", false));
}
[----------] 1 test from BaseTestBlackListFirewall
[ RUN      ] BaseTestBlackListFirewall.ShouldFilter
/home/mateusz.adamski/Documents/repoGit/advanced_cpp/examples/gtest.cpp:101: Failure
Value of: isBlocked(firewall(), "192.188.1.12", false)
  Actual: false (192.188.1.12 shouldn't be blocked)
Expected: true
[  FAILED  ] BaseTestBlackListFirewall.ShouldFilter (0 ms)
[----------] 1 test from BaseTestBlackListFirewall (0 ms total)

EXPECT_PRED* - check string

for (const auto& str : {"There is a nauka!", "nauka.programowania.ma@gmail.com"}) {
    EXPECT_THAT(str, testing::MatchesRegex(
        "^([a-zA-Z0-9_\\-\\.]+)@([a-zA-Z0-9_\\-\\.]+)\\.([a-zA-Z]{2,5})$"));

    ASSERT_THAT(str, testing::HasSubstr("nauka"));
}
[ RUN      ] ExampleTest.ShouldFail
/home/mateusz.adamski/Documents/repoGit/advanced_cpp/examples/gtest.cpp:104: Failure
Value of: str
Expected: matches regular expression "^([a-zA-Z0-9_\\-\\.]+)@([a-zA-Z0-9_\\-\\.]+)\\.([a-zA-Z]{2,5})$"
  Actual: 0x5625cd2c080c pointing to "There is a nauka!" (of type char const*)
[  FAILED  ] ExampleTest.ShouldFail (0 ms)
[----------] 1 test from ExampleTest (0 ms total)

EXPECT_PRED* - comparison of floating point numbers

std::vector<std::pair<double, double>> vec{
    {43.45, 43.45}, {44.34321, 45.1234}, {1.12, 1.1199999}};

for (const auto& [lhs, rhs] : vec) {
    EXPECT_PRED_FORMAT2(testing::FloatLE, lhs, rhs);
    EXPECT_PRED_FORMAT2(testing::DoubleLE, lhs, rhs);
}
[ RUN      ] ExampleTest.ShouldFail
/home/mateusz.adamski/Documents/repoGit/advanced_cpp/examples/gtest.cpp:108: Failure
Expected: (lhs) <= (rhs)
  Actual: 1.1200000000000001 vs 1.1199999
[  FAILED  ] ExampleTest.ShouldFail (0 ms)
[----------] 1 test from ExampleTest (0 ms total)

[----------] Global test environment tear-down

GTEST_SKIP

TEST(ExampleTest, ShouldFail) {
    GTEST_SKIP() << "Skipping test PR12345";
    
    std::vector<std::pair<double, double>> vec{
        {43.45, 43.45}, {44.34321, 45.1234}, {1.12, 1.1199999}};

    for (const auto& [lhs, rhs] : vec) {
        EXPECT_PRED_FORMAT2(testing::FloatLE, lhs, rhs);
        EXPECT_PRED_FORMAT2(testing::DoubleLE, lhs, rhs);
    }
}
[ RUN      ] ExampleTest.ShouldFail
/home/mateusz.adamski/Documents/repoGit/advanced_cpp/examples/gtest.cpp:102: Skipped
Skipping test PR12345

Skipp all fixture

class BaseTestBlackListFirewall : public testing::Test {
public:
    void SetUp() override {
        GTEST_SKIP() << "Skipping test PR12345";
        firewall_ = std::make_unique<BlackListFirewall>();

        firewall_->addToBlacklist("192.201.1.12");
        firewall_->addToBlacklist("192.122.1.12");
        firewall_->addToBlacklist("192.178.1.12");
        firewall_->addToBlacklist("192.144.1.12");
        firewall_->addToBlacklist("192.188.1.12");
    }

    BlackListFirewall* firewall() const { return firewall_.get(); }

private:
    std::unique_ptr<BlackListFirewall> firewall_;
};
[----------] 3 tests from BaseTestBlackListFirewall
[ RUN      ] BaseTestBlackListFirewall.ShouldFilter
/home/mateusz.adamski/Documents/repoGit/advanced_cpp/examples/gtest.cpp:76: Skipped
Skipping test PR12345
[  SKIPPED ] BaseTestBlackListFirewall.ShouldFilter (0 ms)
[ RUN      ] BaseTestBlackListFirewall.ShouldNotFilter
/home/mateusz.adamski/Documents/repoGit/advanced_cpp/examples/gtest.cpp:86: Skipped
Skipping test PR12345
[  SKIPPED ] BaseTestBlackListFirewall.ShouldNotFilter (0 ms)
[ RUN      ] BaseTestBlackListFirewall.ShouldReportError
/home/mateusz.adamski/Documents/repoGit/advanced_cpp/examples/gtest.cpp:96: Skipped
Skipping test PR12345
[  SKIPPED ] BaseTestBlackListFirewall.ShouldReportError (0 ms)
[----------] 3 tests from BaseTestBlackListFirewall (0 ms total)

EXPECT_EXIT and EXPECT_THROW

TEST(MyDeathTest, NormalExit) {
    EXPECT_EXIT(normalExit(), testing::ExitedWithCode(0), "Succes");
}

TEST(MyDeathTest, KillProcess) {
    EXPECT_EXIT(killProcess(), testing::KilledBySignal(SIGKILL),
                "Sending myself unblockable signal");
}

void foo(int val) {
    if (val != 42) {
        throw std::runtime_error("Wrong number");
    }
}

TEST(MyDeathTest, NormalExit) {
    EXPECT_THROW(foo(5), std::runtime_error);
}

Sharing Resources Between Tests

struct Foo {
    std::string name_;
    int value_;
};

class FooTest : public testing::Test {
public:
    // Shared
    static void SetUpTestSuite() {
        if (!shared_resource_) {
            shared_resource_ = std::make_unique<Foo>();
            shared_resource_->name_ = "Shared Foo!\n";
            shared_resource_->value_ = 42;
        }
    }

    // Shared
    static void TearDownTestSuite() {
        shared_resource_ = nullptr;
    }

    // Per-test
    void SetUp() override {}

    // Per-test
    void TearDown() override {}

protected:
    inline static std::unique_ptr<Foo> shared_resource_;
};

TEST_F(FooTest, Test1) {
    EXPECT_EQ(FooTest::shared_resource_->name_, "Shared Foo!\n");
    EXPECT_EQ(FooTest::shared_resource_->value_, 42);
    FooTest::shared_resource_->name_ = "Foo!!!";
    FooTest::shared_resource_->value_ = 13;
}

TEST_F(FooTest, Test2) {
    EXPECT_EQ(FooTest::shared_resource_->name_, "Foo!!!");
    EXPECT_EQ(FooTest::shared_resource_->value_, 13);
}

Testing private code - should be avoided

class Foo {
private:
    FRIEND_TEST(FooTest, PrivateTest);

    int doSth(int val) { return val; }
};

TEST(FooTest, PrivateTest) {
    Foo foo;
    EXPECT_EQ(foo.doSth(42), 42);
}

Exercise 1

Open project SHM and write test for class Ship.cpp which will test it's interface. Remember to test edge cases :). Test it well and fix issues.

Go to Presentation/exercises/SHM/build/test and run test using command ./SHM_Tests.