[PR] Implement tests for Config, Database and ExamResultGenerator #3

Merged
wessel merged 3 commits from 1-grade-generator/exam_result_generator into 1-grade-generator/master 2025-10-02 13:33:33 +02:00
2 changed files with 244 additions and 0 deletions
Showing only changes of commit 14a50c0f03 - Show all commits

View File

@@ -108,6 +108,25 @@ if(BUILD_TESTING)
target_link_libraries(${PROJECT_NAME}_test_database_manager
pqxx pq tomlplusplus::tomlplusplus
)
# Add gtest for ExamResultGenerator
ament_add_gtest(${PROJECT_NAME}_test_exam_result_generator
test/ExamResultGenerator.test.cpp
src/exam_result_generator/nodes/ExamResultGenerator.cpp
src/database/DatabaseManager.cpp
src/config/ConfigManager.cpp
)
target_include_directories(${PROJECT_NAME}_test_exam_result_generator PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/src/exam_result_generator
)
ament_target_dependencies(${PROJECT_NAME}_test_exam_result_generator
rclcpp
g2_2025_interfaces
)
target_link_libraries(${PROJECT_NAME}_test_exam_result_generator
pqxx pq tomlplusplus::tomlplusplus
)
endif()
ament_package()

View File

@@ -0,0 +1,225 @@
#include <chrono>
#include <memory>
#include <rclcpp/rclcpp.hpp>
#include <gtest/gtest.h>
#include "exam_result_generator/nodes/ExamResultGenerator.hpp"
#include "g2_2025_interfaces/msg/exam.hpp"
#include "g2_2025_interfaces/msg/student.hpp"
using namespace std::chrono_literals;
using namespace assignments::one::exam_result_generator;
class ExamResultGeneratorTest : public ::testing::Test {
protected:
void SetUp() override {
rclcpp::init(0, nullptr);
// Create a test subscriber to capture published messages
test_node_ = std::make_shared<rclcpp::Node>("test_subscriber_node");
// Subscriber to capture exam results
exam_subscriber_ = test_node_->create_subscription<g2_2025_interfaces::msg::Exam>(
"exam_results", 10,
[this](const g2_2025_interfaces::msg::Exam::SharedPtr msg) {
received_exam_messages_.push_back(*msg);
}
);
// Publisher to send student management messages
student_publisher_ = 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);
}
);
received_exam_messages_.clear();
received_student_messages_.clear();
}
void TearDown() override {
exam_result_generator_.reset();
test_node_.reset();
rclcpp::shutdown();
}
void create_exam_result_generator() {
exam_result_generator_ = std::make_shared<ExamResultGenerator>();
}
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 (exam_result_generator_) {
rclcpp::spin_some(exam_result_generator_);
}
std::this_thread::sleep_for(10ms);
}
}
std::shared_ptr<rclcpp::Node> test_node_;
std::shared_ptr<ExamResultGenerator> exam_result_generator_;
rclcpp::Subscription<g2_2025_interfaces::msg::Exam>::SharedPtr exam_subscriber_;
rclcpp::Subscription<g2_2025_interfaces::msg::Student>::SharedPtr student_publisher_;
std::vector<g2_2025_interfaces::msg::Exam> received_exam_messages_;
std::vector<g2_2025_interfaces::msg::Student> received_student_messages_;
};
TEST_F(ExamResultGeneratorTest, ConstructorTest) {
ASSERT_NO_THROW({
create_exam_result_generator();
});
ASSERT_NE(exam_result_generator_, nullptr);
EXPECT_TRUE(exam_result_generator_->get_node_base_interface() != nullptr);
EXPECT_TRUE(exam_result_generator_->get_node_clock_interface() != nullptr);
EXPECT_TRUE(exam_result_generator_->get_node_logging_interface() != nullptr);
}
TEST_F(ExamResultGeneratorTest, PublisherCreationTest) {
create_exam_result_generator();
// Get topic names and types to verify publisher exists
auto topic_names_and_types = exam_result_generator_->get_topic_names_and_types();
bool exam_results_topic_found = false;
for (const auto& [topic_name, topic_types] : topic_names_and_types) {
if (topic_name == "/exam_results") {
exam_results_topic_found = true;
// Check if the topic type includes our Exam message type
bool correct_type = false;
for (const auto& type : topic_types) {
if (type == "g2_2025_interfaces/msg/Exam") {
correct_type = true;
break;
}
}
EXPECT_TRUE(correct_type) << "exam_results topic should have Exam message type";
break;
}
}
EXPECT_TRUE(exam_results_topic_found) << "exam_results topic should be published";
}
TEST_F(ExamResultGeneratorTest, SubscriberCreationTest) {
create_exam_result_generator();
// Get subscription names and types to verify subscriber exists
auto topic_names_and_types = exam_result_generator_->get_topic_names_and_types();
bool student_management_topic_found = false;
for (const auto& [topic_name, topic_types] : topic_names_and_types) {
if (topic_name == "/student_course_management") {
student_management_topic_found = true;
// Check if the topic type includes our Student message type
bool correct_type = false;
for (const auto& type : topic_types) {
if (type == "g2_2025_interfaces/msg/Student") {
correct_type = true;
break;
}
}
EXPECT_TRUE(correct_type) << "student_course_management topic should have Student message type";
break;
}
}
EXPECT_TRUE(student_management_topic_found) << "student_course_management topic should be subscribed";
}
TEST_F(ExamResultGeneratorTest, StudentManagementMessageHandlingTest) {
create_exam_result_generator();
// Create a publisher to send student management messages
auto student_mgmt_publisher = test_node_->create_publisher<g2_2025_interfaces::msg::Student>(
"student_course_management", 10
);
// Allow some time for publisher-subscriber connection
spin_some_time(500ms);
// Create and send a student management message
auto student_msg = std::make_shared<g2_2025_interfaces::msg::Student>();
student_msg->student_name = "Test Student";
student_msg->course_name = "Test Course";
student_msg->timestamp = test_node_->now();
student_mgmt_publisher->publish(*student_msg);
spin_some_time(500ms);
// Test passes if no crash occurred during message handling
SUCCEED();
}
TEST_F(ExamResultGeneratorTest, MultipleStudentMessagesTest) {
create_exam_result_generator();
auto student_mgmt_publisher = test_node_->create_publisher<g2_2025_interfaces::msg::Student>(
"student_course_management", 10
);
spin_some_time(500ms);
// Send multiple student management messages
std::vector<std::string> students = {"Alice", "Bob", "Charlie"};
std::vector<std::string> courses = {"Math", "Physics", "Chemistry"};
for (const auto& student : students) {
for (const auto& course : courses) {
auto msg = std::make_shared<g2_2025_interfaces::msg::Student>();
msg->student_name = student;
msg->course_name = course;
msg->timestamp = test_node_->now();
student_mgmt_publisher->publish(*msg);
spin_some_time(50ms);
}
}
spin_some_time(1s);
// Test passes if no crash occurred
SUCCEED();
}
// Test for message content validation (when messages are published)
TEST_F(ExamResultGeneratorTest, ExamMessageValidationTest) {
create_exam_result_generator();
// Create exam result subscriber to capture messages
auto exam_subscriber = test_node_->create_subscription<g2_2025_interfaces::msg::Exam>(
"exam_results", 10,
[this](const g2_2025_interfaces::msg::Exam::SharedPtr msg) {
received_exam_messages_.push_back(*msg);
}
);
spin_some_time(500ms);
// Spin for several timer cycles to potentially catch exam messages
auto start_time = std::chrono::steady_clock::now();
while (std::chrono::steady_clock::now() - start_time < 6s) {
spin_some_time(100ms);
for (const auto& exam_msg : received_exam_messages_) {
// Validate grade range (should be 10-100)
EXPECT_GE(exam_msg.result, 10) << "Grade should be >= 10";
EXPECT_LE(exam_msg.result, 100) << "Grade should be <= 100";
EXPECT_FALSE(exam_msg.course_name.empty()) << "Course name should not be empty";
}
}
// Test passes regardless of whether messages were received (depends on database connectivity)
SUCCEED();
}