SDPT Lab 4
Lab 4: Software Verification - TDD, Mocking, and Code Coverage
Introduction
In traditional embedded systems, testing involves compiling code, flashing it to a board, and physically observing the hardware. This is slow, expensive, and scales poorly. Today, we implement "Shift-Left Testing." We will verify our C++ logic on our Host PC using automated frameworks before it ever touches a microcontroller.
Goals
- Integrate Google Test and Google Mock via CMake.
- Write Unit Tests using the AAA (Arrange, Act, Assert) pattern.
- Use Dependency Injection to decouple business logic from hardware registers.
- Simulate hardware interactions using Google Mock (GMock).
- Generate interactive HTML Code Coverage reports using
gcovr.
Part 1: Theoretical Reference
Use this section as a reference manual to complete the challenges in Part 2.
1.1 Google Test & Mock Cheat Sheet
Basic Assertions:
EXPECT_EQ(actual, expected);(Non-fatal equality check)EXPECT_FLOAT_EQ(actual, expected);(Safe float comparison)EXPECT_TRUE(condition);
Mocking an Interface (GMock):
class MockSensor : public ISensor {
public:
// MOCK_METHOD(ReturnType, MethodName, (Arguments), (override));
MOCK_METHOD(float, read_temperature, (), (override));
};
Setting Mock Expectations:
MockSensor fake_sensor;
// Program the mock to return 45.0 exactly twice.
EXPECT_CALL(fake_sensor, read_temperature())
.Times(2)
.WillRepeatedly(testing::Return(45.0f));
1.2 CMake Debug & Coverage Setup
Coverage mathematically requires a completely unoptimized binary mapping exactly to your source code lines.
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options(my_target PRIVATE -O0 -g --coverage)
target_link_options(my_target PRIVATE --coverage)
endif()
1.3 Running gcovr
Execute this from inside your build/ directory after running the tests:
gcovr -r ../ . --html --html-details -o coverage.html -e ".*_deps.*"
Part 2: The Challenges
Setup: The Raw Materials
You are building the safety controller for an industrial oven. It must read temperatures from an I2C sensor and trigger a physical alarm if it overheats.
Create a new workspace (sdpt-lab4). Create include/ and src/ directories. Populate them with these three files:
include/ISensor.h (The Hardware Interface)
#pragma once
// Pure virtual interface. We DO NOT implement hardware logic here.
class ISensor {
public:
virtual float read_temperature() = 0;
virtual ~ISensor() = default;
};
include/OvenController.h (The Logic Module)
#pragma once
#include "ISensor.h"
class OvenController {
private:
ISensor* temp_sensor;
bool alarm_active;
float max_safe_temp;
public:
// Constructor uses Dependency Injection!
OvenController(ISensor* sensor, float safe_limit = 250.0f);
void check_safety();
bool is_alarm_active() const;
// Pure math function
static float raw_adc_to_celsius(int adc_value);
};
src/OvenController.cpp (The Buggy Implementation)
#include "../include/OvenController.h"
OvenController::OvenController(ISensor* sensor, float safe_limit)
: temp_sensor(sensor), alarm_active(false), max_safe_temp(safe_limit) {}
void OvenController::check_safety() {
float current_temp = temp_sensor->read_temperature();
// BUG 1: It only triggers if STRICTLY greater. What if it equals the limit?
if (current_temp > max_safe_temp) {
alarm_active = true;
} else {
alarm_active = false;
}
}
bool OvenController::is_alarm_active() const {
return alarm_active;
}
float OvenController::raw_adc_to_celsius(int adc_value) {
// BUG 2: Integer division truncates the decimal! (e.g., 5/2 = 2, not 2.5)
return (adc_value / 4095) * 330.0f;
}
Challenge 1: The Verification Build System (Estimated: 20 mins)
We need a build system that fetches Google Test and compiles our code with coverage instrumentation. Your Task:
- Create a
CMakeLists.txtin the root directory. - Build a static library named
oven_libcontainingsrc/OvenController.cpp. - Use
FetchContentto pull Google Test (https://github.com/google/googletest.git, tagv1.13.0). - Create an executable named
unit_tests. (You will createtests/test_oven.cppin the next step). - Link
oven_lib,gtest_main, andgmockto your test executable. - Add the CMake logic to inject
-O0 -g --coverageONLY if the build type is "Debug".
Verification: Run cmake -DCMAKE_BUILD_TYPE=Debug .. inside your build folder. It must configure without errors and download GTest.
Challenge 2: Pure Math TDD (Estimated: 25 mins)
Let's catch the integer division bug using the Red-Green-Refactor cycle. Your Task:
- Create a
tests/directory and atests/test_oven.cppfile. - Include
<gtest/gtest.h>and yourOvenController.h. - Write a
TEST(MathTest, ConvertsADCToCelsius). - Use
EXPECT_FLOAT_EQto assert that an ADC value of2047(roughly half of 4095) returns approximately164.957f. - Compile and run
./unit_tests. (Watch it fail! This is the RED phase). - Refactor
OvenController.cppto fix the integer division bug. Recompile. (Watch it pass! This is the GREEN phase).
Challenge 3: Hardware Mocking (Estimated: 40 mins)
We need to test check_safety(), but we are on a PC. We have no I2C hardware. We must mock the ISensor.
Your Task:
- In your test file, include
<gmock/gmock.h>. - Create a
MockSensorclass that inherits fromISensor. UseMOCK_METHODto mockread_temperature. - Write a
TEST(SafetyTest, TriggersAlarmOnOverheat).- Arrange: Instantiate the MockSensor. Instantiate the OvenController, injecting the mock sensor into it with a safe limit of 250.0f.
- Expectation: Program the mock to return
260.0fwhen called. - Act: Call
check_safety(). - Assert: Expect that
is_alarm_active()is true.
- Write a second test:
TEST(SafetyTest, TriggersAlarmExactlyAtLimit).- Program the mock to return exactly
250.0f. - Run the test. It will FAIL due to Bug 1. Fix the production code so it triggers on `>=`.
- Program the mock to return exactly
Challenge 4: The Coverage Report (Estimated: 15 mins)
Did we test every branch of our code? Let's prove it mathematically. Your Task:
- Ensure you have run
./unit_testsat least once so the.gcdafiles are generated. - Run the
gcovrcommand provided in the Theoretical Reference section. - Open the resulting
coverage.htmlfile in your web browser.
Verification: Click on the OvenController.cpp file in the HTML report. You should see 100% line coverage and 100% branch coverage (green lines). If you have red lines, you are missing a test case!
Lab Evaluation
To receive full credit, call the professor/TA to your workstation and demonstrate:
- Run
./unit_tests. All tests must pass (Green). - Show your test code, explaining how Dependency Injection allowed you to pass the
MockSensorinto the controller. - Open your
coverage.htmlreport in the browser, demonstrating 100% branch coverage for theOvenController.cppfile.