diff --git a/README.md b/README.md index 723bb0e9..0badf108 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,53 @@ -# Testowanie - Coders School -## [Moduł 1: Framework `catch2`](module1/presentation_catch.md) +# Testowanie + +## [Moduł 1](module1/) + +### [Framework `catch2`](module1/00_catch.pl.md) + +### [Zadanie domowe](module1/01_homework.pl.md) + +## [Moduł 2](module2/) + +### [Framework `GTest`](module2/00_gtest.pl.md) + +### [Zadanie domowe](module2/01_homework.pl.md) + +## [Moduł 3](module3/) + +### [Wstrzykiwanie zależności](module3/00_dependency_injection.pl.md) + +### [Atrapy](module3/01_test_doubles.pl.md) + +### [`GMock`](module3/02_gmock.pl.md) + +### [Praca domowa](module3/03_homework.pl.md) + +___ + +# Testing + +## [Module 1](module1/) + +### [`catch2` framework](module1/00_catch.en.md) + +### [Homework](module1/01_homework.en.md) + +## [Module 2](module2/) + +### [`GTest` framework](module2/00_gtest.en.md) + +### [Homework](module2/01_homework.en.md) + +## [Module 3](module3/) + +### [Dependency injection](module3/00_dependency_injection.en.md) + +### [Test doubles](module3/01_test_doubles.en.md) + +### [`GMock`](module3/02_gmock.en.md) -## [Moduł 2: Framework `gtest`](module2/presentation_gtest.md) +### [Homework](module3/03_homework.en.md) diff --git a/module1/00_catch.en.md b/module1/00_catch.en.md new file mode 100644 index 00000000..0a59cc1e --- /dev/null +++ b/module1/00_catch.en.md @@ -0,0 +1,74 @@ + + +# Testing + +## `Catch2`framework + + + Coders School + + +___ + +## Useful assertions + +* REQUIRE( expression ) +* CHECK( expression ) +* REQUIRE_FALSE( expression ) +* CHECK_FALSE( expression ) +* REQUIRE_NOTHROW( expression ) +* CHECK_NOTHROW( expression ) +* REQUIRE_THROWS_AS( expression, exception type ) +* CHECK_THROWS_AS( expression, exception type ) +* REQUIRE_THAT( lhs, matcher expression ) +* CHECK_THAT( lhs, matcher expression ) + +___ + +## Useful matchers + +* String matchers + * StartsWith + * EndsWith + * Contains + * Equals + * Matches +* Vector matchers + * Contains which checks whether a specified vector is present in the result + * VectorContains which checks whether a specified element is present in the result + * Equals which checks whether the result is exactly equal (order matters) to a specific vector + * UnorderedEquals which checks whether the result is equal to a specific vector under a permutation + +___ + +## Exercise + +Test the algorithm `std::sort()`. + +In this task, it is important to cover as many scenarios of this algorithm as possible. + +___ + +## Organization of tests + +* TEST_CASE( test name [, tags ] ) +* SECTION( section name ) + +Or + + +* SCENARIO( scenario name [, tags ] ) +* GIVEN( something ) +* WHEN( something ) +* THEN( something ) + +Where `SCENARIO` maps to `TEST_CASE`, while `GIVEN`, `WHEN` and `THEN` on `SECTION`. + + +___ + +## Generators + +Thanks to data generators, one test scenario can be run on different test data. + +[Link to documentation](https://github.com/catchorg/Catch2/blob/master/docs/generators.md#top) diff --git a/module1/presentation_catch.md b/module1/00_catch.pl.md similarity index 100% rename from module1/presentation_catch.md rename to module1/00_catch.pl.md diff --git a/module1/01_homework.en.md b/module1/01_homework.en.md new file mode 100644 index 00000000..c29b5fb9 --- /dev/null +++ b/module1/01_homework.en.md @@ -0,0 +1,95 @@ + + +# Testing + +## Homework + + + Coders School + + +___ + +## SHM - group task + +* Write tests for SHM (0 points) + * do not report PR to us 😉 you can show the tests or ask about them on Discord. + +___ + +## Bowling - a new task + +Get into new groups. There should be people in the group with whom you haven't cooperated yet 🙂 + +* Brainstorm and write test scenarios in the GIVEN WHEN THEN format for a program whose task is to count points in bowling. Scenarios are to be written in the Catch2 framework, but do not need to contain any code. You don't even have to implement this program (yet 😉). The point is to cover as much functionality as possible with tests. Description of bowling rules below. + * 1 point for each test scenario. Scenarios covering the same functionality will be counted as one. + +___ + +## Bowling - The scoring rules + +```text +Each game, or "line" of bowling, includes ten turns, +or "frames" for the bowler. + +In each frame, the bowler gets up to two tries to +knock down all ten pins. + +If the first ball in a frame knocks down all ten pins, +this is called a "strike". The frame is over. The score +for the frame is ten plus the total of the pins knocked +down in the next two balls. + +If the second ball in a frame knocks down all ten pins, +this is called a "spare". The frame is over. The score +for the frame is ten plus the number of pins knocked +down in the next ball. + +If, after both balls, there is still at least one of the +ten pins standing the score for that frame is simply +the total number of pins knocked down in those two balls. + +If you get a spare in the last (10th) frame you get one +more bonus ball. If you get a strike in the last (10th) +frame you get two more bonus balls. +These bonus throws are taken as part of the same turn. +If a bonus ball knocks down all the pins, the process +does not repeat. The bonus balls are only used to +calculate the score of the final frame. + +The game score is the total of all frame scores. + +Examples: + +X indicates a strike +/ indicates a spare +- indicates a miss +| indicates a frame boundary +The characters after the || indicate bonus balls + +X|X|X|X|X|X|X|X|X|X||XX +Ten strikes on the first ball of all ten frames. +Two bonus balls, both strikes. +Score for each frame == 10 + score for next two +balls == 10 + 10 + 10 == 30 +Total score == 10 frames x 30 == 300 + +9-|9-|9-|9-|9-|9-|9-|9-|9-|9-|| +Nine pins hit on the first ball of all ten frames. +Second ball of each frame misses last remaining pin. +No bonus balls. +Score for each frame == 9 +Total score == 10 frames x 9 == 90 + +5/|5/|5/|5/|5/|5/|5/|5/|5/|5/||5 +Five pins on the first ball of all ten frames. +Second ball of each frame hits all five remaining +pins, a spare. +One bonus ball, hits five pins. +Score for each frame == 10 + score for next one +ball == 10 + 5 == 15 +Total score == 10 frames x 15 == 150 + +X|7/|9-|X|-8|8/|-6|X|X|X||81 +Total score == 167 +``` diff --git a/module1/presentation_homework.md b/module1/01_homework.pl.md similarity index 96% rename from module1/presentation_homework.md rename to module1/01_homework.pl.md index ecb4b6b8..41729cc4 100644 --- a/module1/presentation_homework.md +++ b/module1/01_homework.pl.md @@ -13,7 +13,7 @@ ___ ## SHM - zadanie grupowe * Napisz testy do SHM (0 punktów) - * nie zgłaszajcie nam PR ;) możecie się pochwalić testami lub zapytać o nie na Discordzie. + * nie zgłaszajcie nam PR 😉 możecie się pochwalić testami lub zapytać o nie na Discordzie. ___ diff --git a/module1/index.en.html b/module1/index.en.html new file mode 100644 index 00000000..327bf830 --- /dev/null +++ b/module1/index.en.html @@ -0,0 +1,108 @@ + + + + + + + Testing - Coders School + + + + + + + + + + + + + + + +
+
+
+
+ +

