generated from wessel/boilerplate
[PR] Implement tests for GradeCalculator and FinalGradeDeterminator, add documentation for aformentioned nodes #4
2
.gitignore
vendored
2
.gitignore
vendored
@@ -33,5 +33,7 @@ qtcreator-*
|
|||||||
COLCON_IGNORE
|
COLCON_IGNORE
|
||||||
AMENT_IGNORE
|
AMENT_IGNORE
|
||||||
|
|
||||||
|
.vscode
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/ros2
|
# End of https://www.toptal.com/developers/gitignore/api/ros2
|
||||||
|
|
||||||
|
|||||||
35
doc/nodes/FinalGradeDeterminator.md
Normal file
35
doc/nodes/FinalGradeDeterminator.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# FinalGradeDeterminator (`assignments::one::final_grade_determinator`)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
The `FinalGradeDeterminator` node collects exam results for student-course combinations, triggers grade calculation when enough results are gathered, and stores final grades in the database. It interacts with ROS2 publishers, subscribers, and service clients to manage the grading workflow.
|
||||||
|
|
||||||
|
#### Implementation Details
|
||||||
|
|
||||||
|
**Constructor**
|
||||||
|
```cpp
|
||||||
|
FinalGradeDeterminator()
|
||||||
|
```
|
||||||
|
- Initializes ROS2 node with name `final_grade_determinator`
|
||||||
|
- Declares and retrieves `grade_collection_amount` parameter
|
||||||
|
- Sets up `DatabaseManager` <!--(uses provided or creates new instance) std::unique_ptr<DatabaseManager> db_manager -->
|
||||||
|
- Creates publisher for student course management
|
||||||
|
- Subscribes to exam results topic
|
||||||
|
- Initializes service client for grade calculation
|
||||||
|
|
||||||
|
**Core Functions**
|
||||||
|
|
||||||
|
**`void exam_results_callback(const g2_2025_interfaces::msg::Exam::SharedPtr msg)`**
|
||||||
|
- Updates internal map with received exam result for student-course combo
|
||||||
|
- Checks if enough results have been collected
|
||||||
|
- Triggers grade calculation request when threshold is met
|
||||||
|
|
||||||
|
**`void grade_calculator_request(StudentCourse combo)`**
|
||||||
|
- Waits for grade calculator service to be available
|
||||||
|
- Sends async request with collected exam grades for the student-course combination
|
||||||
|
- Uses callback to handle service response
|
||||||
|
|
||||||
|
**`void grade_calculator_response(rclcpp::Client<g2_2025_interfaces::srv::Exams>::SharedFuture future, StudentCourse studentCourseCombo)`**
|
||||||
|
- Verifies database connection
|
||||||
|
- Publishes final student message to ROS2 topic
|
||||||
|
- Logs final grade information
|
||||||
|
- Stores final course result in database
|
||||||
25
doc/nodes/GradeCalculator.md
Normal file
25
doc/nodes/GradeCalculator.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# GradeCalculator (`assignments::one::grade_calculator::GradeCalculator`)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
The `GradeCalculator` node provides a ROS2 service for calculating student exam grades. It processes exam scores, applies custom logic for specific student names, and ensures grade results are within valid bounds.
|
||||||
|
|
||||||
|
#### Implementation Details
|
||||||
|
|
||||||
|
**Constructor**
|
||||||
|
```cpp
|
||||||
|
GradeCalculator()
|
||||||
|
```
|
||||||
|
- Initializes ROS2 node with name `grade_calculator`
|
||||||
|
- Creates a ROS2 service server for `grade_calculator_service`
|
||||||
|
- Binds the service callback to handle grade calculation requests
|
||||||
|
- Logs service startup
|
||||||
|
|
||||||
|
**Core Functions**
|
||||||
|
|
||||||
|
**`void grade_calculator_callback(const Exams::Request::SharedPtr request, const Exams::Response::SharedPtr response)`**
|
||||||
|
- Checks if exam grades are provided
|
||||||
|
- Calculates the total and average of exam grades
|
||||||
|
- Converts student name to lowercase for comparison
|
||||||
|
- Adds a bonus of 10 points if the student name is "wessel"
|
||||||
|
- Ensures the final grade is clamped between 10 and 100
|
||||||
|
- Sends the calculated grade back through the service response
|
||||||
@@ -127,6 +127,39 @@ if(BUILD_TESTING)
|
|||||||
target_link_libraries(${PROJECT_NAME}_test_exam_result_generator
|
target_link_libraries(${PROJECT_NAME}_test_exam_result_generator
|
||||||
pqxx pq tomlplusplus::tomlplusplus
|
pqxx pq tomlplusplus::tomlplusplus
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Add gtest for GradeCalculator
|
||||||
|
ament_add_gtest(${PROJECT_NAME}_test_grade_calculator
|
||||||
|
test/GradeCalculator.test.cpp
|
||||||
|
src/grade_calculator/nodes/GradeCalculator.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(${PROJECT_NAME}_test_grade_calculator PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/grade_calculator
|
||||||
|
)
|
||||||
|
ament_target_dependencies(${PROJECT_NAME}_test_grade_calculator
|
||||||
|
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()
|
endif()
|
||||||
|
|
||||||
ament_package()
|
ament_package()
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ public:
|
|||||||
~DatabaseManager() = default;
|
~DatabaseManager() = default;
|
||||||
|
|
||||||
bool connect(const std::string& connection_string);
|
bool connect(const std::string& connection_string);
|
||||||
bool is_connected() const;
|
virtual bool is_connected() const;
|
||||||
|
|
||||||
// Table operations
|
// Table operations
|
||||||
void init_database();
|
virtual void init_database();
|
||||||
void create_tables();
|
void create_tables();
|
||||||
void insert_sample_data();
|
void insert_sample_data();
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ public:
|
|||||||
std::vector<StudentCourse> queue_pending_combinations();
|
std::vector<StudentCourse> queue_pending_combinations();
|
||||||
bool store_exam_result(const std::string& student_name, const std::string& course_name, int grade);
|
bool store_exam_result(const std::string& student_name, const std::string& course_name, int grade);
|
||||||
bool enroll_student_into_course(const StudentCourse& sc);
|
bool enroll_student_into_course(const StudentCourse& sc);
|
||||||
bool store_final_course_result(const StudentCourse& sc, int exam_count, int final_grade);
|
virtual bool store_final_course_result(const StudentCourse& sc, int exam_count, int final_grade);
|
||||||
|
|
||||||
int get_final_course_grade(const StudentCourse& sc);
|
int get_final_course_grade(const StudentCourse& sc);
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,16 @@
|
|||||||
|
|
||||||
namespace assignments::one::final_grade_determinator {
|
namespace assignments::one::final_grade_determinator {
|
||||||
|
|
||||||
FinalGradeDeterminator::FinalGradeDeterminator() : Node("final_grade_determinator") {
|
FinalGradeDeterminator::FinalGradeDeterminator(std::unique_ptr<DatabaseManager> db_manager) : Node("final_grade_determinator") {
|
||||||
this->declare_parameter("grade_collection_amount", 5);
|
this->declare_parameter("grade_collection_amount", 5);
|
||||||
grade_collection_amount_ = this->get_parameter("grade_collection_amount").as_int();
|
grade_collection_amount_ = this->get_parameter("grade_collection_amount").as_int();
|
||||||
|
|
||||||
|
// Make db_manager optional for testing purposes
|
||||||
|
if (db_manager) {
|
||||||
|
db_manager_ = std::move(db_manager);
|
||||||
|
} else {
|
||||||
db_manager_ = std::make_unique<DatabaseManager>(this->get_logger());
|
db_manager_ = std::make_unique<DatabaseManager>(this->get_logger());
|
||||||
|
}
|
||||||
// Create publisher for exam results
|
// Create publisher for exam results
|
||||||
student_publisher_ = this->create_publisher<g2_2025_interfaces::msg::Student>(
|
student_publisher_ = this->create_publisher<g2_2025_interfaces::msg::Student>(
|
||||||
"student_course_management", 10
|
"student_course_management", 10
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace assignments::one::final_grade_determinator {
|
|||||||
|
|
||||||
class FinalGradeDeterminator : public rclcpp::Node {
|
class FinalGradeDeterminator : public rclcpp::Node {
|
||||||
public:
|
public:
|
||||||
FinalGradeDeterminator();
|
FinalGradeDeterminator(std::unique_ptr<DatabaseManager> db_manager = nullptr);
|
||||||
private:
|
private:
|
||||||
rclcpp::Subscription<g2_2025_interfaces::msg::Exam>::SharedPtr exam_subscriber_;
|
rclcpp::Subscription<g2_2025_interfaces::msg::Exam>::SharedPtr exam_subscriber_;
|
||||||
rclcpp::Publisher<g2_2025_interfaces::msg::Student>::SharedPtr student_publisher_;
|
rclcpp::Publisher<g2_2025_interfaces::msg::Student>::SharedPtr student_publisher_;
|
||||||
|
|||||||
@@ -0,0 +1,221 @@
|
|||||||
|
#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;
|
||||||
|
|
||||||
|
namespace assignments::one {
|
||||||
|
|
||||||
|
struct MockStoredResult {
|
||||||
|
StudentCourse sc;
|
||||||
|
int exam_count;
|
||||||
|
int final_grade;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MockDatabaseManager : public DatabaseManager {
|
||||||
|
public:
|
||||||
|
explicit MockDatabaseManager(rclcpp::Logger logger = rclcpp::get_logger("fake_db"))
|
||||||
|
: DatabaseManager(logger) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_connected() const override {
|
||||||
|
return true; // Always pretend we are connected
|
||||||
|
}
|
||||||
|
|
||||||
|
bool store_final_course_result(const StudentCourse& sc, int exam_count, int final_grade) override {
|
||||||
|
stored_results_.push_back({ sc, exam_count, final_grade });
|
||||||
|
return true; // Always succeed
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_database() override {
|
||||||
|
} // no-op
|
||||||
|
|
||||||
|
std::vector<MockStoredResult> stored_results_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace assignments::one
|
||||||
|
|
||||||
|
class FinalGradeDeterminatorTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
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
|
||||||
|
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();
|
||||||
|
fake_db_.reset();
|
||||||
|
test_node_.reset();
|
||||||
|
rclcpp::shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void create_final_grade_determinator() {
|
||||||
|
fake_db_ = std::make_unique<assignments::one::MockDatabaseManager>();
|
||||||
|
final_grade_determinator_ = std::make_shared<FinalGradeDeterminator>(std::move(fake_db_));
|
||||||
|
}
|
||||||
|
|
||||||
|
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::unique_ptr<assignments::one::MockDatabaseManager> fake_db_;
|
||||||
|
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 CASES ----
|
||||||
|
|
||||||
|
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;
|
||||||
|
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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <rclcpp/rclcpp.hpp>
|
||||||
|
#include <thread>
|
||||||
|
#include <g2_2025_interfaces/srv/exams.hpp>
|
||||||
|
#include "grade_calculator/nodes/GradeCalculator.hpp"
|
||||||
|
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
using assignments::one::grade_calculator::GradeCalculator;
|
||||||
|
|
||||||
|
class GradeCalculatorTest : public ::testing::Test
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
static void SetUpTestSuite() {
|
||||||
|
int argc = 0;
|
||||||
|
char** argv = nullptr;
|
||||||
|
rclcpp::init(argc, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void TearDownTestSuite() {
|
||||||
|
rclcpp::shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetUp() override
|
||||||
|
{
|
||||||
|
// Start GradeCalculator node
|
||||||
|
grade_calculator_node_ = std::make_shared<GradeCalculator>();
|
||||||
|
executor_ = std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
|
||||||
|
executor_->add_node(grade_calculator_node_);
|
||||||
|
|
||||||
|
// Spin in background thread
|
||||||
|
spin_thread_ = std::thread([this]() {
|
||||||
|
executor_->spin();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create client node
|
||||||
|
client_node_ = rclcpp::Node::make_shared("grade_calculator_test_client");
|
||||||
|
client_ = client_node_->create_client<g2_2025_interfaces::srv::Exams>("grade_calculator_service");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override
|
||||||
|
{
|
||||||
|
executor_->cancel();
|
||||||
|
if (spin_thread_.joinable()) {
|
||||||
|
spin_thread_.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
grade_calculator_node_.reset();
|
||||||
|
client_.reset();
|
||||||
|
client_node_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
int call_service(const std::string& name, const std::vector<int>& grades)
|
||||||
|
{
|
||||||
|
if (!client_->wait_for_service(5s)) {
|
||||||
|
throw std::runtime_error("Service not available");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto request = std::make_shared<g2_2025_interfaces::srv::Exams::Request>();
|
||||||
|
request->student_name = name;
|
||||||
|
request->exam_grades = grades;
|
||||||
|
|
||||||
|
auto future = client_->async_send_request(request);
|
||||||
|
|
||||||
|
if (rclcpp::spin_until_future_complete(client_node_, future) == rclcpp::FutureReturnCode::SUCCESS) {
|
||||||
|
return future.get()->result;
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("Service call failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<rclcpp::executors::SingleThreadedExecutor> executor_;
|
||||||
|
std::shared_ptr<GradeCalculator> grade_calculator_node_;
|
||||||
|
std::thread spin_thread_;
|
||||||
|
rclcpp::Node::SharedPtr client_node_;
|
||||||
|
rclcpp::Client<g2_2025_interfaces::srv::Exams>::SharedPtr client_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- TEST CASES ----
|
||||||
|
|
||||||
|
TEST_F(GradeCalculatorTest, NormalAverage) {
|
||||||
|
EXPECT_EQ(call_service("Alice", { 80, 90, 70 }), 80);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GradeCalculatorTest, WesselBonus) {
|
||||||
|
EXPECT_EQ(call_service("Wessel", { 80, 90, 70 }), 90);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GradeCalculatorTest, wesselBonus) {
|
||||||
|
EXPECT_EQ(call_service("wessel", { 80, 90, 70 }), 90);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GradeCalculatorTest, GradeTooHigh) {
|
||||||
|
EXPECT_EQ(call_service("Wessel", { 100, 100, 100 }), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GradeCalculatorTest, GradeTooLow) {
|
||||||
|
EXPECT_EQ(call_service("Alice", { 0, 0, 0 }), 10);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user