Skip to content

Commit

Permalink
Rename the example file
Browse files Browse the repository at this point in the history
Fix the license on the file
Improve the example to make the use cases much clearer. Also fix up the documentation
  • Loading branch information
KStocky committed Aug 3, 2024
1 parent bf4575a commit 39a4b82
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 52 deletions.
79 changes: 62 additions & 17 deletions docs/test-fixtures.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,18 @@ class UniqueTestsFixture {
}
```
The two test cases here will create uniquely-named derived classes of UniqueTestsFixture and thus can access the `getID()` protected method and `conn` member variables. This ensures that both the test cases are able to create a DBConnection using the same method (DRY principle) and that any ID's created are unique such that the order that tests are executed does not matter.
The two test cases here will create uniquely-named derived classes of
UniqueTestsFixture and thus can access the `getID()` protected method
and `conn` member variables. This ensures that both the test cases
are able to create a DBConnection using the same method
(DRY principle) and that any ID's created are unique such that the
order that tests are executed does not matter.
### 2. `METHOD_AS_TEST_CASE`
`METHOD_AS_TEST_CASE` lets you register a member function of a class as a Catch2 test case. The class will be separately instantiated for each method registered in this way.
`METHOD_AS_TEST_CASE` lets you register a member function of a class
as a Catch2 test case. The class will be separately instantiated
for each method registered in this way.
```cpp
class TestClass {
Expand All @@ -75,40 +82,78 @@ public:
METHOD_AS_TEST_CASE( TestClass::testCase, "Use class's method as a test case", "[class]" )
```

This type of fixture is similar to [TEST_CASE_METHOD](#1-test_case_method) except in this case it will directly use the provided class to create an object rather than a derived class.
This type of fixture is similar to [TEST_CASE_METHOD](#1-test_case_method) except in this
case it will directly use the provided class to create an object rather than a derived
class.

### 3. `TEST_CASE_PERSISTENT_FIXTURE`

> [Introduced](link-to-issue-or-PR) in Catch2 X.Y.Z
> [Introduced](https://github.com/catchorg/Catch2/pull/2885) in Catch2 X.Y.Z

`TEST_CASE_PERSISTENT_FIXTURE` behaves in the same way as
[TEST_CASE_METHOD](#1-test_case_method) except that there will only be
one instance created throughout the entire run of a test case. To
demonstrate this have a look at the following example:

```cpp
struct MyFixture{
int MyInt = 0;
class ClassWithExpensiveSetup {
public:
ClassWithExpensiveSetup() {
// expensive construction
std::this_thread::sleep_for( std::chrono::seconds( 2 ) );
}

~ClassWithExpensiveSetup() noexcept {
// expensive destruction
std::this_thread::sleep_for( std::chrono::seconds( 1 ) );
}

int getInt() const { return 42; }
};

TEST_CASE_PERSISTENT_FIXTURE(MyFixture, "Tests with MyFixture") {

const int val = MyInt++;
struct MyFixture {
mutable int myInt = 0;
ClassWithExpensiveSetup expensive;
};

SECTION("First partial run") {
REQUIRE(val == 0);
}
TEST_CASE_PERSISTENT_FIXTURE( MyFixture, "Tests with MyFixture" ) {

const int val = myInt++;

SECTION("Second partial run") {
REQUIRE(val == 1);
SECTION( "First partial run" ) {
const auto otherValue = expensive.getInt();
REQUIRE( val == 0 );
REQUIRE( otherValue == 42 );
}

SECTION( "Second partial run" ) { REQUIRE( val == 1 ); }
}
```
This example demonstates two possible use-cases of this fixture type:
1. Improve test run times by reducing the amount of expensive and
redundant setup and tear-down required.
2. Reusing results from the previous partial run, in the current
partial run.
This test case will be executed twice as there are two leaf sections.
On the first run `val` will be `0` and on the second run `val` will be
`1`. This is useful if you would like to share some expensive setup code
with all runs of your test case which can't be done at static
initialization time.
`1`.
Additionally, we are simulating an expensive object using
`std::this_thread::sleep_for`, but real world use-cases could be:
1. Creating a D3D12/Vulkan device
2. Connecting to a database
3. Loading a file.
The fixture object will be constructed just before the test case begins, and
it will be destroyed just after the test case ends.
NOTE: The member function which runs the test case is `const`. Therefore
if you want to mutate any member of the fixture it must be marked as
`mutable` as shown in this example. This is to make it clear that
the initial state of the fixture is intended to mutate during the
execution of the test case.
## Templated test fixtures
Expand Down
74 changes: 74 additions & 0 deletions examples/111-Fix-PersistentFixture.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@

// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)

// SPDX-License-Identifier: BSL-1.0

// Fixture.cpp

// Catch has three ways to express fixtures:
// - Sections
// - Traditional class-based fixtures that are created and destroyed on every
// partial run
// - Traditional class-based fixtures that are created at the start of a test
// case and destroyed at the end of a test case (this file)

// main() provided by linkage to Catch2WithMain

#include <catch2/catch_test_macros.hpp>

#include <thread>

class ClassWithExpensiveSetup {
public:
ClassWithExpensiveSetup() {
// Imagine some really expensive set up here.
// e.g.
// setting up a D3D12/Vulkan Device,
// connecting to a database,
// loading a file
// etc etc etc
std::this_thread::sleep_for( std::chrono::seconds( 2 ) );
}

~ClassWithExpensiveSetup() noexcept {
// We can do any clean up of the expensive class in the destructor
// e.g.
// destroy D3D12/Vulkan Device,
// disconnecting from a database,
// release file handle
// etc etc etc
std::this_thread::sleep_for( std::chrono::seconds( 1 ) );
}

int getInt() const { return 42; }
};

struct MyFixture {

// The test case member function is const.
// Therefore we need to mark any member of the fixture
// that needs to mutate as mutable.
mutable int myInt = 0;
ClassWithExpensiveSetup expensive;
};

// Only one object of type MyFixture will be instantiated for the run
// of this test case even though there are two leaf sections.
// This is useful if your test case requires an object that is
// expensive to create and could be reused for each partial run of the
// test case.
TEST_CASE_PERSISTENT_FIXTURE( MyFixture, "Tests with MyFixture" ) {

const int val = myInt++;

SECTION( "First partial run" ) {
const auto otherValue = expensive.getInt();
REQUIRE( val == 0 );
REQUIRE( otherValue == 42 );
}

SECTION( "Second partial run" ) { REQUIRE( val == 1 ); }
}
2 changes: 1 addition & 1 deletion examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ target_compile_definitions(231-Cfg_OutputStreams PUBLIC CATCH_CONFIG_NOSTDOUT)

# These examples use the standard separate compilation
set( SOURCES_IDIOMATIC_EXAMPLES
Fixture.cpp
030-Asn-Require-Check.cpp
100-Fix-Section.cpp
110-Fix-ClassFixture.cpp
111-Fix-PersistentFixture.cpp
120-Bdd-ScenarioGivenWhenThen.cpp
210-Evt-EventListeners.cpp
232-Cfg-CustomMain.cpp
Expand Down
34 changes: 0 additions & 34 deletions examples/Fixture.cpp

This file was deleted.

0 comments on commit 39a4b82

Please sign in to comment.