Author and run GoogleTest 1.17+ for embedded C++ on ARM targets - TEST() / TEST_F() / TEST_P() / TYPED_TEST(), EXPECT_* vs ASSERT_* assertions, fixtures with SetUp() / TearDown(), value-parameterised tests, GoogleMock when paired, cross-compile with arm-none-eabi-g++, run on host or under QEMU via the qemu-system-test-runner skill, --gtest_filter / --gtest_output=xml:results.xml / --gtest_shuffle / --gtest_repeat command-line flags, and XML / JSON output parsing for CI. Use when the unit-under-test is C++ (modern C++17+) and the team wants the de-facto C++ test framework instead of the C-only Unity. For C use unity-test-framework-c; for pure mocks use cmock-reference.
74
93%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
GoogleTest is, per the README at
github.com/google/googletest,
"Google's C++ test framework" - an xUnit-style framework merged
with GoogleMock so the two ship together. The 1.17.x branch
"requires at least C++17". For embedded ARM use, the framework
runs anywhere a hosted C++ standard library is available - on
the host, under QEMU, and on Cortex-A Linux targets; for
Cortex-M without an OS, a minimal _write / _exit semihosting
stub is the bridge.
This skill wraps GoogleTest for embedded C++ work. Composes with:
embedded-coverage-strategy-reference
for gcov / llvm-cov instrumentation.qemu-system-test-runner
for running the binary on a virtual Cortex-M / Cortex-A.unity-test-framework-c;
for mock-heavy suites prefer the Ceedling stack via
cmock-reference.unity-test-framework-c.unity-test-framework-c).gtest_main supplies main()); author TEST() / TEST_F() cases - see Authoring.arm-none-eabi-g++ and run it under QEMU for architecture sanity - full toolchain, CMake, and build invariants in references/cross-compile-and-ci.md.--gtest_output=xml:results.xml; gate the host job on the JUnit XML and the QEMU job on the semihosting exit code.TEST_P), typed (TYPED_TEST), and death tests as coverage grows - see references/authoring-variants.md.The full single-test path (author to QEMU) is in Worked example below.
Per google.github.io/googletest/primer.html:
#include <gtest/gtest.h>
#include "ringbuffer.h"
TEST(RingbufferTest, EmptyOnInit) {
Ringbuffer<int, 8> rb;
EXPECT_TRUE(rb.empty());
EXPECT_EQ(0u, rb.size());
}
TEST(RingbufferTest, PushIncrementsSize) {
Ringbuffer<int, 8> rb;
rb.push(42);
EXPECT_EQ(1u, rb.size());
EXPECT_FALSE(rb.empty());
}TEST(SuiteName, TestName) registers an independent test; the
first argument is "the name of the test suite, and the second
argument is the test's name within the test suite" (Primer doc).
When several tests share setup, derive from ::testing::Test:
class RingbufferTest : public ::testing::Test {
protected:
void SetUp() override { rb.clear(); }
void TearDown() override { /* nothing */ }
Ringbuffer<int, 8> rb;
};
TEST_F(RingbufferTest, PopReturnsPushedValue) {
rb.push(7);
int v = 0;
EXPECT_TRUE(rb.pop(&v));
EXPECT_EQ(7, v);
}Per the Primer: "GoogleTest does not reuse the same test fixture
for multiple tests" - each TEST_F gets a fresh instance.
| Family | On failure | Use when |
|---|---|---|
EXPECT_* (e.g. EXPECT_EQ) | Records failure; continues | Each assertion is independent; collect all failures |
ASSERT_* (e.g. ASSERT_EQ) | Records failure; aborts current function | Subsequent assertions would crash (e.g. null pointer deref) |
Per the Primer: "Usually EXPECT_* are preferred, as they allow
more than one failure to be reported in a test." Use ASSERT_*
only when the next line would dereference a possibly-null pointer
returned from the previous check.
Value-parameterised (TEST_P), typed (TYPED_TEST), and death
tests live in references/authoring-variants.md.
One test from host to QEMU, end to end. It uses the
RingbufferTest cases from Authoring above.
Put those cases in test/ringbuffer_test.cpp.
Host build + run (fast inner loop) with the CMake config from references/cross-compile-and-ci.md:
cmake -S . -B build-host && cmake --build build-host
./build-host/ringbuffer_test
# [==========] Running 2 tests from 1 test suite.
# [ PASSED ] 2 tests.arm-none-eabi-g++ -mcpu=cortex-m4 -mthumb -O0 -g -std=c++17 \
-DGTEST_HAS_PTHREAD=0 --specs=rdimon.specs \
-I ext/googletest/googletest/include \
test/ringbuffer_test.cpp ext/googletest/googletest/src/gtest-all.cc \
-o ringbuffer_test.elf -lrdimon
qemu-system-arm -M mps2-an385 -cpu cortex-m4 -nographic \
-semihosting-config enable=on,target=native \
-kernel ringbuffer_test.elf
echo $? # gtest RUN_ALL_TESTS() return code, propagated via semihosting exitTwo flags make the target run work: -DGTEST_HAS_PTHREAD=0 is
mandatory on bare-metal M-profile (no pthreads, link fails
without it), and --specs=rdimon.specs pulls in the
librdimon
semihosting library so stdout and exit() reach QEMU. The
-kernel flag loads the ELF.
For a machine-readable gate, add --gtest_output=xml:results.xml
to the host run and count failing testcases - the XML schema
and parsing pattern are in
references/cross-compile-and-ci.md.
The flags every embedded run reaches for (per the Advanced Guide):
| Flag | Effect |
|---|---|
--gtest_filter=Pattern | :-separated wildcard list; supports *, ?, negative - |
--gtest_output=xml:results.xml / json:results.json | Machine-readable report for CI |
--gtest_shuffle | Random order each run - reveals inter-test dependencies |
--gtest_repeat=N | Repeat N times (-1 = infinite, for flake hunting) |
The full flag set (--gtest_break_on_failure,
--gtest_catch_exceptions=0, --gtest_brief,
--gtest_list_tests, and more), the XML / JSON output schema, and
the CI-gate parsing pattern are in
references/cross-compile-and-ci.md.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
ASSERT_EQ then ignore the abort and continue | Subsequent assertions undefined; can crash | Use EXPECT_EQ unless next line dereferences the value |
GTEST_HAS_PTHREAD=1 on bare-metal Cortex-M | Link fails: pthread symbols missing | Always -DGTEST_HAS_PTHREAD=0 on M-profile cross-builds |
| Death tests on bare-metal | No fork(); test hangs or aborts oddly | Skip death tests on M-profile; or use --gtest_filter=-*DeathTest* |
Linking gtest without gtest_main then no own main | "undefined reference to main" | Link gtest_main or write int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } |
| Test order dependence | --gtest_shuffle reveals; CI breaks intermittently | Each TEST_F must work in any order; reset state in SetUp |
| Optimised coverage build | -O2 collapses branches gcov can't see | -O0 -g for the coverage build per coverage-reference skill |
TEST_P without INSTANTIATE_TEST_SUITE_P | Compiles but never runs | Always pair |
| Mocking everything | Tests measure mocks not behaviour | Mock at the I/O boundary only - see cmock-reference |
unity-test-framework-c.TEST_P instance is Boundaries/WrapTest.WrapAtCapacity/4 -
the /4 is the param index, not the value. Use
INSTANTIATE_TEST_SUITE_P(..., ::testing::PrintToStringParamName())
to encode the value into the name.fork() - not available on bare-metal
or under most RTOSes. Per the Advanced Guide they run "in
separate child processes".--gtest_shuffle - the seed
is logged but rerunning with the same seed and a code change
produces a different schedule.std::function-based call recording -
on cross-compiles with -fno-exceptions you may need
-DGTEST_HAS_EXCEPTIONS=0 to compile cleanly.Cited inline. Foundational documents:
TEST_P / TYPED_TEST / death tests) in
references/authoring-variants.md;
full toolchain, output schema, and CI in
references/cross-compile-and-ci.md.unity-test-framework-c,
ceedling-build-runner,
cmock-reference,
qemu-system-test-runner,
embedded-coverage-strategy-reference,
hardware-in-loop-reference.