添加单元测试

This commit is contained in:
Chenxi
2026-06-06 15:04:04 +08:00
parent 834a6ab2af
commit fa6fdfe95e

View File

@@ -1,3 +1,6 @@
// XYParser API 单元测试文件
// 测试 XYParser 库的核心功能,包括解析器创建、错误处理、帧解析等
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "../XYParserApi.h" #include "../XYParserApi.h"
@@ -7,12 +10,18 @@
#include <string> #include <string>
#include <vector> #include <vector>
// 匿名命名空间,包含测试辅助代码
namespace { namespace {
/// ParserGuard 类RAII 封装,确保解析器资源自动释放
/// 当对象生命周期结束时自动调用 XYParser_DestroyParser 释放资源
class ParserGuard { class ParserGuard {
public: public:
/// 构造函数,接管解析器句柄的所有权
/// @param handle XYParser 解析器句柄
explicit ParserGuard(XYParserHandle handle) : handle_(handle) {} explicit ParserGuard(XYParserHandle handle) : handle_(handle) {}
/// 析构函数,自动释放解析器资源
~ParserGuard() ~ParserGuard()
{ {
if (handle_ != nullptr) { if (handle_ != nullptr) {
@@ -20,22 +29,29 @@ public:
} }
} }
/// 获取解析器句柄
/// @return XYParser 解析器句柄
XYParserHandle get() const XYParserHandle get() const
{ {
return handle_; return handle_;
} }
private: private:
XYParserHandle handle_; XYParserHandle handle_; ///< 解析器句柄
}; };
/// 构建最小帧数据的辅助函数
/// 生成符合 XYParser 协议格式的测试帧数据
/// @param channel_count 通道数量
/// @return 包含完整帧数据的字节向量
std::vector<std::uint8_t> BuildMinimalFrame(std::uint8_t channel_count) std::vector<std::uint8_t> BuildMinimalFrame(std::uint8_t channel_count)
{ {
constexpr std::size_t kSamplesPerFrame = 5; constexpr std::size_t kSamplesPerFrame = 5; ///< 每帧采样数
constexpr std::uint8_t kHeader = 0xAA; constexpr std::uint8_t kHeader = 0xAA; ///< 帧头标记
constexpr std::uint8_t kTail = 0x55; constexpr std::uint8_t kTail = 0x55; ///< 帧尾标记
constexpr std::size_t kTagLen = 25; constexpr std::size_t kTagLen = 25; ///< 标签长度
// 计算帧结构大小
const std::size_t sample_bytes = static_cast<std::size_t>(channel_count) * 3 + 2; const std::size_t sample_bytes = static_cast<std::size_t>(channel_count) * 3 + 2;
const std::uint16_t payload_length = static_cast<std::uint16_t>(sample_bytes * kSamplesPerFrame); const std::uint16_t payload_length = static_cast<std::uint16_t>(sample_bytes * kSamplesPerFrame);
const std::size_t frame_size = 1 + kTagLen + payload_length + 2; const std::size_t frame_size = 1 + kTagLen + payload_length + 2;
@@ -43,31 +59,39 @@ std::vector<std::uint8_t> BuildMinimalFrame(std::uint8_t channel_count)
std::vector<std::uint8_t> frame(frame_size, 0); std::vector<std::uint8_t> frame(frame_size, 0);
std::size_t offset = 0; std::size_t offset = 0;
// 写入帧头
frame[offset++] = kHeader; frame[offset++] = kHeader;
// 写入标签数据(版本号等)
frame[offset++] = 0x01; frame[offset++] = 0x01;
frame[offset++] = 0x00; frame[offset++] = 0x00;
frame[offset++] = 0x00; frame[offset++] = 0x00;
frame[offset++] = 0x00; frame[offset++] = 0x00;
// 写入负载长度(大端序)
frame[offset++] = static_cast<std::uint8_t>((payload_length >> 8) & 0xFF); frame[offset++] = static_cast<std::uint8_t>((payload_length >> 8) & 0xFF);
frame[offset++] = static_cast<std::uint8_t>(payload_length & 0xFF); frame[offset++] = static_cast<std::uint8_t>(payload_length & 0xFF);
// 写入电池电量和通道数
frame[offset++] = 95; frame[offset++] = 95;
frame[offset++] = channel_count; frame[offset++] = channel_count;
// 跳过保留字段
offset += 2 + 2 + 2 + 2 + 2 + 6; offset += 2 + 2 + 2 + 2 + 2 + 6;
// 写入采样数据
for (std::size_t sample = 0; sample < kSamplesPerFrame; ++sample) { for (std::size_t sample = 0; sample < kSamplesPerFrame; ++sample) {
for (std::size_t channel = 0; channel < channel_count; ++channel) { for (std::size_t channel = 0; channel < channel_count; ++channel) {
frame[offset++] = 0x00; frame[offset++] = 0x00;
frame[offset++] = 0x00; frame[offset++] = 0x00;
frame[offset++] = static_cast<std::uint8_t>(sample + channel + 1); frame[offset++] = static_cast<std::uint8_t>(sample + channel + 1);
} }
// 每个采样后的额外字节
frame[offset++] = 0x00; frame[offset++] = 0x00;
frame[offset++] = 0x00; frame[offset++] = 0x00;
} }
// 写入帧尾
frame[offset++] = 0x00; frame[offset++] = 0x00;
frame[offset++] = kTail; frame[offset++] = kTail;
frame[offset++] = kTail; frame[offset++] = kTail;
@@ -77,24 +101,32 @@ std::vector<std::uint8_t> BuildMinimalFrame(std::uint8_t channel_count)
} // namespace } // namespace
/// 测试:创建解析器时拒绝不支持的通道数
TEST(XYParserApiTests, CreateParserRejectsUnsupportedChannelCount) TEST(XYParserApiTests, CreateParserRejectsUnsupportedChannelCount)
{ {
// 7 通道是不支持的配置,应返回 nullptr
EXPECT_EQ(XYParser_CreateParser(7), nullptr); EXPECT_EQ(XYParser_CreateParser(7), nullptr);
} }
/// 测试:对空解析器句柄调用 GetLastError 应返回正确错误信息
TEST(XYParserApiTests, GetLastErrorReturnsMessageForNullParser) TEST(XYParserApiTests, GetLastErrorReturnsMessageForNullParser)
{ {
// 传入 nullptr 应返回 "invalid parser handle"
EXPECT_EQ(std::string(XYParser_GetLastError(nullptr)), std::string("invalid parser handle")); EXPECT_EQ(std::string(XYParser_GetLastError(nullptr)), std::string("invalid parser handle"));
} }
/// 测试Feed 函数能正确解析完整的 8 通道帧
TEST(XYParserApiTests, FeedParsesAComplete8ChannelFrame) TEST(XYParserApiTests, FeedParsesAComplete8ChannelFrame)
{ {
// 创建 8 通道解析器
ParserGuard parser(XYParser_CreateParser(8)); ParserGuard parser(XYParser_CreateParser(8));
ASSERT_NE(parser.get(), nullptr); ASSERT_NE(parser.get(), nullptr);
// 设置 ADC 参数和校验和绕过标志
XYParser_SetAdcParams(parser.get(), 4.5, 6.0); XYParser_SetAdcParams(parser.get(), 4.5, 6.0);
XYParser_SetBypassChecksum(parser.get(), 1); XYParser_SetBypassChecksum(parser.get(), 1);
// 构建测试帧并解析
const std::vector<std::uint8_t> bytes = BuildMinimalFrame(8); const std::vector<std::uint8_t> bytes = BuildMinimalFrame(8);
std::array<XYParserFrameSummary, 1> summaries{}; std::array<XYParserFrameSummary, 1> summaries{};
@@ -105,17 +137,255 @@ TEST(XYParserApiTests, FeedParsesAComplete8ChannelFrame)
summaries.data(), summaries.data(),
static_cast<int>(summaries.size())); static_cast<int>(summaries.size()));
ASSERT_EQ(frame_count, 1); // 验证解析结果
EXPECT_EQ(summaries[0].frame_index, static_cast<std::uint32_t>(1)); ASSERT_EQ(frame_count, 1); // 应解析出 1 帧
EXPECT_EQ(summaries[0].channel_count, static_cast<std::uint8_t>(8)); EXPECT_EQ(summaries[0].frame_index, 1U); // 帧索引应为 1
EXPECT_EQ(summaries[0].battery, static_cast<std::uint8_t>(95)); EXPECT_EQ(summaries[0].channel_count, 8U); // 通道数应为 8
EXPECT_EQ(summaries[0].sample_count, static_cast<std::uint8_t>(5)); EXPECT_EQ(summaries[0].battery, 95U); // 电池电量应为 95
EXPECT_GT(summaries[0].channel_values_uv[0][0], 0.0); EXPECT_EQ(summaries[0].sample_count, 5U); // 采样数应为 5
EXPECT_EQ(summaries[0].trigger_types[0], static_cast<std::uint8_t>(0)); EXPECT_GT(summaries[0].channel_values_uv[0][0], 0.0); // 通道值应大于 0
EXPECT_EQ(summaries[0].trigger_indices[0], static_cast<std::uint8_t>(0)); EXPECT_EQ(summaries[0].trigger_types[0], 0U); // 触发类型应为 0
EXPECT_EQ(summaries[0].trigger_indices[0], 0U); // 触发索引应为 0
} }
/// 测试Feed 函数能缓冲部分数据直到完整帧可用
TEST(XYParserApiTests, FeedBuffersPartialDataUntilAFullFrameIsAvailable) TEST(XYParserApiTests, FeedBuffersPartialDataUntilAFullFrameIsAvailable)
{
// 创建 8 通道解析器
ParserGuard parser(XYParser_CreateParser(8));
ASSERT_NE(parser.get(), nullptr);
// 设置校验和绕过标志
XYParser_SetBypassChecksum(parser.get(), 1);
// 构建测试帧并分成两部分
const std::vector<std::uint8_t> bytes = BuildMinimalFrame(8);
const std::size_t split_index = bytes.size() / 2;
std::array<XYParserFrameSummary, 1> summaries{};
// 第一次 Feed传入前半部分数据
const int first_result = XYParser_Feed(
parser.get(),
bytes.data(),
split_index,
summaries.data(),
static_cast<int>(summaries.size()));
EXPECT_EQ(first_result, 0); // 数据不完整,不应解析出帧
// 第二次 Feed传入后半部分数据
const int second_result = XYParser_Feed(
parser.get(),
bytes.data() + split_index,
bytes.size() - split_index,
summaries.data(),
static_cast<int>(summaries.size()));
ASSERT_EQ(second_result, 1); // 数据完整,应解析出 1 帧
EXPECT_EQ(summaries[0].frame_index, 1U); // 帧索引应为 1
}
// ============================================================================
// 解析器创建测试
// ============================================================================
/// 测试:成功创建 8 通道解析器
TEST(XYParserApiTests, CreateParserSucceedsFor8Channels)
{
ParserGuard parser(XYParser_CreateParser(8));
EXPECT_NE(parser.get(), nullptr);
}
/// 测试:成功创建 64 通道解析器
TEST(XYParserApiTests, CreateParserSucceedsFor64Channels)
{
ParserGuard parser(XYParser_CreateParser(64));
EXPECT_NE(parser.get(), nullptr);
}
/// 测试:创建 0 通道解析器应失败
TEST(XYParserApiTests, CreateParserRejectsZeroChannels)
{
EXPECT_EQ(XYParser_CreateParser(0), nullptr);
}
/// 测试:创建不支持的通道数(如 100应失败
TEST(XYParserApiTests, CreateParserRejectsExcessiveChannels)
{
EXPECT_EQ(XYParser_CreateParser(100), nullptr);
}
// ============================================================================
// 参数设置函数测试
// ============================================================================
/// 测试:设置正常的 ADC 参数
TEST(XYParserApiTests, SetAdcParamsAcceptsValidValues)
{
ParserGuard parser(XYParser_CreateParser(8));
ASSERT_NE(parser.get(), nullptr);
// 不应崩溃
EXPECT_NO_THROW(XYParser_SetAdcParams(parser.get(), 4.5, 6.0));
}
/// 测试:设置边界值的 ADC 参数
TEST(XYParserApiTests, SetAdcParamsAcceptsBoundaryValues)
{
ParserGuard parser(XYParser_CreateParser(8));
ASSERT_NE(parser.get(), nullptr);
// 测试零值
EXPECT_NO_THROW(XYParser_SetAdcParams(parser.get(), 0.0, 0.0));
// 测试较大值
EXPECT_NO_THROW(XYParser_SetAdcParams(parser.get(), 100.0, 1000.0));
}
/// 测试:对空句柄调用 SetAdcParams
TEST(XYParserApiTests, SetAdcParamsOnNullHandle)
{
// 不应崩溃
EXPECT_NO_THROW(XYParser_SetAdcParams(nullptr, 4.5, 6.0));
}
/// 测试:关闭校验和绕过
TEST(XYParserApiTests, SetBypassChecksumOff)
{
ParserGuard parser(XYParser_CreateParser(8));
ASSERT_NE(parser.get(), nullptr);
EXPECT_NO_THROW(XYParser_SetBypassChecksum(parser.get(), 0));
}
/// 测试:对空句柄调用 SetBypassChecksum
TEST(XYParserApiTests, SetBypassChecksumOnNullHandle)
{
EXPECT_NO_THROW(XYParser_SetBypassChecksum(nullptr, 1));
}
// ============================================================================
// Feed 函数帧解析测试
// ============================================================================
/// 测试:传入空数据
TEST(XYParserApiTests, FeedParsesEmptyData)
{
ParserGuard parser(XYParser_CreateParser(8));
ASSERT_NE(parser.get(), nullptr);
std::array<XYParserFrameSummary, 10> summaries{};
const int result = XYParser_Feed(
parser.get(),
nullptr,
0,
summaries.data(),
static_cast<int>(summaries.size()));
EXPECT_EQ(result, 0);
}
/// 测试:只传入帧头数据
TEST(XYParserApiTests, FeedParsesOnlyHeader)
{
ParserGuard parser(XYParser_CreateParser(8));
ASSERT_NE(parser.get(), nullptr);
XYParser_SetBypassChecksum(parser.get(), 1);
// 只发送帧头
const std::vector<std::uint8_t> header_only = {0xAA, 0x01, 0x00, 0x00, 0x00};
std::array<XYParserFrameSummary, 10> summaries{};
const int result = XYParser_Feed(
parser.get(),
header_only.data(),
header_only.size(),
summaries.data(),
static_cast<int>(summaries.size()));
EXPECT_EQ(result, 0); // 数据不完整,不应解析出帧
}
/// 测试:解析完整的 64 通道帧
TEST(XYParserApiTests, FeedParses64ChannelFrame)
{
ParserGuard parser(XYParser_CreateParser(64));
ASSERT_NE(parser.get(), nullptr);
XYParser_SetAdcParams(parser.get(), 4.5, 6.0);
XYParser_SetBypassChecksum(parser.get(), 1);
const std::vector<std::uint8_t> bytes = BuildMinimalFrame(64);
std::array<XYParserFrameSummary, 1> summaries{};
const int frame_count = XYParser_Feed(
parser.get(),
bytes.data(),
bytes.size(),
summaries.data(),
static_cast<int>(summaries.size()));
ASSERT_EQ(frame_count, 1);
EXPECT_EQ(summaries[0].channel_count, 64U);
}
/// 测试:连续解析多个帧
TEST(XYParserApiTests, FeedParsesMultipleFrames)
{
ParserGuard parser(XYParser_CreateParser(8));
ASSERT_NE(parser.get(), nullptr);
XYParser_SetBypassChecksum(parser.get(), 1);
// 构建两个连续的帧
const std::vector<std::uint8_t> frame1 = BuildMinimalFrame(8);
const std::vector<std::uint8_t> frame2 = BuildMinimalFrame(8);
std::vector<std::uint8_t> combined(frame1);
combined.insert(combined.end(), frame2.begin(), frame2.end());
std::array<XYParserFrameSummary, 10> summaries{};
const int frame_count = XYParser_Feed(
parser.get(),
combined.data(),
combined.size(),
summaries.data(),
static_cast<int>(summaries.size()));
EXPECT_EQ(frame_count, 2);
EXPECT_EQ(summaries[0].frame_index, 1U);
EXPECT_EQ(summaries[1].frame_index, 2U);
}
/// 测试:帧索引递增
TEST(XYParserApiTests, FeedIncrementsFrameIndex)
{
ParserGuard parser(XYParser_CreateParser(8));
ASSERT_NE(parser.get(), nullptr);
XYParser_SetBypassChecksum(parser.get(), 1);
// 连续发送多个帧
std::array<XYParserFrameSummary, 10> summaries{};
int total_frames = 0;
for (int i = 0; i < 3; ++i) {
const std::vector<std::uint8_t> frame = BuildMinimalFrame(8);
const int count = XYParser_Feed(
parser.get(),
frame.data(),
frame.size(),
summaries.data() + total_frames,
static_cast<int>(summaries.size()) - total_frames);
total_frames += count;
}
EXPECT_EQ(total_frames, 3);
for (int i = 0; i < 3; ++i) {
EXPECT_EQ(summaries[i].frame_index, static_cast<std::uint32_t>(i + 1));
}
}
/// 测试:电池电量字段解析
TEST(XYParserApiTests, FeedParsesBatteryValue)
{ {
ParserGuard parser(XYParser_CreateParser(8)); ParserGuard parser(XYParser_CreateParser(8));
ASSERT_NE(parser.get(), nullptr); ASSERT_NE(parser.get(), nullptr);
@@ -123,23 +393,88 @@ TEST(XYParserApiTests, FeedBuffersPartialDataUntilAFullFrameIsAvailable)
XYParser_SetBypassChecksum(parser.get(), 1); XYParser_SetBypassChecksum(parser.get(), 1);
const std::vector<std::uint8_t> bytes = BuildMinimalFrame(8); const std::vector<std::uint8_t> bytes = BuildMinimalFrame(8);
const std::size_t split_index = bytes.size() / 2;
std::array<XYParserFrameSummary, 1> summaries{}; std::array<XYParserFrameSummary, 1> summaries{};
const int first_result = XYParser_Feed( XYParser_Feed(
parser.get(), parser.get(),
bytes.data(), bytes.data(),
split_index, bytes.size(),
summaries.data(), summaries.data(),
static_cast<int>(summaries.size())); static_cast<int>(summaries.size()));
EXPECT_EQ(first_result, 0);
const int second_result = XYParser_Feed( EXPECT_EQ(summaries[0].battery, 95U); // BuildMinimalFrame 中设置的电池电量
}
// ============================================================================
// 帧数据边界测试
// ============================================================================
/// 测试:跨多次 Feed 的不完整帧
TEST(XYParserApiTests, FeedHandlesIncompleteFrameAcrossMultipleFeeds)
{
ParserGuard parser(XYParser_CreateParser(8));
ASSERT_NE(parser.get(), nullptr);
XYParser_SetBypassChecksum(parser.get(), 1);
const std::vector<std::uint8_t> bytes = BuildMinimalFrame(8);
std::array<XYParserFrameSummary, 1> summaries{};
// 分成 3 次发送
const std::size_t part1_size = bytes.size() / 3;
const std::size_t part2_size = bytes.size() / 3;
EXPECT_EQ(XYParser_Feed(parser.get(), bytes.data(), part1_size,
summaries.data(), static_cast<int>(summaries.size())), 0);
EXPECT_EQ(XYParser_Feed(parser.get(), bytes.data() + part1_size, part2_size,
summaries.data(), static_cast<int>(summaries.size())), 0);
const int result = XYParser_Feed(
parser.get(), parser.get(),
bytes.data() + split_index, bytes.data() + part1_size + part2_size,
bytes.size() - split_index, bytes.size() - part1_size - part2_size,
summaries.data(), summaries.data(),
static_cast<int>(summaries.size())); static_cast<int>(summaries.size()));
ASSERT_EQ(second_result, 1);
EXPECT_EQ(summaries[0].frame_index, static_cast<std::uint32_t>(1)); ASSERT_EQ(result, 1);
EXPECT_EQ(summaries[0].frame_index, 1U);
} }
// ============================================================================
// 销毁和错误处理测试
// ============================================================================
/// 测试:销毁空句柄
TEST(XYParserApiTests, DestroyParserAcceptsNullHandle)
{
EXPECT_NO_THROW(XYParser_DestroyParser(nullptr));
}
/// 测试:连续创建和销毁
TEST(XYParserApiTests, CreateAndDestroyMultipleParsers)
{
for (int i = 0; i < 10; ++i) {
ParserGuard parser(XYParser_CreateParser(8));
EXPECT_NE(parser.get(), nullptr);
}
// 析构时自动销毁所有解析器
}
/*
/// 测试GetLastError 在正常操作后
TEST(XYParserApiTests, GetLastErrorAfterSuccessfulCreate)
{
ParserGuard parser(XYParser_CreateParser(8));
ASSERT_NE(parser.get(), nullptr);
// 正常操作后,错误信息应为空或无错误
const char* error = XYParser_GetLastError(parser.get());
// 错误信息可能为空或特定实现
}
/// 测试GetLastError 在空句柄上
TEST(XYParserApiTests, GetLastErrorReturnsMessageForNullParser)
{
EXPECT_EQ(std::string(XYParser_GetLastError(nullptr)), std::string("invalid parser handle"));
}
*/