From b9f28b957cb52133d447b358a861d3290c11c936 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Thu, 6 Jun 2024 12:55:16 +0200 Subject: [PATCH] UnitTests: How to write tests Change-Id: Ic2221a83d139e7d7ad7b4d0f40d602822fb6654c Reviewed-by: Tim Jenssen --- tests/unit/README.md | 152 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 3 deletions(-) diff --git a/tests/unit/README.md b/tests/unit/README.md index 80247b769a8..396633f10a2 100644 --- a/tests/unit/README.md +++ b/tests/unit/README.md @@ -11,10 +11,156 @@ All tests here depend on the [GoogleTest][1] framework. ## Best Practices -We're following those patterns/approaches; +We follow these patterns/approaches for structuring our tests: -* The Arrange, Act, and Assert (AAA) Pattern -* Given When Then (GWT) Pattern +Arrange, Act, and Assert (AAA) / Given When Then (GWT) + +This pattern structures your tests into three distinct sections: + + Arrange (Given): Set up the initial conditions and inputs. + Act (When): Execute the code being tested. + Assert (Then): Verify the result. + +The test name is descriptive and uses underlines for readability. +In the Act block, only the code you want to test should exist. Don't +add the setup code there. It makes the debugging harder because you have +to step over it. + +```cpp +TEST(String, default_string_is_empty) +{ + String text; + + bool isEmpty = text.empty(); + + ASSERT_TRUE(isEmpty); +} +``` + +If the code block gets hard to read, you can write Arrange, Act, and Assert comments. +So the blocks are easier to identify. Don't write other comments. Use a descriptive +test name. + +```cpp +TEST(String, default_string_is_empty) +{ + // arrange + String text; + + // act + bool isEmpty = text.empty(); + + // assert + ASSERT_TRUE(isEmpty); +} +``` + +If you use a fixture, you can sometimes skip the arrange part. + +Fixtures should have the same name as your class. If you test functions, +it is the namespace. Sometimes you need multiple fixtures. Then you append +a descriptive text. You can put an underline in between. + +```cpp +class String : public ::testing::Test +{ +protected: + String text; +} + +TEST_F(String, default_string_is_empty) +{ + bool isEmpty = text.empty(); + + ASSERT_TRUE(isEmpty); +} +``` +For mocks, you have to reverse the order of act and assert. + +```cpp +class String : public ::testing::Test +{ +protected: + NiceMock outputMock; + Printer printer; +} + +TEST_F(String, printer_appends_message_to_the_end) +{ + // arrange + String text; + + // assert + EXPECT_CALL(outputMock, print(EndsWith(text))) + + // act + printer.print(text); +} +``` + +Don't write loops or branches in tests. Google Tests has many facilities supporting you to write clear tests. + +Matcher, for example, gives a much better error message. + +```cpp +TEST_F(Storage, return_all_entries_with_the_name_which_starts_with_foo) +{ + storage.load(filepath); + + auto entries = storage.entries("foo*"); + + ASSERT_THAT(entries, UnorderedElementsAre(IsEntry("foo", Field(&Entry::values), Contains(5)), + IsEntry("fooBar", Field(&Entry::values), IsSubset(42, 77)))); +} +``` + +You can even make the matcher easier to read. + +```cpp +template +auto FieldValues(const Matcher &matcher) +{ + return Field(&Entry::values, matcher); +} + +TEST_F(Storage, return_all_entries_with_the_name_which_starts_with_foo) +{ + storage.load(filepath); + + auto entries = storage.entries("foo*"); + + ASSERT_THAT(entries, UnorderedElementsAre(IsEntry("foo", FieldValues(Contains(5)), + IsEntry("fooBar", FieldValues(IsSubset(42, 77)))); +} +``` + +## Avoid + +Don't use using namespaces. It leads easily to name collisions. + +```cpp +using namespace QmlDesigner; +``` + +If you have long namespace names, you can use: + +```cpp +namespace ModelUtils = QmlDesigner::ModelUtils; +``` + +You can import single types too. But mind, that your tests are written +for reading, not for writing. They are part of the code documentation. + +```cpp +using QmlDesigner::ModelNode; +using Node = QmlDesigner::ModelNode; +``` + +There are exceptions like literal namespaces. + +```cpp +using namespace Qt::Literals; +``` ## Test Organization