How to Test F Prime Code: Component Unit Testing with Google Test

F Prime unit testing relies on a three-layer architecture where fprime-util impl --ut generates scaffold files, developers implement test logic in a Tester class derived from GTestBase, and the register_fprime_ut CMake macro integrates everything with Google Test for execution via fprime-util check.

Testing flight software in the nasa/fprime repository requires a structured workflow that verifies component behavior at the unit level. The framework automates the creation of test harnesses through autocoded base classes while providing assertion macros specific to telemetry, events, and command responses. This guide walks through the complete workflow from scaffold generation to coverage analysis.

Understanding the F Prime Unit Test Architecture

F Prime component testing follows a three-class hierarchy defined in the test/ut/ directory. This design separates auto-generated infrastructure from developer-written test logic.

TesterBase: The Port Mirroring Foundation

The TesterBase class is auto-generated for each component and mirrors its ports, internal histories, and parameter structures. It provides helper methods to interact with the component under test, including sendCOMMAND, invoke_to_<port>, paramSet_<Param>, and setTime. The base also manages history buffers through clearHistory(), which must be called between test actions to ensure isolated assertions.

GTestBase: Google Test Integration

The GTestBase class derives from TesterBase and introduces Google Test headers alongside F Prime-specific assertion macros. This layer is technically optional for platforms that cannot link Google Test, but it is standard for development environments. It provides macros like ASSERT_TLM_*, ASSERT_EVENTS_*, and ASSERT_CMD_RESPONSE_* that verify component outputs against expected values.

Tester: Developer Test Implementation

The Tester class is where you write concrete test cases. It inherits from GTestBase (or TesterBase if omitting Google Test) and contains methods like testNominal() and testErrorCase(). Here you implement the sequence of stimuli and assertions that define each test scenario. The class typically manages the component instance and calls connectPorts() and initComponents() during setup.

Generating Test Scaffolds with fprime-util

All F Prime unit tests begin with scaffold generation. Execute the following command in your component directory:

fprime-util impl --ut

This creates template files under test/ut/:

  • <Component>Tester.template.hpp and .cpp – Rename these to <Component>Tester.hpp and <Component>Tester.cpp to implement your test class
  • TestMain.cpp – The Google Test entry point containing main() and TEST() macros

The templates include placeholders for namespace declarations, constructor/destructor definitions, and the initial connectPorts() implementation required to wire the test harness to the component's input and output ports.

Implementing Test Cases and Helper Methods

After renaming the templates, populate the Tester class with test logic. The implementation typically follows this pattern:

#include "<Namespace>/<Component>/test/ut/<Component>Tester.hpp"
#include "<Namespace>/<Component>/<Component>.hpp"

namespace <Namespace> {

<Component>Tester::<Component>Tester() 
    : <Component>GTestBase("<Component>Tester", MAX_HISTORY_SIZE) {
    this->connectPorts();
    this->initComponents();
}

void <Component>Tester::testNominal() {
    // Clear history buffers before the test action
    this->clearHistory();
    
    // Send a command with opcode, argument, and instance ID
    this->sendCOMMAND_NAME(0, 42, 7);
    
    // Dispatch for active/queued components
    this->component.doDispatch();
    
    // Verify command response
    ASSERT_CMD_RESPONSE_SIZE(1);
    ASSERT_CMD_RESPONSE(0, 
        <Component>::OPCODE_COMMAND_NAME, 
        0, 
        Fw::CmdResponse::OK);
    
    // Verify telemetry channel
    ASSERT_TLM_ChannelName_SIZE(1);
    ASSERT_TLM_ChannelName(0, 42);
}

} // namespace <Namespace>

Key implementation details include calling clearHistory() to reset internal buffers, invoking doDispatch() for active components to process queued messages, and using the sendCOMMAND_* methods generated by the autocoder to stimulate command input ports.

Writing Test Assertions with F Prime Macros