Testing #1

+

catch2 framework

+ + + Coders School + + +

Łukasz Ziobroń

+ +
+
+ +
+
+ +
+
+
+
+
+ +

Coders School

+ Coders School + +
+
+
+ + + + + + diff --git a/module1/index.html b/module1/index.pl.html similarity index 95% rename from module1/index.html rename to module1/index.pl.html index 15f4c1ed..3e35e128 100644 --- a/module1/index.html +++ b/module1/index.pl.html @@ -4,7 +4,7 @@ - Testing - Coders School + Testowanie - Coders School @@ -66,11 +66,11 @@

Łukasz Ziobroń

You can change the port by using npm start -- --port=8001. --> -
-
diff --git a/module2/00_gtest.en.md b/module2/00_gtest.en.md new file mode 100644 index 00000000..c4dfd69e --- /dev/null +++ b/module2/00_gtest.en.md @@ -0,0 +1,169 @@ + + +# Testing + +## Framework `GTest` + + + Coders School + + +___ + +## GTest documentation + +* [Repo GTest](https://github.com/google/googletest) +* [Primer](https://github.com/google/googletest/blob/master/googletest/docs/primer.md) +* [Advanced Guide](https://github.com/google/googletest/blob/master/googletest/docs/advanced.md) + +___ + + +## Useful assertions + +| Fatal assertion | Nonfatal assertion | Verifies | +| ------------------------------ | ------------------------------ | --------------------- | +| `ASSERT_TRUE(condition);` | `EXPECT_TRUE(condition);` | condition is true | +| `ASSERT_FALSE(condition);` | `EXPECT_FALSE(condition);` | condition is false | +| `ASSERT_EQ(val1, val2);` | `EXPECT_EQ(val1, val2);` | val1 == val2 | +| `ASSERT_NE(val1, val2);` | `EXPECT_NE(val1, val2);` | val1! = val2 | +| `ASSERT_LT(val1, val2);` | `EXPECT_LT(val1, val2);` | val1 val2 | +| `ASSERT_GE(val1, val2);` | `EXPECT_GE(val1, val2);` | val1> = val2 | +| `ASSERT_THAT(value, matcher);` | `EXPECT_THAT(value, matcher);` | value matches matcher | + +___ + + +## Assertions with exceptions + +| Fatal assertion | Nonfatal assertion | Verifies | +| ------------------------------------------ | ------------------------------------------ | ----------------------------------------------- | +| `ASSERT_THROW(statement, exception_type);` | `EXPECT_THROW(statement, exception_type);` | statement throws an exception of the given type | +| `ASSERT_ANY_THROW(statement);` | `EXPECT_ANY_THROW(statement);` | statement throws an exception of any type | +| `ASSERT_NO_THROW(statement);` | `EXPECT_NO_THROW(statement);` | statement doesn't throw any exception | + +___ + +## Organization of tests + +* TEST(TestSuiteName, TestName) +* TEST_F(TestFixtureName, TestName) + +___ + +## Test case - `TEST` + +```cpp +TEST(ClassUnderTestSuite, callMeShouldAlwaysReturn42) { + // GIVEN + ClassUnderTest cut{}; + auto expected = 42; + + // WHEN + auto result = cut.callMe(); + + // THEN + ASSERT_EQ(result, expected); +} +``` + +___ + +## Tests with shared data - `TEST_F` + +```cpp +struct ClassUnderTestFixture : public ::testing::Test { + // common data and helper functions + ClassUnderTest cut{}; +} + +TEST_F(ClassUnderTestFixture, callMeShouldAlwaysReturn42) { + // GIVEN + auto expected = 42; + + // WHEN + auto result = cut.callMe(); + + // THEN + ASSERT_EQ(result, expected); +} +``` + +___ + +## Exercise + +Test the algorithm `std::sort()`. + +In this task, it is important to cover as many scenarios of this algorithm as possible. + +___ + +## Parametric tests - `TEST_P` + +We need 3 things: + +1. Fixture class, inheriting from `testing::TestWithParam` where T is the type of input that will be provided to the test +2. Test scenario `TEST_P` +3. Generator `INSTANTIATE_TEST_SUITE_P` which will generate test cases + +[Link to documentation](https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#value-parameterized-tests) + +___ + +### Example + +```cpp +class MyFixture : public testing::TestWithParam> { + // You can implement all the usual fixture class members here. + // To access the test parameter, call GetParam() from class + // TestWithParam. + ClassUnderTest cut; +}; + +TEST_P(MyFixture, MyTestName) { + // Inside a test, access the test parameter with the GetParam() method + // of the TestWithParam class: + auto [value, expected] = GetParam(); + EXPECT_EQ(cut.myFunction(value), expected); +} + +INSTANTIATE_TEST_SUITE_P(MyInstantiationName, + MyFixture, + testing::Values({1, 1}, + {2, 1}, + {3, 2})); +``` + +___ + +## Generators + +Thanks to data generators, one test scenario can be run on different test data. + +* Range(begin, end [, step]) + * Yields values `{begin, begin+step, begin+step+step, ...}`. The values ​​do not include end. step defaults is 1. +* Values(v1, v2, ..., vN) + * Yields values `{v1, v2, ..., vN}`. +* ValuesIn(container), ValuesIn(begin,end) + * Yields values ​​from a C-style array, an STL-style container, or an iterator range [begin, end) +* Bool() + * Yields sequence `{false, true}`. +* Combine(g1, g2, ..., gN) + * Yields all combinations (Cartesian product) as `std::tuples` of the values ​​generated by the N generators. + +[Link to documentation](https://github.com/catchorg/Catch2/blob/master/docs/generators.md#top) + + +___ + +## Additional task + +(If there is time or someone is willing) + +Complete repo tasks with fan_controller. We will work on it in Testing lesson #3. +In addition to the README.md command, add the same tests in the GTest framework. + +[Repo fan_controller](http://github.com/coders-school/fan_controller) diff --git a/module2/presentation_gtest.md b/module2/00_gtest.pl.md similarity index 100% rename from module2/presentation_gtest.md rename to module2/00_gtest.pl.md diff --git a/module2/01_homework.en.md b/module2/01_homework.en.md new file mode 100644 index 00000000..24ca786b --- /dev/null +++ b/module2/01_homework.en.md @@ -0,0 +1,175 @@ + + +# Testing + +## Homework + + + Coders School + + +___ + +## Questions from job interviews + +* What are unit tests? +* Are unit tests white-box or black-box tests? +* What code coverage is appropriate? +* What tools do you know that calculate code coverage? +* What frameworks do you know for testing? +* Which framework do you consider the most interesting and why? +* How to test the code that causes an exception? +* How to test the code that is supposed to crash the application? + +___ + +## Pre-work + +* Read [GMock for Dummies](https://github.com/google/googletest/blob/master/googlemock/docs/for_dummies.md) +* Please see the tests in the repository [Pizzas](https://github.com/ziobron/Pizzas). There you will find some mocking code `TEST_F(PizzeriaTest, calculatePriceForPizzaMock)` and the file `test/mocks/PizzaMock.hpp`. +* Add your own tests / mocks as you wish + +___ + +## Bowling 🎳 - new project + +Watch [Uncle Bob's video about TDD](https://trello-attachments.s3.amazonaws.com/5b20ebcd819b419f2d65c274/5b5d70bf109bc670f6d8d10d/90fb5c9305b6e8092df116da1c845210/fm_CleanCode2-540.mp4). + +To understand the scoring of a bowling game, it is helpful to describe the rules. It is attached at the end of the presentation or available [here](https://github.com/coders-school/testing/blob/master/module2/bowling_rules.txt) + +In new groups, write an application that will count points in the bowling alley. + +___ + + +## Bowling 🎳 + +### Requirements (+10 XP for each condition met): + +* counting partial points (for incomplete frames, e.g. `3-|X|4/|5`) +* counting total points - [description of the rules](https://github.com/coders-school/testing/blob/master/module2/bowling_rules.txt) +* input validation with incomplete frames for several players (see next slide) +* input from multiple files in one directory, each file with multiple players represents a different track ([Filesystem library from C ++ 17](https://en.cppreference.com/w/cpp/filesystem) is recommended) +* displaying the results on the screen with the division into tracks (with game status) and players and saving to one file (next slide) +* program (`main.cpp`) has to take 2 parameters from the command line. The first is the directory where will be txt files with game states on the tracks, the second optional is the output file where the processed results are to be saved. If the second parameter is not given, the results are to be written to the screen. Example of use: `./bowling inputDirectory results.txt`. +* program (`main.cpp`) after giving the parameter `-h` or `--help` has to display short information about what it does and how to use it (i.e. the point above) + +#### +20 XP for delivering by the end of August + +___ + + +### Bowling - input + +`lane1.txt` + +```text +Name1:X|4-|3 +Name2:34|X|0- +:X|22|33 +``` + +`lane2.txt` (empty) + +`lane3.txt` + +```text +Michael:X|7/|9-|X|-8|8/|-6|X|X|X||81 +Radek:9-|9-|9-|9-|9-|9-|9-|9-|9-|9-|| +``` + +### Bowling - output + +```text +### Lane 1: game in progress ### +Name1 30 +Name2 44 +34 +### Lane 2: no game ### +### Lane 3: game finished ### +Michael 167 +Radek 90 +``` + +___ + +## Organizational requirements + +* project board with a division into notes after planning +* Continuous Integration and build system configured +* work through pull requests (each PR needs to have a number and description from the note, must undergo internal Code Review +* the content of the table may change as new requirements are discovered (and surely what you assume will change at the beginning and many things will happen that you did not anticipate) +* try to work in TDD mode from the very beginning +* each functionality must be tested; no tests = requirement not met. +* work on repo forks `coders-school/testing` +* after implementing all requirements PR to `coders-school/testing:master` + +___ + +## Bowling - rules for counting points + +```text +Each game, or "line" of bowling, includes ten turns, +or "frames" for the bowler. + +In each frame, the bowler gets up to two tries to +knock down all ten pins. + +If the first ball in a frame knocks down all ten pins, +this is called a "strike". The frame is over. The score +for the frame is ten plus the total of the pins knocked +down in the next two balls. + +If the second ball in a frame knocks down all ten pins, +this is called a "spare". The frame is over. The score +for the frame is ten plus the number of pins knocked +down in the next ball. + +If, after both balls, there is still at least one of the +ten pins standing the score for that frame is simply +the total number of pins knocked down in those two balls. + +If you get a spare in the last (10th) frame you get one +more bonus ball. If you get a strike in the last (10th) +frame you get two more bonus balls. +These bonus throws are taken as part of the same turn. +If a bonus ball knocks down all the pins, the process +does not repeat. The bonus balls are only used to +calculate the score of the final frame. + +The game score is the total of all frame scores. + +Examples: + +X indicates a strike +/ indicates a spare +- indicates a miss +| indicates a frame boundary +The characters after the || indicate bonus balls + +X|X|X|X|X|X|X|X|X|X||XX +Ten strikes on the first ball of all ten frames. +Two bonus balls, both strikes. +Score for each frame == 10 + score for next two +balls == 10 + 10 + 10 == 30 +Total score == 10 frames x 30 == 300 + +9-|9-|9-|9-|9-|9-|9-|9-|9-|9-|| +Nine pins hit on the first ball of all ten frames. +Second ball of each frame misses last remaining pin. +No bonus balls. +Score for each frame == 9 +Total score == 10 frames x 9 == 90 + +5/|5/|5/|5/|5/|5/|5/|5/|5/|5/||5 +Five pins on the first ball of all ten frames. +Second ball of each frame hits all five remaining +pins, a spare. +One bonus ball, hits five pins. +Score for each frame == 10 + score for next one +ball == 10 + 5 == 15 +Total score == 10 frames x 15 == 150 + +X|7/|9-|X|-8|8/|-6|X|X|X||81 +Total score == 167 +``` diff --git a/module2/presentation_homework.md b/module2/01_homework.pl.md similarity index 100% rename from module2/presentation_homework.md rename to module2/01_homework.pl.md diff --git a/module2/index.en.html b/module2/index.en.html new file mode 100644 index 00000000..78b4250a --- /dev/null +++ b/module2/index.en.html @@ -0,0 +1,108 @@ + + + + + + + Testing - Coders School + + + + + + + + + + + + + + + +
+
+
+
+ +

