|
| 1 | +# Unit testing with xtd.tunit (🟡 Intermediate) |
| 2 | + |
| 3 | +How to create a simple unit test application using xtd.tunit, and how it compares to Google Test and Catch2. |
| 4 | + |
| 5 | +We will validate several properties of a simple integer collection: `std::vector<int> items = {1, 3, 5, 4, 2};` |
| 6 | +Validations performed : |
| 7 | +* Check that the collection size equals 5. |
| 8 | +* Verify that it contains the elements 1, 2, 3, 4, and 5 (regardless of order). |
| 9 | +* Verify that it contains the items 2 and 4. |
| 10 | +* Verify that it does not contain 6. |
| 11 | +* Verify that the collection is ordered. |
| 12 | + |
| 13 | +## With Google Tests |
| 14 | + |
| 15 | +```cpp |
| 16 | +#include <vector> |
| 17 | +#include <algorithm> |
| 18 | +#include <gtest/gtest.h> |
| 19 | + |
| 20 | +namespace collection_tests { |
| 21 | + class vector_tests : public ::testing::Test { |
| 22 | + protected: |
| 23 | + std::vector<int> items = {1, 3, 5, 4, 2}; |
| 24 | + }; |
| 25 | + |
| 26 | + TEST_F(vector_tests, items_size_equals_5) { |
| 27 | + EXPECT_EQ(items.size(), 5); |
| 28 | + } |
| 29 | + |
| 30 | + TEST_F(vector_tests, items_are_equivalent_to_1_2_3_4_5) { |
| 31 | + std::vector<int> expected = {1, 2, 3, 4, 5}; |
| 32 | + auto sorted_items = items; |
| 33 | + std::sort(sorted_items.begin(), sorted_items.end()); |
| 34 | + EXPECT_EQ(sorted_items, expected) << "Expected equivalent collections {1, 2, 3, 4, 5}, but got {1, 3, 5, 4, 2}"; |
| 35 | + } |
| 36 | + |
| 37 | + TEST_F(vector_tests, items_contains_2_and_4) { |
| 38 | + for (int v : {2, 4}) |
| 39 | + EXPECT_NE(std::find(items.begin(), items.end(), v), items.end()) << "Expected to find " << v << " in collection."; |
| 40 | + } |
| 41 | + |
| 42 | + TEST_F(vector_tests, items_does_not_contain_6) { |
| 43 | + EXPECT_EQ(std::find(items.begin(), items.end(), 6), items.end()) << "Expected collection not to contain 6."; |
| 44 | + } |
| 45 | + |
| 46 | + TEST_F(vector_tests, items_is_ordered) { |
| 47 | + EXPECT_TRUE(std::is_sorted(items.begin(), items.end())) << "Expected ordered, but got {1, 3, 5, 4, 2}"; |
| 48 | + } |
| 49 | +} |
| 50 | + |
| 51 | +// This code produces the following output : |
| 52 | +// |
| 53 | +// [==========] Running 5 tests from 1 test suite. |
| 54 | +// [----------] Global test environment set-up. |
| 55 | +// [----------] 5 tests from vector_tests |
| 56 | +// [ RUN ] vector_tests.items_size_equals_5 |
| 57 | +// [ OK ] vector_tests.items_size_equals_5 (0 ms) |
| 58 | +// [ RUN ] vector_tests.items_are_equivalent_to_1_2_3_4_5 |
| 59 | +// [ OK ] vector_tests.items_are_equivalent_to_1_2_3_4_5 (0 ms) |
| 60 | +// [ RUN ] vector_tests.items_contains_2_and_4 |
| 61 | +// [ OK ] vector_tests.items_contains_2_and_4 (0 ms) |
| 62 | +// [ RUN ] vector_tests.items_does_not_contain_6 |
| 63 | +// [ OK ] vector_tests.items_does_not_contain_6 (0 ms) |
| 64 | +// [ RUN ] vector_tests.items_is_ordered |
| 65 | +// /!---OMITTED---!/unit_test_project1/src/unit_test1.cpp:32: Failure |
| 66 | +// Value of: std::is_sorted(items.begin(), items.end()) |
| 67 | +// Actual: false |
| 68 | +// Expected: true |
| 69 | +// Expected ordered, but got {1, 3, 5, 4, 2} |
| 70 | +// [ FAILED ] vector_tests.items_is_ordered (0 ms) |
| 71 | +// [----------] 5 tests from vector_tests (0 ms total) |
| 72 | +// |
| 73 | +// [----------] Global test environment tear-down |
| 74 | +// [==========] 5 tests from 1 test suite ran. (0 ms total) |
| 75 | +// [ PASSED ] 4 tests. |
| 76 | +// [ FAILED ] 1 test, listed below: |
| 77 | +// [ FAILED ] vector_tests.items_is_ordered |
| 78 | +// |
| 79 | +// 1 FAILED TEST |
| 80 | +``` |
| 81 | +
|
| 82 | +## With Catch2 |
| 83 | +
|
| 84 | +```cpp |
| 85 | +#include <vector> |
| 86 | +#include <algorithm> |
| 87 | +#include <catch2/catch_all.hpp> |
| 88 | +
|
| 89 | +namespace collection_tests { |
| 90 | + TEST_CASE("vector_tests") { |
| 91 | + std::vector<int> items = {1, 3, 5, 4, 2}; |
| 92 | + |
| 93 | + SECTION("items_size_equals_5") { |
| 94 | + REQUIRE(items.size() == 5); |
| 95 | + } |
| 96 | + |
| 97 | + SECTION("items_are_equivalent_to_1_2_3_4_5") { |
| 98 | + std::vector<int> expected = {1, 2, 3, 4, 5}; |
| 99 | + auto sorted_expected = expected; |
| 100 | + auto sorted_items = items; |
| 101 | + std::sort(sorted_expected.begin(), sorted_expected.end()); |
| 102 | + std::sort(sorted_items.begin(), sorted_items.end()); |
| 103 | + REQUIRE(sorted_items == sorted_expected); |
| 104 | + } |
| 105 | + |
| 106 | + SECTION("items_contains_2_and_4") { |
| 107 | + for (int v : {2, 4}) |
| 108 | + REQUIRE(std::find(items.begin(), items.end(), v) != items.end()); |
| 109 | + } |
| 110 | + |
| 111 | + SECTION("items_does_not_contain_6") { |
| 112 | + REQUIRE(std::find(items.begin(), items.end(), 6) == items.end()); |
| 113 | + } |
| 114 | + |
| 115 | + SECTION("items_is_ordered") { |
| 116 | + REQUIRE(std::is_sorted(items.begin(), items.end())); |
| 117 | + } |
| 118 | + } |
| 119 | +} |
| 120 | +// This code produces the following output : |
| 121 | +// |
| 122 | +// Randomness seeded to: 2162595183 |
| 123 | +// |
| 124 | +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 125 | +// unit_test_project2 is a Catch2 v3.11.0 host application. |
| 126 | +// Run with -? for options |
| 127 | +// |
| 128 | +// ------------------------------------------------------------------------------- |
| 129 | +// vector_tests |
| 130 | +// items_is_ordered |
| 131 | +// ------------------------------------------------------------------------------- |
| 132 | +// /!---OMITTED---!/unit_test_project2/src/unit_test1.cpp:31 |
| 133 | +// ............................................................................... |
| 134 | +// |
| 135 | +// /!---OMITTED---!/unit_test_project2/src/unit_test1.cpp:32: FAILED: |
| 136 | +// REQUIRE( std::is_sorted(items.begin(), items.end()) ) |
| 137 | +// with expansion: |
| 138 | +// false |
| 139 | +// |
| 140 | +// =============================================================================== |
| 141 | +// test cases: 1 | 1 failed |
| 142 | +// assertions: 6 | 5 passed | 1 failed |
| 143 | +``` |
| 144 | + |
| 145 | +## With xtd.tunit |
| 146 | + |
| 147 | +```cpp |
| 148 | +#include <vector> |
| 149 | +#include <xtd/xtd> |
| 150 | + |
| 151 | +namespace collection_tests { |
| 152 | + class test_class_(vector_tests) { |
| 153 | + std::vector<int> items = {1, 3, 5, 4, 2}; |
| 154 | + |
| 155 | + void test_method_(items_size_equals_5) { |
| 156 | + assert::are_equal(5, items.size()); |
| 157 | + } |
| 158 | + |
| 159 | + void test_method_(items_are_equivalent_to_1_2_3_4_5) { |
| 160 | + collection_assert::are_equivalent({1, 2, 3, 4, 5}, items); |
| 161 | + } |
| 162 | + |
| 163 | + void test_method_(items_conatains_2_and_4) { |
| 164 | + collection_assert::contains({2, 4}, items); |
| 165 | + } |
| 166 | + |
| 167 | + void test_method_(items_does_not_conatain_6) { |
| 168 | + collection_assert::does_not_contain({6}, items); |
| 169 | + } |
| 170 | + |
| 171 | + void test_method_(items_is_ordered) { |
| 172 | + collection_assert::is_ordered(items); |
| 173 | + } |
| 174 | + }; |
| 175 | +} |
| 176 | + |
| 177 | +// This code produces the following output : |
| 178 | +// |
| 179 | +// Start 5 tests from 1 test case |
| 180 | +// Run tests: |
| 181 | +// SUCCEED collection_tests::vector_tests.items_size_equals_5 [< 1 ms] |
| 182 | +// SUCCEED collection_tests::vector_tests.items_are_equivalent_to_1_2_3_4_5 [< 1 ms] |
| 183 | +// SUCCEED collection_tests::vector_tests.items_conatains_2_and_4 [< 1 ms] |
| 184 | +// SUCCEED collection_tests::vector_tests.items_does_not_conatain_6 [< 1 ms] |
| 185 | +// FAILED collection_tests::vector_tests.items_is_ordered [< 1 ms] |
| 186 | +// Expected: <ordered> |
| 187 | +// But was: < 1, 3, 5, 4, 2 > |
| 188 | +// Stack Trace: in /!---OMITTED---!/unit_test_project3/src/unit_test1.cpp:25 |
| 189 | +// |
| 190 | +// Test results: |
| 191 | +// SUCCEED 4 tests. |
| 192 | +// FAILED 1 test. |
| 193 | +// End 5 tests from 1 test case ran. [0.0004 seconds] |
| 194 | +``` |
| 195 | +
|
| 196 | +## Remarks |
| 197 | +
|
| 198 | +* [xtd.tunit](https://gammasoft71.github.io/xtd/reference_guides/latest/group__xtd__tunit.html) contains collection checks out of the box. It is therefore not necessary to write 'boilerplate' code to carry out the tests. |
| 199 | +* [xtd.tunit](https://gammasoft71.github.io/xtd/reference_guides/latest/group__xtd__tunit.html) provides explicit and human-readable failure messages. The optional user message is meant to clarify the intent of the test, not to compensate for missing diagnostics. |
| 200 | +* [xtd.tunit](https://gammasoft71.github.io/xtd/reference_guides/latest/group__xtd__tunit.html) has different classes to help you write your unit tests quickly: |
| 201 | + * [xtd::tunit::assert](https://gammasoft71.github.io/xtd/reference_guides/latest/classxtd_1_1tunit_1_1assert.html) |
| 202 | + * [xtd::tunit::collection_assert](https://gammasoft71.github.io/xtd/reference_guides/latest/classxtd_1_1tunit_1_1collection__assert.html) |
| 203 | + * [xtd::tunit::directory_assert](https://gammasoft71.github.io/xtd/reference_guides/latest/classxtd_1_1tunit_1_1directory__assert.html) |
| 204 | + * [xtd::tunit::file_assert](https://gammasoft71.github.io/xtd/reference_guides/latest/classxtd_1_1tunit_1_1file__assert.html) |
| 205 | + * [xtd::tunit::string_assert](https://gammasoft71.github.io/xtd/reference_guides/latest/classxtd_1_1tunit_1_1string__assert.html) |
| 206 | +
|
| 207 | +## To go further |
| 208 | +* [xtd.tunit](https://gammasoft71.github.io/xtd/reference_guides/latest/group__xtd__tunit.html) also provides the possibility of ensuring that a test is in the right conditions to run: |
| 209 | +
|
| 210 | +Beyond simple assertions, xtd.tunit also provides advanced features such as assumptions and validations. |
| 211 | +
|
| 212 | +```cpp |
| 213 | +#include <xtd/xtd> |
| 214 | +
|
| 215 | +namespace system_tests { |
| 216 | + class test_class_(fundamental_types) { |
| 217 | + void test_method_(int_size) { |
| 218 | + assume::is_true(environment::compiler_version().is_64_bit()); |
| 219 | + assert::are_equal(4, sizeof(int)); |
| 220 | + } |
| 221 | + }; |
| 222 | +} |
| 223 | +
|
| 224 | +// This code produces the following output is build in 32 bits : |
| 225 | +// |
| 226 | +// Start 1 test from 1 test case |
| 227 | +// Run tests: |
| 228 | +// ABORTED system_tests::fundamental_types.int_size [< 1 ms] |
| 229 | +// Test aborted |
| 230 | +// Expected: true |
| 231 | +// But was: false |
| 232 | +// Stack Trace: in /!---OMITTED---!/unit_test_project3/src/unit_test1.cpp:6 |
| 233 | +// |
| 234 | +// Test results: |
| 235 | +// ABORTED 1 test. |
| 236 | +// End 1 test from 1 test case ran. [0.0004 seconds] |
| 237 | +``` |
| 238 | + |
| 239 | +* The classes [xtd::tunit::valid](https://gammasoft71.github.io/xtd/reference_guides/latest/classxtd_1_1tunit_1_1valid.html), [xtd::tunit::collection_valid](https://gammasoft71.github.io/xtd/reference_guides/latest/classxtd_1_1tunit_1_1collection__valid.html), [xtd::tunit::directory_valid](https://gammasoft71.github.io/xtd/reference_guides/latest/classxtd_1_1tunit_1_1directory__valid.html), [xtd::tunit::file_valid](https://gammasoft71.github.io/xtd/reference_guides/latest/classxtd_1_1tunit_1_1file__valid.html) and , [xtd::tunit::string_valid](https://gammasoft71.github.io/xtd/reference_guides/latest/classxtd_1_1tunit_1_1string__valid.html) contrary to asserts allow you to run a check but without stopping the test at the first error. |
| 240 | + |
| 241 | +Unlike assertions, validations do not stop the test when a check fails, allowing you to gather multiple failures in a single run. |
| 242 | + |
| 243 | +```cpp |
| 244 | +#include <xtd/xtd> |
| 245 | + |
| 246 | +namespace system_tests { |
| 247 | + class test_class_(fundamental_types) { |
| 248 | + void test_method_(int_operations) { |
| 249 | + int i = 6; |
| 250 | + valid::are_equal(14, i + 5); |
| 251 | + valid::are_equal(4, i - 5); |
| 252 | + valid::are_equal(3, i / 3); |
| 253 | + valid::are_equal(27, i * 3); |
| 254 | + } |
| 255 | + void test_method_(short_operations) { |
| 256 | + short i = 6; |
| 257 | + assert::are_equal(14, i + 5); |
| 258 | + assert::are_equal(4, i - 5); |
| 259 | + assert::are_equal(3, i / 3); |
| 260 | + assert::are_equal(27, i * 3); |
| 261 | + } |
| 262 | + }; |
| 263 | +} |
| 264 | + |
| 265 | +// This code produces the following output : |
| 266 | +// |
| 267 | +// Start 2 tests from 1 test case |
| 268 | +// Run tests: |
| 269 | +// FAILED system_tests::fundamental_types.int_operations [< 1 ms] |
| 270 | +// Expected: 14 |
| 271 | +// But was: 11 |
| 272 | +// Stack Trace: in /!---OMITTED---!/unit_test_project3/src/unit_test1.cpp:7 |
| 273 | +// FAILED system_tests::fundamental_types.int_operations [< 1 ms] |
| 274 | +// Expected: 4 |
| 275 | +// But was: 1 |
| 276 | +// Stack Trace: in /!---OMITTED---!/unit_test_project3/src/unit_test1.cpp:8 |
| 277 | +// FAILED system_tests::fundamental_types.int_operations [< 1 ms] |
| 278 | +// Expected: 3 |
| 279 | +// But was: 2 |
| 280 | +// Stack Trace: in /!---OMITTED---!/unit_test_project3/src/unit_test1.cpp:9 |
| 281 | +// FAILED system_tests::fundamental_types.int_operations [< 1 ms] |
| 282 | +// Expected: 27 |
| 283 | +// But was: 18 |
| 284 | +// Stack Trace: in /!---OMITTED---!/unit_test_project3/src/unit_test1.cpp:10 |
| 285 | +// FAILED system_tests::fundamental_types.short_operations [< 1 ms] |
| 286 | +// Expected: 14 |
| 287 | +// But was: 11 |
| 288 | +// Stack Trace: in /!---OMITTED---!/unit_test_project3/src/unit_test1.cpp:14 |
| 289 | +// |
| 290 | +// Test results: |
| 291 | +// FAILED 2 tests. |
| 292 | +// End 2 tests from 1 test case ran. [0.0005 seconds] |
| 293 | +``` |
| 294 | + |
| 295 | +## See also |
| 296 | +
|
| 297 | +* [xtd.tunit reference](https://gammasoft71.github.io/xtd/reference_guides/latest/group__xtd__tunit.html) |
| 298 | +* [Tips & Tricks](/docs/documentation/tips_and_tricks) |
| 299 | +* [Tips & Tricks](/docs/documentation/tips_and_tricks) |
| 300 | +* [Documentation](/docs/documentation) |
0 commit comments