generated from wessel/boilerplate
[PR] Implement tests for GradeCalculator and FinalGradeDeterminator, add documentation for aformentioned nodes #4
@@ -14,30 +14,31 @@ using namespace assignments::one::final_grade_determinator;
|
|||||||
|
|
||||||
namespace assignments::one {
|
namespace assignments::one {
|
||||||
|
|
||||||
class FakeDatabaseManager : public DatabaseManager {
|
struct MockStoredResult {
|
||||||
|
vincent marked this conversation as resolved
Outdated
|
|||||||
|
StudentCourse sc;
|
||||||
|
int exam_count;
|
||||||
|
int final_grade;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MockDatabaseManager : public DatabaseManager {
|
||||||
public:
|
public:
|
||||||
explicit FakeDatabaseManager(rclcpp::Logger logger = rclcpp::get_logger("fake_db"))
|
explicit MockDatabaseManager(rclcpp::Logger logger = rclcpp::get_logger("fake_db"))
|
||||||
: DatabaseManager(logger) {}
|
: DatabaseManager(logger) {
|
||||||
|
}
|
||||||
|
|
||||||
bool is_connected() const override {
|
bool is_connected() const override {
|
||||||
return true; // Always pretend we are connected
|
return true; // Always pretend we are connected
|
||||||
}
|
}
|
||||||
|
|
||||||
bool store_final_course_result(const StudentCourse& sc, int exam_count, int final_grade) override {
|
bool store_final_course_result(const StudentCourse& sc, int exam_count, int final_grade) override {
|
||||||
stored_results_.push_back({sc, exam_count, final_grade});
|
stored_results_.push_back({ sc, exam_count, final_grade });
|
||||||
return true; // Always succeed
|
return true; // Always succeed
|
||||||
|
vincent marked this conversation as resolved
Outdated
wessel
commented
I'd recommend keeping structs outside the class, but inside the namespace I'd recommend keeping structs outside the class, but inside the namespace
|
|||||||
}
|
}
|
||||||
|
|
||||||
void init_database() override {
|
void init_database() override {
|
||||||
// No-op for fake
|
} // no-op
|
||||||
}
|
|
||||||
|
|
||||||
struct StoredResult {
|
std::vector<MockStoredResult> stored_results_;
|
||||||
StudentCourse sc;
|
|
||||||
int exam_count;
|
|
||||||
int final_grade;
|
|
||||||
};
|
|
||||||
std::vector<StoredResult> stored_results_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace assignments::one
|
} // namespace assignments::one
|
||||||
@@ -66,14 +67,14 @@ protected:
|
|||||||
grade_calculator_service_ = test_node_->create_service<g2_2025_interfaces::srv::Exams>(
|
grade_calculator_service_ = test_node_->create_service<g2_2025_interfaces::srv::Exams>(
|
||||||
"grade_calculator_service",
|
"grade_calculator_service",
|
||||||
[this](const g2_2025_interfaces::srv::Exams::Request::SharedPtr request,
|
[this](const g2_2025_interfaces::srv::Exams::Request::SharedPtr request,
|
||||||
g2_2025_interfaces::srv::Exams::Response::SharedPtr response) {
|
g2_2025_interfaces::srv::Exams::Response::SharedPtr response) {
|
||||||
service_requests_.push_back(*request);
|
service_requests_.push_back(*request);
|
||||||
// Mock calculation - average of grades
|
// Mock calculation - average of grades
|
||||||
int sum = 0;
|
int sum = 0;
|
||||||
for (const auto& grade : request->exam_grades) {
|
for (const auto& grade : request->exam_grades) {
|
||||||
sum += grade;
|
sum += grade;
|
||||||
}
|
}
|
||||||
response->result = sum / request->exam_grades.size();
|
response->result = sum / request->exam_grades.size();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -89,21 +90,25 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void create_final_grade_determinator() {
|
void create_final_grade_determinator() {
|
||||||
fake_db_ = std::make_unique<assignments::one::FakeDatabaseManager>();
|
fake_db_ = std::make_unique<assignments::one::MockDatabaseManager>();
|
||||||
final_grade_determinator_ = std::make_shared<FinalGradeDeterminator>(std::move(fake_db_));
|
final_grade_determinator_ = std::make_shared<FinalGradeDeterminator>(std::move(fake_db_));
|
||||||
}
|
}
|
||||||
|
|
||||||
void spin_some_time(std::chrono::milliseconds duration = 100ms) {
|
void spin_some_time(std::chrono::milliseconds duration = 100ms) {
|
||||||
auto start_time = std::chrono::steady_clock::now();
|
auto start_time = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
while (std::chrono::steady_clock::now() - start_time < duration) {
|
while (std::chrono::steady_clock::now() - start_time < duration) {
|
||||||
rclcpp::spin_some(test_node_);
|
rclcpp::spin_some(test_node_);
|
||||||
|
|
||||||
if (final_grade_determinator_) {
|
if (final_grade_determinator_) {
|
||||||
rclcpp::spin_some(final_grade_determinator_);
|
rclcpp::spin_some(final_grade_determinator_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vincent marked this conversation as resolved
Outdated
wessel
commented
I personally prefer to split off with newlines in between the variables that are used together (or somewhat) I personally prefer to split off with newlines in between the variables that are used together (or somewhat)
|
|||||||
std::this_thread::sleep_for(10ms);
|
std::this_thread::sleep_for(10ms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::unique_ptr<assignments::one::FakeDatabaseManager> fake_db_;
|
|
||||||
|
std::unique_ptr<assignments::one::MockDatabaseManager> fake_db_;
|
||||||
std::shared_ptr<rclcpp::Node> test_node_;
|
std::shared_ptr<rclcpp::Node> test_node_;
|
||||||
std::shared_ptr<FinalGradeDeterminator> final_grade_determinator_;
|
std::shared_ptr<FinalGradeDeterminator> final_grade_determinator_;
|
||||||
rclcpp::Subscription<g2_2025_interfaces::msg::Student>::SharedPtr student_subscriber_;
|
rclcpp::Subscription<g2_2025_interfaces::msg::Student>::SharedPtr student_subscriber_;
|
||||||
@@ -113,6 +118,8 @@ protected:
|
|||||||
std::vector<g2_2025_interfaces::srv::Exams::Request> service_requests_;
|
std::vector<g2_2025_interfaces::srv::Exams::Request> service_requests_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ---- TEST CASES ----
|
||||||
|
|
||||||
TEST_F(FinalGradeDeterminatorTest, ConstructorTest) {
|
TEST_F(FinalGradeDeterminatorTest, ConstructorTest) {
|
||||||
ASSERT_NO_THROW({
|
ASSERT_NO_THROW({
|
||||||
create_final_grade_determinator();
|
create_final_grade_determinator();
|
||||||
@@ -127,7 +134,7 @@ TEST_F(FinalGradeDeterminatorTest, ExamCollectionTest) {
|
|||||||
|
|
||||||
const std::string student_name = "Test Student";
|
const std::string student_name = "Test Student";
|
||||||
const std::string course_name = "Test Course";
|
const std::string course_name = "Test Course";
|
||||||
std::vector<int> grades = {80, 85, 90, 75, 95};
|
std::vector<int> grades = { 80, 85, 90, 75, 95 };
|
||||||
|
|
||||||
// Send 5 exam results (default collection amount)
|
// Send 5 exam results (default collection amount)
|
||||||
for (const auto& grade : grades) {
|
for (const auto& grade : grades) {
|
||||||
@@ -162,7 +169,7 @@ TEST_F(FinalGradeDeterminatorTest, PartialExamCollectionTest) {
|
|||||||
// Send only 3 exam results (less than required 5)
|
// Send only 3 exam results (less than required 5)
|
||||||
const std::string student_name = "Test Student";
|
const std::string student_name = "Test Student";
|
||||||
const std::string course_name = "Test Course";
|
const std::string course_name = "Test Course";
|
||||||
std::vector<int> grades = {80, 85, 90};
|
std::vector<int> grades = { 80, 85, 90 };
|
||||||
|
|
||||||
for (const auto& grade : grades) {
|
for (const auto& grade : grades) {
|
||||||
auto msg = std::make_shared<g2_2025_interfaces::msg::Exam>();
|
auto msg = std::make_shared<g2_2025_interfaces::msg::Exam>();
|
||||||
@@ -187,9 +194,9 @@ TEST_F(FinalGradeDeterminatorTest, MultipleStudentsTest) {
|
|||||||
create_final_grade_determinator();
|
create_final_grade_determinator();
|
||||||
spin_some_time(500ms);
|
spin_some_time(500ms);
|
||||||
|
|
||||||
std::vector<std::string> students = {"Alice", "Bob"};
|
std::vector<std::string> students = { "Alice", "Bob" };
|
||||||
const std::string course_name = "Test Course";
|
const std::string course_name = "Test Course";
|
||||||
std::vector<int> grades = {80, 85, 90, 75, 95};
|
std::vector<int> grades = { 80, 85, 90, 75, 95 };
|
||||||
|
|
||||||
// Send complete set of grades for each student
|
// Send complete set of grades for each student
|
||||||
for (const auto& student : students) {
|
for (const auto& student : students) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class GradeCalculatorTest : public ::testing::Test
|
|||||||
protected:
|
protected:
|
||||||
static void SetUpTestSuite() {
|
static void SetUpTestSuite() {
|
||||||
int argc = 0;
|
int argc = 0;
|
||||||
char **argv = nullptr;
|
char** argv = nullptr;
|
||||||
rclcpp::init(argc, argv);
|
rclcpp::init(argc, argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ protected:
|
|||||||
client_node_.reset();
|
client_node_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
int call_service(const std::string &name, const std::vector<int> &grades)
|
int call_service(const std::string& name, const std::vector<int>& grades)
|
||||||
{
|
{
|
||||||
if (!client_->wait_for_service(5s)) {
|
if (!client_->wait_for_service(5s)) {
|
||||||
throw std::runtime_error("Service not available");
|
throw std::runtime_error("Service not available");
|
||||||
@@ -77,27 +77,22 @@ protected:
|
|||||||
|
|
||||||
// ---- TEST CASES ----
|
// ---- TEST CASES ----
|
||||||
|
|
||||||
TEST_F(GradeCalculatorTest, NormalAverage)
|
TEST_F(GradeCalculatorTest, NormalAverage) {
|
||||||
{
|
EXPECT_EQ(call_service("Alice", { 80, 90, 70 }), 80);
|
||||||
EXPECT_EQ(call_service("Alice", {80, 90, 70}), 80);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(GradeCalculatorTest, WesselBonus)
|
TEST_F(GradeCalculatorTest, WesselBonus) {
|
||||||
{
|
EXPECT_EQ(call_service("Wessel", { 80, 90, 70 }), 90);
|
||||||
EXPECT_EQ(call_service("Wessel", {80, 90, 70}), 90);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(GradeCalculatorTest, wesselBonus)
|
TEST_F(GradeCalculatorTest, wesselBonus) {
|
||||||
{
|
EXPECT_EQ(call_service("wessel", { 80, 90, 70 }), 90);
|
||||||
EXPECT_EQ(call_service("wessel", {80, 90, 70}), 90);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(GradeCalculatorTest, GradeTooHigh)
|
TEST_F(GradeCalculatorTest, GradeTooHigh) {
|
||||||
{
|
EXPECT_EQ(call_service("Wessel", { 100, 100, 100 }), 100);
|
||||||
EXPECT_EQ(call_service("Wessel", {100, 100, 100}), 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(GradeCalculatorTest, GradeTooLow)
|
TEST_F(GradeCalculatorTest, GradeTooLow) {
|
||||||
{
|
EXPECT_EQ(call_service("Alice", { 0, 0, 0 }), 10);
|
||||||
EXPECT_EQ(call_service("Alice", {0, 0, 0}), 10);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user
These classes are called "Mock" classes, and thus I'd prefix them as "MockDatabaseManager" or something like that.
Also interesting for your own information, GTest has a whole interface and setup around this; https://google.github.io/googletest/gmock_for_dummies.html