trainings/CreatingReliableSoftwareCpp/Presentation/testing_gtest.md

473 lines
15 KiB
Markdown

## GTEST
* <!-- .element: class="fragment fade-in" --> <code>TEST(...)</code> Create single test
* <!-- .element: class="fragment fade-in" --> <code>TEST_F(...)</code> Use fixtures for testing
* <!-- .element: class="fragment fade-in" --> <code>TEST_P(...)</code> Use package of parameters for all tests
* <!-- .element: class="fragment fade-in" --> <code>EXPECT_*</code> Check condition
* <!-- .element: class="fragment fade-in" --> <code>ASSERT_*</code> Check condition and stop test if fails.
* <!-- .element: class="fragment fade-in" --> <code>EXPECT_PRED*</code> Allow to create own predicate, and other format of display errors
* <!-- .element: class="fragment fade-in" --> <code>testing::FloatLE</code> and <code>testing::DoubleLE</code> can be use for <code>EXPECT_PRED_FORMAT*</code> for floating-point comparison
* <!-- .element: class="fragment fade-in" --> <code>GTEST_SKIP()</code> can be use for skipping the test
* <!-- .element: class="fragment fade-in" --> <code>EXPECT_EXIT</code> can test signall which end program, like: <code>SIGKILL</code>
* <!-- .element: class="fragment fade-in" --> <code>EXPECT_THROW</code> will catch and check exceptions
___
## TEST
To create a simple test we can use `TEST` macro
```C++
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);
}
```
<!-- .slide: data-background="#ccc" -->
___
## 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.
```C++
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"));
}
```
<!-- .slide: style="font-size: 0.77em" -->
___
## TEST_P (1)
Whenever we need to run the same test but with a different set of parameters we should use `TEST_P`
```C++
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_;
};
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.77em" -->
___
<!-- .slide: data-background="#ccc" -->
## TEST_P (2)
```C++
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}));
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.8em" -->
___
<!-- .slide: data-background="#ccc" -->
## 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)
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.8em" -->
___
<!-- .slide: data-background="#ccc" -->
## TEST_P - inherit from other test
```C++
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_;
};
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.62em" -->
___
<!-- .slide: data-background="#ccc" -->
## testing::WithParamInterface
```C++
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"));
```
<!-- .element: class="fragment fade-in" -->
```
[----------] 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)
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.7em" -->
___
<!-- .slide: data-background="#ccc" -->
## EXPECT_PRED* - own error message
```C++
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));
}
```
<!-- .element: class="fragment fade-in" -->
```
[----------] 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)
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.7em" -->
___
<!-- .slide: data-background="#ccc" -->
## EXPECT_PRED* - check string
```C++
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"));
}
```
<!-- .element: class="fragment fade-in" -->
```
[ 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)
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.85em" -->
___
<!-- .slide: data-background="#ccc" -->
## EXPECT_PRED* - comparison of floating point numbers
```C++
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);
}
```
<!-- .element: class="fragment fade-in" -->
```
[ 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
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.7em" -->
___
<!-- .slide: data-background="#ccc" -->
## GTEST_SKIP
```C++
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);
}
}
```
<!-- .element: class="fragment fade-in" -->
```
[ RUN ] ExampleTest.ShouldFail
/home/mateusz.adamski/Documents/repoGit/advanced_cpp/examples/gtest.cpp:102: Skipped
Skipping test PR12345
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.7em" -->
___
<!-- .slide: data-background="#ccc" -->
## Skipp all fixture
```C++
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_;
};
```
<!-- .element: class="fragment fade-in" -->
```
[----------] 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)
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.60em" -->
___
<!-- .slide: data-background="#ccc" -->
## EXPECT_EXIT and EXPECT_THROW
```C++
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);
}
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.7em" -->
___
<!-- .slide: data-background="#ccc" -->
## Sharing Resources Between Tests
```C++
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);
}
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.7em" -->
___
<!-- .slide: data-background="#ccc" -->
## Testing private code - should be avoided
```C++
class Foo {
private:
FRIEND_TEST(FooTest, PrivateTest);
int doSth(int val) { return val; }
};
TEST(FooTest, PrivateTest) {
Foo foo;
EXPECT_EQ(foo.doSth(42), 42);
}
```
<!-- .element: class="fragment fade-in" -->
<!-- .slide: style="font-size: 0.9em" -->
___
## 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`.