Testing #2

+

gtest framework

+ + + Coders School + + +

Łukasz Ziobroń

+ +
+
+ +
+
+ +
+
+
+
+
+ +

Coders School

+ Coders School + +
+
+
+ + + + + + diff --git a/module2/index.html b/module2/index.pl.html similarity index 95% rename from module2/index.html rename to module2/index.pl.html index 7dc75bef..61b01c68 100644 --- a/module2/index.html +++ b/module2/index.pl.html @@ -4,7 +4,7 @@ - Testing - Coders School + Testowanie - Coders School @@ -66,11 +66,11 @@

Łukasz Ziobroń

You can change the port by using npm start -- --port=8001. --> -
-
diff --git a/module3/00_dependency_injection.en.md b/module3/00_dependency_injection.en.md new file mode 100644 index 00000000..53142cba --- /dev/null +++ b/module3/00_dependency_injection.en.md @@ -0,0 +1,227 @@ + + +# Dependency injection + +___ + + +## Problem #1 - network connections + +How to test a function that sends data over the network? + +```cpp +size_t send(int socket, const void *buffer, size_t length, int flags); +``` + +* What if the socket won't be open? +* What if the flags are set incorrectly? +* What if the connection has not been established? +* What if the connection is lost before sending the data? +* What if the connection is lost while sending data? +* What if our network buffers are clogged and we don't get information about the number of bytes sent? +* What if the link delays (lags) are very high? +* What if we run the tests concurrently and try to make 1000 connections at once? + +You can find some answers in the send function manual - `man 2 send`. + + +Network and distributed programming requires testing many untypical scenarios from the point of view of ordinary programming. + + +___ + +## Problem #2 - database + +How do I test a function that queries the database? + +```cpp +Response sendQuery(Query q); +``` + +* What happens if the database server is inactive? + * There will be no connection to the database server. The test will not pass. You must ensure that the server is always active before running the tests. +* What if the database server is on a different machine? + * We may not be connected to the database server. The test will not pass. You must ensure to always be connected to the server. +* What happens if the database server is busy? + * Reply will be returned after a long time. Tests will take a long time to complete or fail due to long waiting times. +* What if the database stores terabytes of data? + * Searching for data may take too long. Tests may fail. + +___ + +## Problem #3 - function with timeout + +How do I test a function that waits for a remote procedure to execute for a specified period of time and then reports an error? + +```cpp +template +Response executeRemotely(IpAddr address, T function, Timeout timeout); +``` + +* Is a unit test that takes 2 minutes good? + * It is not. UTs should give the programmer a virtually immediate response. Other types of tests (system, integration) may take correspondingly longer. +* What will happen if we set timeout to 0? + * The function will end immediately or will it wait forever and the tests will crash? +* Will it be okay to change the timeout to 1ms for the test? + * What if the machine is overloaded and the test lasts 2ms? Should the test be reported as FAILED? + +___ + + +## Cutting off and changing dependencies + +In order to conduct effective tests for these problems, it is necessary to cut off all dependence on time, databases or network operations. + + +These types of operations should be closed into separate classes, which we will replace during testing with something that will not cause problems. For example, we can use special versions of the above-mentioned functions. + + +```cpp +size_t send(int socket, const void *buffer, size_t length, int flags) { + return 10; +} + +Response sendQuery(Request r) { + return Response{r}; +} + +template +Response executeRemotely(IpAddr address, T function, Timeout timeout) { + if (timeout == 0) { + throw RemoteExecutionTimeout{}; + } else { + return Response{}; + } +} +``` + + +___ + +## Dependency injection + +The technique in which we put our own implementations into tests instead of actual objects to get rid of problems or simplify tests is called **dependency injection**. + + +There are several different types of it, but the most common is the use of dynamic polymorphism to replace dependencies. Using interfaces, you can substitute any other objects that implement a given interface (in C++ they inherit from this interface). + + +___ + +## We are testing the Player class + +```cpp +class Player { + std::vector> weapons_; + Weapon& currentWeapon_; + // ... +public: + Player(const std::shared_ptr & defaultWeapon) + : weapons_.emplace_back(defaultWeapon) + , currentWeapon_(*defaultWeapon) + {} + void shoot() { currentWeapon_.shoot(); } + void reload() { currentWeapon_.reload(); } + // ... +}; + +class Weapon { +public: + virtual void shoot() = 0; + virtual void reload() = 0; + virtual ~Weapon() = default; +}; + +class Machinegun : public Weapon { + static size_t capacity_ = 40; + size_t bullets_ = 40; +public: + void shoot() override { --bullets_; } + void reload() override { bullets_ = 40; }; +}; + +``` + +___ + +### Use of the Player class in your code + +```cpp +auto ak47 = std::make_shared(); +Player grzesiek{ak47}; +grzesiek.shoot(); +// shoot 40 times +grzesiek.reload(); +// no possibility to check if mp5 has shoot, no such interface +``` + +___ + + +### Use in tests + +We test the `Player` class, we cut the dependence - `Machinegun`. `Machinegun` class is not possible to check whether we have shot or not, because its interface does not allow checking the magazine capacity. + + +```cpp +class TestWeapon : public Weapon { +public: + void shoot() override { hasShoot = true; } + void reload() override { hasShoot = false; } + bool hasShoot = false; +}; +``` + + +`TestWeapon` class has a public field indicating if a weapon fired. It inherits from the interface `Weapon` so we can use it instead of a real weapon in tests. + + +```cpp +TEST(PlayerTest, shootingAndReloadingWeapon) { + auto testWeapon = std::make_shared; + Player testPayer{testWeapon}; + EXPECT_FALSE(testWeapon.hasShoot); + testPlayer.shoot(); + EXPECT_TRUE(testWeapon.hasShoot); + testPlayer.reload(); + EXPECT_FALSE(testWeapon.hasShoot); +} +``` + + +If `Player` class uses `Weapon` class correctly, the test should pass. + + +___ + +## Dependency injection using interfaces + +Dependency injection simplifies your test code. For example, we don't need to call a method `shoot()` 40 times to unload the magazine. If class `Machinegun` needed in constructor object of `Magazine` class and class `Magazine` 40 objects of `Bullet` class, you would have to create them in tests as well. + + +Writing your own `TestWeapon` class is also an overhead, but dependency injection frameworks allow us to do it in one line of code. + + +___ + +## What to cut? + +In **unit tests** everything except the tested class. + + +In **module tests** everything except the tested module (several or a dozen classes implementing one bigger functionality). + + +In **system tests** external systems - databases, network, time dependencies. We test the operation of the entire system. + + +In **integration tests** nothing. We test the system's interaction with real dependencies. + + +___ + +## Other techniques + +* split backend - injecting dependencies by linking with another implementation. For this purpose, the build system (Makefile, CMakeLists.txt) must be properly configured to compile and link with the replaced implementations when building tests. +* split header - dependency injection by including other header files for testing. +* injecting via setters - not very elegant, especially when we need to write a setter especially for testing. diff --git a/module3/01_dependency_injection.md b/module3/00_dependency_injection.pl.md similarity index 99% rename from module3/01_dependency_injection.md rename to module3/00_dependency_injection.pl.md index 407325f7..4926d638 100644 --- a/module3/01_dependency_injection.md +++ b/module3/00_dependency_injection.pl.md @@ -51,7 +51,7 @@ Response sendQuery(Query q); ___ -## Problem 3 - funkcja z timeoutem +## Problem #3 - funkcja z timeoutem Jak przetestować funkcję, która czeka na wykonanie zdalnej procedury przez określony czas, po czym zgłasza błąd? diff --git a/module3/01_test_doubles.en.md b/module3/01_test_doubles.en.md new file mode 100644 index 00000000..8fc5a07b --- /dev/null +++ b/module3/01_test_doubles.en.md @@ -0,0 +1,175 @@ + + +# Test doubles + +___ + +## Types of test doubles + +* dummy +* fake +* stub +* spy +* mock + +___ + +## Dummy + +* the simplest test doubles +* does nothing, empty functions +* it is only intended to meet function signature requirements and may not be used at all + +### Original functions + + +```cpp +double Car::accelerate(int) { /* complicated implementation */ } +void sayHello(std::string name) { std::cout << "Hello " << name << '\n'; } +``` + + +### Dummy implementation + + +```cpp +double DummyCar::accelerate(int) { return 0.0; } +void dummyHello(std::string name) {} +``` + + +___ + + +## Stub + +* a little more advanced test double +* it may have logic or minimal implementation +* returns the values ​​defined by us + +### Original function + + +```cpp +double Car::accelerate(int) { /* complicated implementation */ } +void sayHello(std::string name) { std::cout << "Hello " << name << '\n'; } +``` + + +### Stub implementation + + +```cpp +double StubCar::accelerate(int value) { + return value < 0 ? -10.0 : 10.0; +} +void stubHello(std::string name) { + if (name == "anonymous") { + throw std::logic_error("anonymous not allowed"); + } +} +``` + + +___ + +## Fake + +* models more complex interactions than a stub +* in practice in C++ it does not even stand out as a separate entity +* often fake == stub + +Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an InMemoryTestDatabase is a good example). + + +___ + +## Mock + +* verifies the behavior of the tested object +* checks if it called expected functions on the mock in expected way + +### Original function + + +```cpp +double Car::accelerate(int) { /* complicated implementation */ } +``` + + +### Mock implementation + + +```cpp +class MockCar : public Car { + MOCK_METHOD(double, accelerate, (int), (override)); +}; +``` + + +Mocks are pre-programmed with expectations which form a specification of the calls they are expected to receive. They can throw an exception if they receive a call they don't expect and are checked during verification to ensure they got all the calls they were expecting. + + +___ + +### Mocking functions outside of the class + +* Usually stubs or dummy are used +* GMock can be used, but it is complicating the code + +See [GoogleMock CookBook](https://github.com/google/googletest/blob/master/googlemock/docs/cook_book.md#mocking-free-functions) + + +___ + +## Spy + +* mock, which additionally counts the number of function calls +* in GMock spy == mock + +Spies are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent. + + +___ + +## In brief + +* Dummy + * Its purpose is to "satisfy the signature." + * No interactions are performed on it. +* Stub + * Minimal implementation for interactions between objects. + * Void methods are empty. + * Values ​​returned by members are either type-specific or hard-coded. +* Fake + * Includes an implementation of logic that imitates production code, but in the simplest possible way. +* Mock + * Verifies behavior by logging whether an interaction has been performed. +* Spy + * Verifies behavior by recording the number of interactions performed. + +Source: [TDD course part 19](https://dariuszwozniak.net/posts/kurs-tdd-19-mock-stub-fake-spy-dummy) + + +___ + +## In practice + +In C++ we will meet this simplification: + + +* dummy ~= stub + * test double without logic +* stub == fake + * test double with logic +* mock == spy + * test double with action logging + +We implement dummy and stub objects ourselves. They are so simple that you don't need any frameworks for them. + + +The GMock library is most often used for mock objects. + + +[See examples of dummy items in the Pizzas repo](https://github.com/coders-school/pizzas/blob/master/test/mocks/PizzaMock.hpp) + diff --git a/module3/02_test_doubles.md b/module3/01_test_doubles.pl.md similarity index 100% rename from module3/02_test_doubles.md rename to module3/01_test_doubles.pl.md diff --git a/module3/02_gmock.en.md b/module3/02_gmock.en.md new file mode 100644 index 00000000..33a210f8 --- /dev/null +++ b/module3/02_gmock.en.md @@ -0,0 +1,151 @@ + + +# GMock + +___ + +## GMock - documentation + +* [GMock for Dummies](https://github.com/google/googletest/blob/master/googlemock/docs/for_dummies.md) +* [GMock cheatsheet](https://github.com/google/googletest/blob/master/googlemock/docs/cheat_sheet.md) +* [GMock cookbook](https://github.com/google/googletest/blob/master/googlemock/docs/cook_book.md) + +___ + +## Creating mocks + +1. Writing from scratch +2. [Using the generator script](https://github.com/google/googletest/tree/master/googlemock/scripts/generator) + +___ + +```cpp +class Foo { + ... + virtual ~Foo(); + virtual int GetSize() const = 0; + virtual string Describe(const char* name) = 0; + virtual string Describe(int type) = 0; + virtual bool Process(Bar elem, int count) = 0; +}; +``` + +```cpp +#include "gmock/gmock.h" + +class MockFoo : public Foo { + ... + MOCK_METHOD(int, GetSize, (), (const, override)); + MOCK_METHOD(string, Describe, (const char* name), (override)); + MOCK_METHOD(string, Describe, (int type), (override)); + MOCK_METHOD(bool, Process, (Bar elem, int count), (override)); +}; +``` + + +___ + +## Setting expectations + +```cpp +MockFoo foo; +// ... +EXPECT_CALL(foo, Describe(5)) + .Times(3) + .WillRepeatedly(Return("Category 5")); +``` + + +We expect that there will be called `Describe` function 3 times on `foo` with parameter `5` and it's going to return `std::string` with the text "Category 5" each time. + + +```cpp +EXPECT_CALL(foo, Process(_, 10)) + .WillOnce(Return(true)) + .WillOnce(Return(false)); +``` + + +We expect that there will be called `Process` function on `foo` with no matter what the first parameter is and 10 as the second parameter. The first time it returns true, the second time it returns false. + + +[More possibilities in GoogleMock CheatSheet](https://github.com/google/googletest/blob/master/googlemock/docs/cheat_sheet.md#setting-expectations-expectcall) + + +___ + + +## Mock decorators + +### Nice Mock + + +```cpp +NiceMock nice_foo; // The type is a subclass of MockFoo. +``` + + +Ignores extra functions that have been called in addition to expected functions. + + +### Naggy Mock (Default) + + +```cpp +MockFoo foo; +NaggyMock naggy_foo; // The type is a subclass of MockFoo. +``` + + +Displays warnings about additional functions that have been called in addition to those expected. + + +### Strict Mock + + +```cpp +StrictMock strict_foo; // The type is a subclass of MockFoo. +``` + + +Calls to additional functions beyond the expected ones are treated as errors which the test does not pass. + + +___ + + +## Exercises + +### [Repo Pizzas](https://github.com/coders-school/pizzas) + +0. Correct bugs in the program to make the tests pass +1. Correct the PizzaMock class to be written with a newer version of GMock. +2. Remove the time dependency from the tests with a dummy or a stub. + +___ + + +### 1. Remove time dependency in tests + +#### Where do we become dependent on time? + + +`Pizzeria.cpp:52` +`std::this_thread::sleep_for(pizza->getBakingTime());` + + +#### How can you cut this dependency? + + +You must create an interface from which 2 implementations will inherit. + + +* The first one will actually call the current function std::this_thread::sleep_for() and will be used in normal binary. +* The second one will do nothing and will be used in testing. + +#### How do I inject these dependencies? + + +* In the class constructor Pizzeria an additional argument should be added - a pointer/reference to the interface. +* Class Pizzeria should have a new field - also a pointer/reference to the interface. +* In place of the former function call std::this_thread::sleep_for() one must call the appropriate function from the interface. diff --git a/module3/03_gmock.md b/module3/02_gmock.pl.md similarity index 97% rename from module3/03_gmock.md rename to module3/02_gmock.pl.md index 4963d07c..5b468ba1 100644 --- a/module3/03_gmock.md +++ b/module3/02_gmock.pl.md @@ -140,7 +140,7 @@ ___ Należy utworzyć interfejs, po którym będą dziedziczyć 2 implementacje. -* Pierwsza z nich będzie faktycznie wywoływać obecną funkcję `std::this_thread::sleep_for()` i będzie używana w zwykłej binarce. +* Pierwsza z nich będzie faktycznie wywoływać obecną funkcję std::this_thread::sleep_for() i będzie używana w zwykłej binarce. * Druga z nich nie będzie nic robiła i będzie używana w testach. #### W jaki sposób wstrzykiwać te zależności? diff --git a/module3/03_homework.en.md b/module3/03_homework.en.md new file mode 100644 index 00000000..7272c4d1 --- /dev/null +++ b/module3/03_homework.en.md @@ -0,0 +1,29 @@ + + +# Homework + +___ + + +## Post-work + +### [Repo Pizzas](https://github.com/coders-school/pizzas) + +1. (10 XP) Write a TimeMock class to replace the dummy/stub written during the class. Use it through StrictMock. Add appropriate EXPECT_CALL in test code. +2. (15 XP) Add a new test case in which you duplicate the main.cpp functionality, but order 3 pizzas: StubPizza and 2 different MockPizza (different names, prices and baking times; one as StrictMock, the other as NiceMock). Set the right EXPECT_CALL. +3. (5 XP) Create your own .github/workflows/module3.yml file which will cause GitHub to run tests automatically. + ++3 XP for each of the 3 points delivered by 20/09 23:59. + + +You can work in pairs 🙂 + + +___ + +## Pre-work + +* Find and read what are: + * RAII + * memory leaks and how to detect them + * exceptions and how to use them in C++ diff --git a/module3/04_homework.md b/module3/03_homework.pl.md similarity index 100% rename from module3/04_homework.md rename to module3/03_homework.pl.md diff --git a/module3/index.en.html b/module3/index.en.html new file mode 100644 index 00000000..8bdbe7ed --- /dev/null +++ b/module3/index.en.html @@ -0,0 +1,118 @@ + + + + + + + Testing - Coders School + + + + + + + + + + + + + + + +
+
+
+
+ +

Testing #3

+

Dependency injection

+ + + Coders School + + +

Łukasz Ziobroń

+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+ +

Coders School

+ Coders School + +
+
+
+ + + + + + diff --git a/module3/index.html b/module3/index.pl.html similarity index 93% rename from module3/index.html rename to module3/index.pl.html index d06e410f..6df828a9 100644 --- a/module3/index.html +++ b/module3/index.pl.html @@ -4,7 +4,7 @@ - Testing - Coders School + Testowanie - Coders School @@ -68,19 +68,19 @@

Łukasz Ziobroń

You can change the port by using npm start -- --port=8001. --> -
-
-
-