forked from qt-creator/qt-creator
UnitTests: How to write tests
Change-Id: Ic2221a83d139e7d7ad7b4d0f40d602822fb6654c Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
This commit is contained in:
@@ -11,10 +11,156 @@ All tests here depend on the [GoogleTest][1] framework.
|
|||||||
|
|
||||||
## Best Practices
|
## Best Practices
|
||||||
|
|
||||||
We're following those patterns/approaches;
|
We follow these patterns/approaches for structuring our tests:
|
||||||
|
|
||||||
* The Arrange, Act, and Assert (AAA) Pattern
|
Arrange, Act, and Assert (AAA) / Given When Then (GWT)
|
||||||
* Given When Then (GWT) Pattern
|
|
||||||
|
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> 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<typename Matcher>
|
||||||
|
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
|
## Test Organization
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user