F Prime extends Google Test with domain-specific assertions that verify flight software behavior. These macros check the component's history buffers rather than immediate return values:

  • Command Verification: ASSERT_CMD_RESPONSE(opcode, seq, response) validates command execution status
  • Telemetry Verification: ASSERT_TLM_<ChannelName>(index, expectedValue) checks telemetry values by channel name
  • Event Verification: ASSERT_EVENTS_<EventName>(index, args...) verifies event reporting
  • Port Call Verification: ASSERT_FROM_PORT_<PortName>_SIZE(count) ensures port invocations occurred

For components with complex state transitions, use the STest library to implement rule-based testing. Include STest/Random/Random.hpp to seed reproducible random data via STest::Random::seed() in your TestMain.cpp.

Registering Tests in CMakeLists.txt

Each component must register its unit test target using the register_fprime_ut macro in its CMakeLists.txt:

register_fprime_ut(
    AUTOCODER_INPUTS "${CMAKE_CURRENT_LIST_DIR}/<Component>.fpp"
    SOURCES
        "${CMAKE_CURRENT_LIST_DIR}/test/ut/<Component>TestMain.cpp"
        "${CMAKE_CURRENT_LIST_DIR}/test/ut/<Component>Tester.cpp"
    DEPENDS STest
    UT_AUTO_HELPERS  # Automatically generates connectPorts/initComponents

)

The AUTOCODER_INPUTS parameter links the component's FPP model files, while UT_AUTO_HELPERS instructs the build system to generate standard helper implementations. The DEPENDS STest line includes the testing utilities library for random data generation and rule-based test scenarios.

Building and Executing Unit Tests

Compile and run tests using the F Prime utility commands:


# Build all unit test binaries

fprime-util build --ut

# Execute all registered tests

fprime-util check

# Run with coverage analysis

fprime-util check --coverage

The --coverage flag generates gcov reports showing line and branch coverage for your component implementation. Tests execute as standard Google Test binaries, supporting filter flags and verbose output through the underlying test framework.

Summary

  • Scaffold Generation: Use fprime-util impl --ut to create Tester class templates and TestMain.cpp in the test/ut/ directory
  • Three-Layer Architecture: Inherit from GTestBase (which extends TesterBase) to access port helpers and assertion macros
  • Test Implementation: Write test methods in the Tester class, calling clearHistory() between actions and doDispatch() for active components
  • Assertion Macros: Use ASSERT_CMD_RESPONSE_*, ASSERT_TLM_*, and ASSERT_EVENTS_* to verify component outputs against recorded histories
  • Build Integration: Register tests with register_fprime_ut in CMakeLists.txt, including STest dependencies for advanced testing
  • Execution: Build with fprime-util build --ut and run with fprime-util check --coverage

Frequently Asked Questions

How do I generate the initial test files for an F Prime component?

Run fprime-util impl --ut from within the component directory. This command creates <Component>Tester.template.hpp, <Component>Tester.template.cpp, and TestMain.cpp under test/ut/. Rename the template files to remove the .template extension before implementing your test logic.

What is the difference between TesterBase and GTestBase?

TesterBase is an auto-generated class that mirrors your component's ports and provides methods like sendCOMMAND and clearHistory. GTestBase inherits from TesterBase and adds Google Test integration plus F Prime-specific assertion macros like ASSERT_TLM_*. You typically inherit from GTestBase to write tests, though you can use TesterBase directly on platforms without Google Test support.

How do I verify command handling in F Prime unit tests?

Invoke commands using the auto-generated sendCOMMAND_<Name> method, then call doDispatch() if testing an active component. Verify the response using ASSERT_CMD_RESPONSE_SIZE(1) followed by ASSERT_CMD_RESPONSE(index, opcode, seq, Fw::CmdResponse::OK) to confirm the command executed successfully.

How do I run F Prime unit tests with code coverage?

Execute fprime-util check --coverage after building with fprime-util build --ut. This runs all registered tests and generates gcov coverage reports showing which lines of your component implementation were exercised during the test suite execution.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →