/* * test_weather.cpp -- Weather station example tests. * * THIS FILE IS MANAGED BY ANVIL and will be updated by `anvil refresh`. * Do not edit -- put your own tests in test_unit.cpp and test_system.cpp. */ #include #include #include "mock_arduino.h" #include "hal.h" #include "mock_hal.h" #include "sim_hal.h" #include "tmp36_mock.h" #include "tmp36_sim.h" #include "{{PROJECT_NAME}}_app.h" using ::testing::_; using ::testing::AnyNumber; using ::testing::Return; using ::testing::HasSubstr; // ============================================================================ // Unit Tests -- verify WeatherApp behavior with mock sensor // ============================================================================ class WeatherUnitTest : public ::testing::Test { protected: void SetUp() override { ON_CALL(mock_, millis()).WillByDefault(Return(0)); EXPECT_CALL(mock_, serialBegin(_)).Times(AnyNumber()); EXPECT_CALL(mock_, serialPrint(_)).Times(AnyNumber()); EXPECT_CALL(mock_, serialPrintln(_)).Times(AnyNumber()); EXPECT_CALL(mock_, millis()).Times(AnyNumber()); } ::testing::NiceMock mock_; Tmp36Mock sensor_; }; TEST_F(WeatherUnitTest, BeginPrintsStartupMessage) { WeatherApp app(&mock_, &sensor_); EXPECT_CALL(mock_, serialBegin(115200)).Times(1); EXPECT_CALL(mock_, serialPrintln(HasSubstr("WeatherApp started"))).Times(1); app.begin(); } TEST_F(WeatherUnitTest, BeginTakesInitialReading) { sensor_.setTemperature(25.0f); WeatherApp app(&mock_, &sensor_); app.begin(); EXPECT_EQ(app.readCount(), 1); EXPECT_NEAR(app.lastCelsius(), 25.0f, 0.1f); } TEST_F(WeatherUnitTest, ReadsAfterInterval) { sensor_.setTemperature(20.0f); WeatherApp app(&mock_, &sensor_); ON_CALL(mock_, millis()).WillByDefault(Return(0)); app.begin(); EXPECT_EQ(app.readCount(), 1); // Not enough time yet ON_CALL(mock_, millis()).WillByDefault(Return(1999)); app.update(); EXPECT_EQ(app.readCount(), 1); // Now 2 seconds have passed ON_CALL(mock_, millis()).WillByDefault(Return(2000)); app.update(); EXPECT_EQ(app.readCount(), 2); } TEST_F(WeatherUnitTest, DoesNotReadTooEarly) { sensor_.setTemperature(22.0f); WeatherApp app(&mock_, &sensor_); ON_CALL(mock_, millis()).WillByDefault(Return(0)); app.begin(); ON_CALL(mock_, millis()).WillByDefault(Return(1500)); app.update(); EXPECT_EQ(app.readCount(), 1); } TEST_F(WeatherUnitTest, CelsiusToFahrenheitConversion) { sensor_.setTemperature(0.0f); WeatherApp app(&mock_, &sensor_); app.begin(); EXPECT_NEAR(app.lastCelsius(), 0.0f, 0.1f); EXPECT_NEAR(app.lastFahrenheit(), 32.0f, 0.1f); } TEST_F(WeatherUnitTest, BoilingPoint) { sensor_.setTemperature(100.0f); WeatherApp app(&mock_, &sensor_); app.begin(); EXPECT_NEAR(app.lastCelsius(), 100.0f, 0.1f); EXPECT_NEAR(app.lastFahrenheit(), 212.0f, 0.1f); } TEST_F(WeatherUnitTest, NegativeTemperature) { sensor_.setTemperature(-10.0f); WeatherApp app(&mock_, &sensor_); app.begin(); EXPECT_NEAR(app.lastCelsius(), -10.0f, 0.1f); EXPECT_NEAR(app.lastFahrenheit(), 14.0f, 0.1f); } TEST_F(WeatherUnitTest, PrintsTemperatureOnRead) { sensor_.setTemperature(25.0f); WeatherApp app(&mock_, &sensor_); EXPECT_CALL(mock_, serialPrint(HasSubstr("Temperature: "))).Times(1); EXPECT_CALL(mock_, serialPrintln(HasSubstr(" F)"))).Times(1); app.begin(); } TEST_F(WeatherUnitTest, MultipleReadingsTrackNewTemperature) { WeatherApp app(&mock_, &sensor_); sensor_.setTemperature(20.0f); ON_CALL(mock_, millis()).WillByDefault(Return(0)); app.begin(); EXPECT_NEAR(app.lastCelsius(), 20.0f, 0.1f); sensor_.setTemperature(30.0f); ON_CALL(mock_, millis()).WillByDefault(Return(2000)); app.update(); EXPECT_NEAR(app.lastCelsius(), 30.0f, 0.1f); EXPECT_EQ(app.readCount(), 2); } // ============================================================================ // System Tests -- exercise WeatherApp with simulated sensor and hardware // ============================================================================ class WeatherSystemTest : public ::testing::Test { protected: void SetUp() override { mock_arduino_reset(); sim_.setMillis(0); } SimHal sim_; Tmp36Sim sensor_{25.0f}; // 25 C base temperature }; TEST_F(WeatherSystemTest, StartsAndPrintsToSerial) { WeatherApp app(&sim_, &sensor_); app.begin(); std::string output = sim_.serialOutput(); EXPECT_NE(output.find("WeatherApp started"), std::string::npos); EXPECT_NE(output.find("Temperature:"), std::string::npos); } TEST_F(WeatherSystemTest, InitialReadingIsReasonable) { Tmp36Sim exact_sensor(25.0f, 0.0f); // zero noise WeatherApp app(&sim_, &exact_sensor); app.begin(); EXPECT_NEAR(app.lastCelsius(), 25.0f, 1.0f); EXPECT_EQ(app.readCount(), 1); } TEST_F(WeatherSystemTest, ReadsAtTwoSecondIntervals) { WeatherApp app(&sim_, &sensor_); app.begin(); EXPECT_EQ(app.readCount(), 1); // 1 second -- no new reading sim_.advanceMillis(1000); app.update(); EXPECT_EQ(app.readCount(), 1); // 2 seconds -- new reading sim_.advanceMillis(1000); app.update(); EXPECT_EQ(app.readCount(), 2); // 4 seconds -- another reading sim_.advanceMillis(2000); app.update(); EXPECT_EQ(app.readCount(), 3); } TEST_F(WeatherSystemTest, FiveMinuteRun) { WeatherApp app(&sim_, &sensor_); app.begin(); // Run 5 minutes at 100ms resolution for (int i = 0; i < 3000; ++i) { sim_.advanceMillis(100); app.update(); } // 5 minutes = 300 seconds / 2 second interval = 150 readings + 1 initial EXPECT_EQ(app.readCount(), 151); } TEST_F(WeatherSystemTest, TemperatureChangeMidRun) { Tmp36Sim sensor(20.0f, 0.0f); // start at 20 C, no noise WeatherApp app(&sim_, &sensor); app.begin(); EXPECT_NEAR(app.lastCelsius(), 20.0f, 1.0f); // Change temperature and wait for next reading sensor.setBaseTemperature(35.0f); sim_.advanceMillis(2000); app.update(); EXPECT_NEAR(app.lastCelsius(), 35.0f, 1.0f); } TEST_F(WeatherSystemTest, SerialOutputContainsFahrenheit) { Tmp36Sim exact_sensor(0.0f, 0.0f); // 0 C = 32 F WeatherApp app(&sim_, &exact_sensor); app.begin(); std::string output = sim_.serialOutput(); EXPECT_NE(output.find("32"), std::string::npos) << "Should contain 32 F for 0 C: " << output; } TEST_F(WeatherSystemTest, NoisyReadingsStayInRange) { Tmp36Sim noisy_sensor(25.0f, 2.0f); // +/- 2 C noise noisy_sensor.setSeed(42); WeatherApp app(&sim_, &noisy_sensor); for (int i = 0; i < 20; ++i) { sim_.setMillis(i * 2000); if (i == 0) app.begin(); else app.update(); float c = app.lastCelsius(); EXPECT_GE(c, 20.0f) << "Reading " << i << " too low: " << c; EXPECT_LE(c, 30.0f) << "Reading " << i << " too high: " << c; } }