251 lines
6.9 KiB
Cheetah
251 lines
6.9 KiB
Cheetah
/*
|
|
* 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 <gtest/gtest.h>
|
|
#include <gmock/gmock.h>
|
|
|
|
#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<MockHal> 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;
|
|
}
|
|
}
|