Build and upload tool (arduino-build.sh): - Compile, upload, and monitor via arduino-cli - Device discovery with USB ID identification (--devices) - Persistent reconnecting serial monitor (--watch) - Split compile/upload workflow (--verify, --upload-only) - First-time setup wizard (--setup) - Comprehensive --help with troubleshooting and RedBoard specs Testable application architecture: - Hardware abstraction layer (lib/hal/) decouples logic from Arduino API - Google Mock HAL for unit tests (exact call verification) - Simulated HAL for system tests (GPIO state, virtual clock, I2C devices) - Example I2C temperature sensor simulator (TMP102) - Host-side test suite via CMake + Google Test (21 tests) Example sketch: - blink/ -- LED blink with button-controlled speed, wired through HAL
133 lines
3.8 KiB
C++
133 lines
3.8 KiB
C++
#include <gtest/gtest.h>
|
|
#include <gmock/gmock.h>
|
|
|
|
#include "hal.h"
|
|
#include "mock_hal.h"
|
|
#include "blink_app.h"
|
|
|
|
using ::testing::_;
|
|
using ::testing::AnyNumber;
|
|
using ::testing::Return;
|
|
using ::testing::InSequence;
|
|
using ::testing::HasSubstr;
|
|
|
|
// ============================================================================
|
|
// Unit Tests -- verify exact HAL interactions
|
|
// ============================================================================
|
|
|
|
class BlinkAppUnitTest : public ::testing::Test {
|
|
protected:
|
|
void SetUp() override {
|
|
// Allow timing and serial calls by default so tests can focus
|
|
// on the specific interactions they care about.
|
|
ON_CALL(mock_, millis()).WillByDefault(Return(0));
|
|
ON_CALL(mock_, digitalRead(_)).WillByDefault(Return(HIGH));
|
|
EXPECT_CALL(mock_, serialBegin(_)).Times(AnyNumber());
|
|
EXPECT_CALL(mock_, serialPrintln(_)).Times(AnyNumber());
|
|
EXPECT_CALL(mock_, millis()).Times(AnyNumber());
|
|
}
|
|
|
|
::testing::NiceMock<MockHal> mock_;
|
|
};
|
|
|
|
TEST_F(BlinkAppUnitTest, BeginConfiguresPins) {
|
|
BlinkApp app(&mock_, 13, 2);
|
|
|
|
EXPECT_CALL(mock_, pinMode(13, OUTPUT)).Times(1);
|
|
EXPECT_CALL(mock_, pinMode(2, INPUT_PULLUP)).Times(1);
|
|
EXPECT_CALL(mock_, serialBegin(115200)).Times(1);
|
|
|
|
app.begin();
|
|
}
|
|
|
|
TEST_F(BlinkAppUnitTest, StartsInSlowMode) {
|
|
BlinkApp app(&mock_);
|
|
app.begin();
|
|
|
|
EXPECT_FALSE(app.fastMode());
|
|
EXPECT_EQ(app.interval(), BlinkApp::SLOW_INTERVAL_MS);
|
|
}
|
|
|
|
TEST_F(BlinkAppUnitTest, TogglesLedAfterInterval) {
|
|
BlinkApp app(&mock_);
|
|
|
|
// begin() at t=0
|
|
ON_CALL(mock_, millis()).WillByDefault(Return(0));
|
|
app.begin();
|
|
|
|
// update() at t=500 should toggle LED
|
|
ON_CALL(mock_, millis()).WillByDefault(Return(500));
|
|
EXPECT_CALL(mock_, digitalWrite(13, _)).Times(1);
|
|
app.update();
|
|
}
|
|
|
|
TEST_F(BlinkAppUnitTest, DoesNotToggleBeforeInterval) {
|
|
BlinkApp app(&mock_);
|
|
|
|
ON_CALL(mock_, millis()).WillByDefault(Return(0));
|
|
app.begin();
|
|
|
|
// update() at t=499 -- not enough time, no toggle
|
|
ON_CALL(mock_, millis()).WillByDefault(Return(499));
|
|
EXPECT_CALL(mock_, digitalWrite(_, _)).Times(0);
|
|
app.update();
|
|
}
|
|
|
|
TEST_F(BlinkAppUnitTest, ButtonPressSwitchesToFastMode) {
|
|
BlinkApp app(&mock_, 13, 2);
|
|
|
|
ON_CALL(mock_, millis()).WillByDefault(Return(0));
|
|
ON_CALL(mock_, digitalRead(2)).WillByDefault(Return(HIGH));
|
|
app.begin();
|
|
|
|
// Simulate button press (falling edge: HIGH -> LOW)
|
|
ON_CALL(mock_, digitalRead(2)).WillByDefault(Return(LOW));
|
|
EXPECT_CALL(mock_, serialPrintln(HasSubstr("FAST"))).Times(1);
|
|
app.update();
|
|
|
|
EXPECT_TRUE(app.fastMode());
|
|
EXPECT_EQ(app.interval(), BlinkApp::FAST_INTERVAL_MS);
|
|
}
|
|
|
|
TEST_F(BlinkAppUnitTest, SecondButtonPressReturnsToSlowMode) {
|
|
BlinkApp app(&mock_, 13, 2);
|
|
|
|
ON_CALL(mock_, millis()).WillByDefault(Return(0));
|
|
ON_CALL(mock_, digitalRead(2)).WillByDefault(Return(HIGH));
|
|
app.begin();
|
|
|
|
// First press: fast mode
|
|
ON_CALL(mock_, digitalRead(2)).WillByDefault(Return(LOW));
|
|
app.update();
|
|
EXPECT_TRUE(app.fastMode());
|
|
|
|
// Release
|
|
ON_CALL(mock_, digitalRead(2)).WillByDefault(Return(HIGH));
|
|
app.update();
|
|
|
|
// Second press: back to slow
|
|
ON_CALL(mock_, digitalRead(2)).WillByDefault(Return(LOW));
|
|
EXPECT_CALL(mock_, serialPrintln(HasSubstr("SLOW"))).Times(1);
|
|
app.update();
|
|
|
|
EXPECT_FALSE(app.fastMode());
|
|
}
|
|
|
|
TEST_F(BlinkAppUnitTest, HoldingButtonDoesNotRepeatToggle) {
|
|
BlinkApp app(&mock_, 13, 2);
|
|
|
|
ON_CALL(mock_, millis()).WillByDefault(Return(0));
|
|
ON_CALL(mock_, digitalRead(2)).WillByDefault(Return(HIGH));
|
|
app.begin();
|
|
|
|
// Press and hold
|
|
ON_CALL(mock_, digitalRead(2)).WillByDefault(Return(LOW));
|
|
app.update();
|
|
EXPECT_TRUE(app.fastMode());
|
|
|
|
// Still held -- should NOT toggle again
|
|
app.update();
|
|
app.update();
|
|
EXPECT_TRUE(app.fastMode()); // still fast, not toggled back
|
|
}
|