generated from wessel/boilerplate
[PR] Implement tests for GradeCalculator and FinalGradeDeterminator, add documentation for aformentioned nodes #4
@@ -141,6 +141,25 @@ if(BUILD_TESTING)
|
||||
rclcpp
|
||||
g2_2025_interfaces
|
||||
)
|
||||
|
||||
# Add gtest for FinalGradeDeterminator
|
||||
ament_add_gtest(${PROJECT_NAME}_test_final_grade_determinator
|
||||
test/FinalGradeDeterminator.test.cpp
|
||||
src/final_grade_determinator/nodes/FinalGradeDeterminator.cpp
|
||||
src/database/DatabaseManager.cpp
|
||||
src/config/ConfigManager.cpp
|
||||
)
|
||||
target_include_directories(${PROJECT_NAME}_test_final_grade_determinator PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/final_grade_determinator
|
||||
)
|
||||
ament_target_dependencies(${PROJECT_NAME}_test_final_grade_determinator
|
||||
rclcpp
|
||||
g2_2025_interfaces
|
||||
)
|
||||
target_link_libraries(${PROJECT_NAME}_test_final_grade_determinator
|
||||
pqxx pq tomlplusplus::tomlplusplus
|
||||
)
|
||||
endif()
|
||||
|
||||
ament_package()
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <rclcpp/rclcpp.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "final_grade_determinator/nodes/FinalGradeDeterminator.hpp"
|
||||
#include "database/DatabaseManager.hpp"
|
||||
#include "g2_2025_interfaces/msg/exam.hpp"
|
||||
#include "g2_2025_interfaces/msg/student.hpp"
|
||||
#include "g2_2025_interfaces/srv/exams.hpp"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace assignments::one::final_grade_determinator;
|
||||
|
||||
class FinalGradeDeterminatorTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
|
vincent marked this conversation as resolved
Outdated
|
||||
rclcpp::init(0, nullptr);
|
||||
|
||||
test_node_ = std::make_shared<rclcpp::Node>("test_node");
|
||||
|
||||
// Subscriber to capture student messages
|
||||
student_subscriber_ = test_node_->create_subscription<g2_2025_interfaces::msg::Student>(
|
||||
"student_course_management", 10,
|
||||
[this](const g2_2025_interfaces::msg::Student::SharedPtr msg) {
|
||||
received_student_messages_.push_back(*msg);
|
||||
}
|
||||
);
|
||||
|
||||
// Publisher to send exam messages
|
||||
exam_publisher_ = test_node_->create_publisher<g2_2025_interfaces::msg::Exam>(
|
||||
"exam_results", 10
|
||||
);
|
||||
|
||||
// Mock service server for grade calculator
|
||||
|
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
|
||||
grade_calculator_service_ = test_node_->create_service<g2_2025_interfaces::srv::Exams>(
|
||||
"grade_calculator_service",
|
||||
[this](const g2_2025_interfaces::srv::Exams::Request::SharedPtr request,
|
||||
g2_2025_interfaces::srv::Exams::Response::SharedPtr response) {
|
||||
service_requests_.push_back(*request);
|
||||
// Mock calculation - average of grades
|
||||
int sum = 0;
|
||||
for (const auto& grade : request->exam_grades) {
|
||||
sum += grade;
|
||||
}
|
||||
response->result = sum / request->exam_grades.size();
|
||||
}
|
||||
);
|
||||
|
||||
received_student_messages_.clear();
|
||||
service_requests_.clear();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
final_grade_determinator_.reset();
|
||||
test_node_.reset();
|
||||
rclcpp::shutdown();
|
||||
}
|
||||
|
||||
void create_final_grade_determinator() {
|
||||
final_grade_determinator_ = std::make_shared<FinalGradeDeterminator>();
|
||||
}
|
||||
|
||||
void spin_some_time(std::chrono::milliseconds duration = 100ms) {
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
while (std::chrono::steady_clock::now() - start_time < duration) {
|
||||
rclcpp::spin_some(test_node_);
|
||||
if (final_grade_determinator_) {
|
||||
rclcpp::spin_some(final_grade_determinator_);
|
||||
}
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<rclcpp::Node> test_node_;
|
||||
std::shared_ptr<FinalGradeDeterminator> final_grade_determinator_;
|
||||
rclcpp::Subscription<g2_2025_interfaces::msg::Student>::SharedPtr student_subscriber_;
|
||||
rclcpp::Publisher<g2_2025_interfaces::msg::Exam>::SharedPtr exam_publisher_;
|
||||
rclcpp::Service<g2_2025_interfaces::srv::Exams>::SharedPtr grade_calculator_service_;
|
||||
|
||||
std::vector<g2_2025_interfaces::msg::Student> received_student_messages_;
|
||||
std::vector<g2_2025_interfaces::srv::Exams::Request> service_requests_;
|
||||
};
|
||||
|
||||
TEST_F(FinalGradeDeterminatorTest, ConstructorTest) {
|
||||
ASSERT_NO_THROW({
|
||||
create_final_grade_determinator();
|
||||
});
|
||||
|
||||
ASSERT_NE(final_grade_determinator_, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(FinalGradeDeterminatorTest, ExamCollectionTest) {
|
||||
create_final_grade_determinator();
|
||||
spin_some_time(500ms);
|
||||
|
||||
const std::string student_name = "Test Student";
|
||||
const std::string course_name = "Test Course";
|
||||
std::vector<int> grades = {80, 85, 90, 75, 95};
|
||||
|
||||
// Send 5 exam results (default collection amount)
|
||||
for (const auto& grade : grades) {
|
||||
auto msg = std::make_shared<g2_2025_interfaces::msg::Exam>();
|
||||
msg->student_name = student_name;
|
||||
msg->course_name = course_name;
|
||||
msg->result = grade;
|
||||
|
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)
|
||||
msg->timestamp = test_node_->now();
|
||||
|
||||
exam_publisher_->publish(*msg);
|
||||
spin_some_time(50ms);
|
||||
}
|
||||
|
||||
spin_some_time(1s);
|
||||
|
||||
// Verify service request was made
|
||||
ASSERT_FALSE(service_requests_.empty());
|
||||
EXPECT_EQ(service_requests_.back().student_name, student_name);
|
||||
EXPECT_EQ(service_requests_.back().course_name, course_name);
|
||||
EXPECT_EQ(service_requests_.back().exam_grades, grades);
|
||||
|
||||
// Verify student message was published
|
||||
ASSERT_FALSE(received_student_messages_.empty());
|
||||
EXPECT_EQ(received_student_messages_.back().student_name, student_name);
|
||||
EXPECT_EQ(received_student_messages_.back().course_name, course_name);
|
||||
}
|
||||
|
||||
TEST_F(FinalGradeDeterminatorTest, PartialExamCollectionTest) {
|
||||
create_final_grade_determinator();
|
||||
spin_some_time(500ms);
|
||||
|
||||
// Send only 3 exam results (less than required 5)
|
||||
const std::string student_name = "Test Student";
|
||||
const std::string course_name = "Test Course";
|
||||
std::vector<int> grades = {80, 85, 90};
|
||||
|
||||
for (const auto& grade : grades) {
|
||||
auto msg = std::make_shared<g2_2025_interfaces::msg::Exam>();
|
||||
msg->student_name = student_name;
|
||||
msg->course_name = course_name;
|
||||
msg->result = grade;
|
||||
msg->timestamp = test_node_->now();
|
||||
|
||||
exam_publisher_->publish(*msg);
|
||||
spin_some_time(50ms);
|
||||
}
|
||||
|
||||
spin_some_time(1s);
|
||||
|
||||
// Verify no service request was made yet
|
||||
EXPECT_TRUE(service_requests_.empty());
|
||||
// Verify no student message was published
|
||||
EXPECT_TRUE(received_student_messages_.empty());
|
||||
}
|
||||
|
||||
TEST_F(FinalGradeDeterminatorTest, MultipleStudentsTest) {
|
||||
create_final_grade_determinator();
|
||||
spin_some_time(500ms);
|
||||
|
||||
std::vector<std::string> students = {"Alice", "Bob"};
|
||||
const std::string course_name = "Test Course";
|
||||
std::vector<int> grades = {80, 85, 90, 75, 95};
|
||||
|
||||
// Send complete set of grades for each student
|
||||
for (const auto& student : students) {
|
||||
for (const auto& grade : grades) {
|
||||
auto msg = std::make_shared<g2_2025_interfaces::msg::Exam>();
|
||||
msg->student_name = student;
|
||||
msg->course_name = course_name;
|
||||
msg->result = grade;
|
||||
msg->timestamp = test_node_->now();
|
||||
|
||||
exam_publisher_->publish(*msg);
|
||||
spin_some_time(50ms);
|
||||
}
|
||||
}
|
||||
|
||||
spin_some_time(1s);
|
||||
|
||||
// Verify service requests were made for both students
|
||||
ASSERT_EQ(service_requests_.size(), 2);
|
||||
// Verify student messages were published for both students
|
||||
ASSERT_EQ(received_student_messages_.size(), 2);
|
||||
}
|
||||
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