diff options
| author | Lexi Winter <lexi@le-fay.org> | 2025-06-29 19:25:29 +0100 |
|---|---|---|
| committer | Lexi Winter <lexi@le-fay.org> | 2025-06-29 19:25:29 +0100 |
| commit | bc524d70253a4ab2fe40c3ca3e5666e267c0a4d1 (patch) | |
| tree | 1e629e7b46b1d9972a973bc93fd100bcebd395be /docs/tutorial.md | |
| download | nihil-548ea226e1944e077d3ff305df43ef6b366b03f4.tar.gz nihil-548ea226e1944e077d3ff305df43ef6b366b03f4.tar.bz2 | |
import catch2 3.8.1vendor/catch2/3.8.1vendor/catch2
Diffstat (limited to 'docs/tutorial.md')
| -rw-r--r-- | docs/tutorial.md | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/docs/tutorial.md b/docs/tutorial.md new file mode 100644 index 0000000..fb5a5b3 --- /dev/null +++ b/docs/tutorial.md @@ -0,0 +1,228 @@ +<a id="top"></a> +# Tutorial + +**Contents**<br> +[Getting Catch2](#getting-catch2)<br> +[Writing tests](#writing-tests)<br> +[Test cases and sections](#test-cases-and-sections)<br> +[BDD style testing](#bdd-style-testing)<br> +[Data and Type driven tests](#data-and-type-driven-tests)<br> +[Next steps](#next-steps)<br> + + +## Getting Catch2 + +Ideally you should be using Catch2 through its [CMake integration](cmake-integration.md#top). +Catch2 also provides pkg-config files and two file (header + cpp) +distribution, but this documentation will assume you are using CMake. If +you are using the two file distribution instead, remember to replace +the included header with `catch_amalgamated.hpp` ([step by step instructions](migrate-v2-to-v3.md#how-to-migrate-projects-from-v2-to-v3)). + + +## Writing tests + +Let's start with a really simple example ([code](../examples/010-TestCase.cpp)). Say you have written a function to calculate factorials and now you want to test it (let's leave aside TDD for now). + +```c++ +unsigned int Factorial( unsigned int number ) { + return number <= 1 ? number : Factorial(number-1)*number; +} +``` + +```c++ +#include <catch2/catch_test_macros.hpp> + +unsigned int Factorial( unsigned int number ) { + return number <= 1 ? number : Factorial(number-1)*number; +} + +TEST_CASE( "Factorials are computed", "[factorial]" ) { + REQUIRE( Factorial(1) == 1 ); + REQUIRE( Factorial(2) == 2 ); + REQUIRE( Factorial(3) == 6 ); + REQUIRE( Factorial(10) == 3628800 ); +} +``` + +This will compile to a complete executable which responds to [command line arguments](command-line.md#top). If you just run it with no arguments it will execute all test cases (in this case there is just one), report any failures, report a summary of how many tests passed and failed and return the number of failed tests (useful for if you just want a yes/ no answer to: "did it work"). + +Anyway, as the tests above as written will pass, but there is a bug. +The problem is that `Factorial(0)` should return 1 (due to [its +definition](https://en.wikipedia.org/wiki/Factorial#Factorial_of_zero)). +Let's add that as an assertion to the test case: + +```c++ +TEST_CASE( "Factorials are computed", "[factorial]" ) { + REQUIRE( Factorial(0) == 1 ); + REQUIRE( Factorial(1) == 1 ); + REQUIRE( Factorial(2) == 2 ); + REQUIRE( Factorial(3) == 6 ); + REQUIRE( Factorial(10) == 3628800 ); +} +``` + +After another compile & run cycle, we will see a test failure. The output +will look something like: + +``` +Example.cpp:9: FAILED: + REQUIRE( Factorial(0) == 1 ) +with expansion: + 0 == 1 +``` + +Note that the output contains both the original expression, +`REQUIRE( Factorial(0) == 1 )` and the actual value returned by the call +to the `Factorial` function: `0`. + +We can fix this bug by slightly modifying the `Factorial` function to: +```c++ +unsigned int Factorial( unsigned int number ) { + return number > 1 ? Factorial(number-1)*number : 1; +} +``` + + +### What did we do here? + +Although this was a simple test it's been enough to demonstrate a few +things about how Catch2 is used. Let's take a moment to consider those +before we move on. + +* We introduce test cases with the `TEST_CASE` macro. This macro takes + one or two string arguments - a free form test name and, optionally, + one or more tags (for more see [Test cases and Sections](#test-cases-and-sections)). +* The test automatically self-registers with the test runner, and user + does not have do anything more to ensure that it is picked up by the test + framework. _Note that you can run specific test, or set of tests, + through the [command line](command-line.md#top)._ +* The individual test assertions are written using the `REQUIRE` macro. + It accepts a boolean expression, and uses expression templates to + internally decompose it, so that it can be individually stringified + on test failure. + +On the last point, note that there are more testing macros available, +because not all useful checks can be expressed as a simple boolean +expression. As an example, checking that an expression throws an exception +is done with the `REQUIRE_THROWS` macro. More on that later. + + +## Test cases and sections + +Like most test frameworks, Catch2 supports a class-based fixture mechanism, +where individual tests are methods on class and setup/teardown can be +done in constructor/destructor of the type. + +However, their use in Catch2 is rare, because idiomatic Catch2 tests +instead use _sections_ to share setup and teardown code between test code. +This is best explained through an example ([code](../examples/100-Fix-Section.cpp)): + +```c++ +TEST_CASE( "vectors can be sized and resized", "[vector]" ) { + // This setup will be done 4 times in total, once for each section + std::vector<int> v( 5 ); + + REQUIRE( v.size() == 5 ); + REQUIRE( v.capacity() >= 5 ); + + SECTION( "resizing bigger changes size and capacity" ) { + v.resize( 10 ); + + REQUIRE( v.size() == 10 ); + REQUIRE( v.capacity() >= 10 ); + } + SECTION( "resizing smaller changes size but not capacity" ) { + v.resize( 0 ); + + REQUIRE( v.size() == 0 ); + REQUIRE( v.capacity() >= 5 ); + } + SECTION( "reserving bigger changes capacity but not size" ) { + v.reserve( 10 ); + + REQUIRE( v.size() == 5 ); + REQUIRE( v.capacity() >= 10 ); + } + SECTION( "reserving smaller does not change size or capacity" ) { + v.reserve( 0 ); + + REQUIRE( v.size() == 5 ); + REQUIRE( v.capacity() >= 5 ); + } +} +``` + +For each `SECTION` the `TEST_CASE` is **executed from the start**. This means +that each section is entered with a freshly constructed vector `v`, that +we know has size 5 and capacity at least 5, because the two assertions +are also checked before the section is entered. This behaviour may not be +ideal for tests where setup is expensive. Each run through a test case will +execute one, and only one, leaf section. + +Section can also be nested, in which case the parent section can be +entered multiple times, once for each leaf section. Nested sections are +most useful when you have multiple tests that share part of the set up. +To continue on the vector example above, you could add a check that +`std::vector::reserve` does not remove unused excess capacity, like this: + +```cpp + SECTION( "reserving bigger changes capacity but not size" ) { + v.reserve( 10 ); + + REQUIRE( v.size() == 5 ); + REQUIRE( v.capacity() >= 10 ); + SECTION( "reserving down unused capacity does not change capacity" ) { + v.reserve( 7 ); + REQUIRE( v.size() == 5 ); + REQUIRE( v.capacity() >= 10 ); + } + } +``` + +Another way to look at sections is that they are a way to define a tree +of paths through the test. Each section represents a node, and the final +tree is walked in depth-first manner, with each path only visiting only +one leaf node. + +There is no practical limit on nesting sections, as long as your compiler +can handle them, but keep in mind that overly nested sections can become +unreadable. From experience, having section nest more than 3 levels is +usually very hard to follow and not worth the removed duplication. + + +## BDD style testing + +Catch2 also provides some basic support for BDD-style testing. There are +macro aliases for `TEST_CASE` and `SECTIONS` that you can use so that +the resulting tests read as BDD spec. `SCENARIO` acts as a `TEST_CASE` +with "Scenario: " name prefix. Then there are `GIVEN`, `WHEN`, `THEN` +(and their variants with `AND_` prefix), which act as a `SECTION`, +similarly prefixed with the macro name. + +For more details on the macros look at the [test cases and +sections](test-cases-and-sections.md#top) part of the reference docs, +or at the [vector example done with BDD macros](../examples/120-Bdd-ScenarioGivenWhenThen.cpp). + + +## Data and Type driven tests + +Test cases in Catch2 can also be driven by types, input data, or both +at the same time. + +For more details look into the Catch2 reference, either at the +[type parametrized test cases](test-cases-and-sections.md#type-parametrised-test-cases), +or [data generators](generators.md#top). + + +## Next steps + +This page is a brief introduction to get you up and running with Catch2, +and to show the basic features of Catch2. The features mentioned here +can get you quite far, but there are many more. However, you can read +about these as you go, in the ever-growing [reference section](Readme.md#top) +of the documentation. + + +--- + +[Home](Readme.md#top) |
