Test fixtures
Last updated on 2026-06-30 | Edit this page
Overview
Questions
- I am writing the same setup code in every test — is there a better way?
- How do I share a complex starting state across many tests without tests interfering with each other?
Objectives
- Identify repeated setup code across tests as a signal that a fixture is appropriate
- Write a
TEST_F()fixture class with aSetUp()method for Histogram - Explain that
SetUp()runs fresh before every test and that tests do not share state - Refactor existing Histogram tests to use a fixture where appropriate
- Explain why construction tests should remain outside the fixture
Introduction
We’ve written quite an extensive series of tests for
Histogram, and many of them follow the same pattern
e.g.:
CPP
TEST(HistogramFill, WeightedFillProducesCorrectCounts) {
// 1. Construct histogram and comparison data
Histogram h(10, 0.0f, 1.0f);
std::vector<float> expected(10, 0.0f);
// 2. Prepare state
h.fill(0.1f, 0.5f);
h.fill(0.6f, 1.5f);
expected[0] = 0.5f;
expected[5] = 1.5f;
// 3. Run assertions
EXPECT_THAT(h.bin_counts(), ::testing::Pointwise(::testing::FloatEq(), expected));
}
As our suite grew, we were unwittingly introducing a maintenance
burden - if the Histogram constructor changes in the
future, we would have to update it in every test, if we wanted to uses
different binning/values, we’d also have to update those. There is a
slightly subtler problem too: when a test fails, we have to read through
the setup code to understand the starting state. Test
Fixtures are a solution to this when a set of test cases need a
common starting state.
Writing fixtures in GoogleTest
Fixtures have state, so are naturally programmed as classes in C++.
GoogleTest provide a base class ::testing::Test
from which our fixture needs to inherit. As this in our case this is
purely associated with the Histogram tests, we can put it
in tests/test_histogram.cpp before we add any test
cases:
CPP
//! \file test_histogram.cpp
#include "histogram.hpp"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
//! Setup a histogram and an expected result vector to test filling operations
class HistogramFillTest : public ::testing::Test {
protected:
Histogram h{10, 0.0f, 1.0f};
std::vector<float> expected(10, 0.0f);
};
// Tests follow
This is the close to the simplest possible fixture - just some basic
structured data. Let’s immediately use this in one of our
HistogramFill suite’s test cases:
CPP
TEST_F(HistogramFillTest, SingleFillLeavesOtherBinsZero)
{
h.fill(0.45f);
expected[4] = 1.0f;
EXPECT_THAT(h.bin_counts(), ::testing::ContainerEq(expected));
}
All we’ve done is use the TEST_F macro instead of
TEST. Here, the first argument must be the type name of the
Test Fixture class we want to use. Without going into the gory
details, TEST_F essentially creates a new class
HistogramFillTest_SingleFillLeavesOtherBinsZero that
inherits from HistogramFillTest and sets up things
so that running this test is roughly:
- Construct an instance
Xof this class (runs its constructor) - Run
X->SetUp()(see later) - Run the test body (what we coded between the
{...}) - Run
X->TearDown()(see later) - Destructs instance
X(runs its destructor)
This is why we declared our fixture’s data members as
protected - we can access them directly in any subclass. In
addition the subclassing and execution pattern mean that all fixture
state is isolated to a specific test case - note the
h and expected variables in each test case
will be different. That’s exactly what we had before with
TEST and setting things up in each test case, but we’ve
been able put that code in one place using the fixture mechanism. If we
ever wanted to change the default set up for Histogram
fill, we only need do it in one place.
Challenge
- Refactor your remaining
HistogramFilltest cases to use the fixture. Confirm they still work! - Could/Should you use this fixture, or another, for the
HistogramConstructionsuite?
- This should just be a matter of find/replace in the
HistogramFillsuites - We **shouldn’t* use the fixture, or create a new one, for the
HistogramConstructionsuite. AHistograminstance would already have been constructed by the fixture’s constructor, and we’d have no chance to actually put asserts aroundHistogram{10,0,1}.
It is possible to share state between all test cases in a test suite/fixture and at the program levell too. These are advanced topics for which care is needed.
More complex fixtures
Test fixtures give us two pairs of places we can do more complex setup and teardown of the state. We can either:
- setup the state either in the fixture class constructor and do teardown in the fixture’s destructor
- Override the virtual
SetUp()member function to setup the state, and override the virtualTearDown()member function to teardown the state (Note the capitalization!)
These give us freedom to create complex but repeatable and isolated states. Let’s use the second method to create a new
CPP
//! \file test_histogram.cpp
#include "histogram.hpp"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
class FilledHistogramTest : public ::testing::Test {
protected:
void SetUp() override {
// 10 bins over [0, 10): each bin covers 1 unit
// Fill the Ith bin I times
for (int bin = 0; bin < 10; ++bin)
for (int i = 0; i < bin+1; ++i)
h.fill(bin + 0.5f);
// Add one underflow entry
h.fill(-1.0f);
}
Histogram h{10, 0.0f, 10.0f};
};
We don’t need a TearDown in this fixture as
we’re not doing anything that would require it like, for example, memory
or temporary file management. We can then write a test case for this
fixture:
CPP
TEST_F(LinearHistogramTest, TotalEntryCount)
{
// 50 in-range + 1 underflow
EXPECT_EQ(h.n_entries(), 51);
}
Challenge
- Write
TEST_Fcases forn_underflow()andn_overflow(). - Write one that checks
bin_counts()usingPointwise. - Write one that checks the mean is as expected.
Before writing, work out by hand what the expected values should be — the fixture setup tells you everything you need.
CPP
// 1. Under/Overflow are trivial!
TEST_F(LinearHistogramTest, UnderflowCount)
{
EXPECT_EQ(h.n_underflow(), 1);
}
TEST_F(LinearHistogramTest, OverflowCount)
{
EXPECT_EQ(h.n_overflow(), 0);
}
// 2. Like before, we use `FloatEq`
TEST_F(LinearHistogramTest, BinCounts)
{
std::vector<float> expected{1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f};
EXPECT_THAT(h.bin_counts(), ::testing::Pointwise(::testing::FloatEq(), expected));
}
// 3. `EXPECT_NEAR` given large number of operations
TEST_F(LinearHistogramTest, MeanIsNearCentreOfRange)
{
// Nth bin filled N+1 times
// Nth bin center at 1.0*N + 0.5 -> Sum_{bins}(fill*bin_center) = 357.5
// 55 fills in total (Underflow fill of -1.0f is excluded from mean)
EXPECT_NEAR(h.mean(), 357.5f/55.0f, 0.01);
}
The key thing here is that whilst the tests are simple, they are only so because of the fixture. Imagine repeating that setup across five tests!
We now have a well-organised test suite: construction tests that
stand alone, behavioural tests grouped into suites, and a fixture that
gives tests needing a realistic starting state a clean, shared setup.
The next question is: how do we know whether this suite is thorough? We
have been writing tests based on our reading of Histogram’s
specification — but the specification may not have told us about every
branch in the implementation. In the next episode we will look
at the implementation itself for the first time, and use coverage tools
to find the gaps.
This is where we stop on introducing further GoogleTest capabilities. There is much more it can do, so do take the time to read through its documentation and see what else it can do.
- A fixture eliminates repeated setup code and makes the intended starting state of each test using that fixture explicit.
- SetUp() runs before every individual test — each test starts from a clean, identical state regardless of what other tests do
- Fixtures do not change what is being tested, only how the starting state is prepared
- Construction tests belong outside the fixture — the fixture assumes construction succeeds and tests behaviour from that point