diff --git a/docs/test-fixtures.md b/docs/test-fixtures.md index fb52a6aadb..e5f1cbcf95 100644 --- a/docs/test-fixtures.md +++ b/docs/test-fixtures.md @@ -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 { @@ -75,11 +82,13 @@ 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 @@ -87,28 +96,64 @@ 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 diff --git a/examples/111-Fix-PersistentFixture.cpp b/examples/111-Fix-PersistentFixture.cpp new file mode 100644 index 0000000000..d9f623a709 --- /dev/null +++ b/examples/111-Fix-PersistentFixture.cpp @@ -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 + +#include + +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 ); } +} \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 3dce2a8c54..4647df1dd9 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -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 diff --git a/examples/Fixture.cpp b/examples/Fixture.cpp deleted file mode 100644 index f1561dd1f0..0000000000 --- a/examples/Fixture.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// 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 - -struct MyFixture { - mutable int MyInt = 0; -}; - -TEST_CASE_PERSISTENT_FIXTURE(MyFixture, "Tests with MyFixture") { - - const int val = MyInt++; - - SECTION("First partial run") { - REQUIRE(val == 0); - } - - SECTION("Second partial run") { - REQUIRE(val == 1); - } -} \ No newline at end of file