重构代码的架构设计,增加测试单元,提高代码可靠性

This commit is contained in:
2026-03-23 20:40:04 +08:00
parent c2ce221691
commit a4bf0962b2
31 changed files with 2084 additions and 703 deletions

289
tests/test_p1_lcd_basic.c Normal file
View File

@@ -0,0 +1,289 @@
/* -------------------------------------------------------------------------
* 文件名: test_p1_lcd_basic.c
* 作用:
* 对 LCD 基础模块进行单元测试,覆盖以下三个子模块:
* 1) lcd.c : 显存初始化、像素读写、边界保护
* 2) lcd_draw.c : 填充、反显、水平线/垂直线绘制
* 3) lcd_text.c : UTF-8 解码、字符串显示ASCII/中文/换行)与边界返回值
*
* 测试策略:
* - 每个 test_* 函数仅验证一个明确行为域,失败即立即返回非 0。
* - 使用 ASSERT_* 宏统一断言风格,失败时打印文件+行号。
* - main() 串行执行所有子用例,便于定位首个失败点。
*
* 设计说明:
* - 本文件只依赖公开头文件,不包含被测 .c实现黑盒+灰盒结合验证。
* - 通过“像素点结果”验证绘图函数,不依赖外设或真实 LCD 硬件。
* ------------------------------------------------------------------------- */
#include "../src/Drv/lcd/lcd.h"
#include "../src/Drv/lcd/lcd_draw.h"
#include "../src/Drv/lcd/lcd_text.h"
#include "test_common.h"
/* -------------------------------------------------------------------------
* 用例: test_lcd_init_and_pixel_rw
* 目标:
* - 验证 Lcd_Init() 会把显存清零(黑色)
* - 验证 Lcd_SetPixel/Lcd_GetPixel 的基本读写正确性
* - 验证越界写入/读取均返回错误码
*
* 覆盖函数:
* - Lcd_Init
* - Lcd_SetPixel
* - Lcd_GetPixel
* ------------------------------------------------------------------------- */
static int test_lcd_init_and_pixel_rw(void)
{
/* 初始化后显存应为全黑0x00 */
Lcd_Init();
/* 左上角像素校验 */
ASSERT_EQ_INT(LCD_COLOR_BLACK, Lcd_GetPixel(0, 0));
/* 右下角像素校验,确认全屏范围都被正确初始化 */
ASSERT_EQ_INT(LCD_COLOR_BLACK, Lcd_GetPixel(LCD_SIZE_X - 1, LCD_SIZE_Y - 1));
/* 在合法坐标写白点并回读 */
ASSERT_EQ_INT(LCD_OK, Lcd_SetPixel(1, 1, LCD_COLOR_WHITE));
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(1, 1));
/* 非法颜色应返回错误码,且不改变原有像素值 */
ASSERT_EQ_INT(LCD_ERR_INVALID_COLOR, Lcd_SetPixel(1, 1, 0x7F));
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(1, 1));
/* 越界写入应返回错误码x == LCD_SIZE_X 已越界 */
ASSERT_EQ_INT(LCD_ERR_OUT_OF_RANGE, Lcd_SetPixel(LCD_SIZE_X, 1, LCD_COLOR_WHITE));
/* 越界写入应返回错误码y == LCD_SIZE_Y 已越界 */
ASSERT_EQ_INT(LCD_ERR_OUT_OF_RANGE, Lcd_SetPixel(1, LCD_SIZE_Y, LCD_COLOR_WHITE));
/* 越界读取应返回错误码 */
ASSERT_EQ_INT(LCD_ERR_OUT_OF_RANGE, Lcd_GetPixel(LCD_SIZE_X, 1));
ASSERT_EQ_INT(LCD_ERR_OUT_OF_RANGE, Lcd_GetPixel(1, LCD_SIZE_Y));
/* 附加点位校验,确保越界写没有污染邻近合法区域 */
ASSERT_EQ_INT(LCD_COLOR_BLACK, Lcd_GetPixel(2, 2));
return 0;
}
/* -------------------------------------------------------------------------
* 用例: test_fillrect_and_invert
* 目标:
* - 验证 Lcd_FillRect 的闭区间填充行为(包含 right_x/bottom_y与错误码
* - 验证 Lcd_Invert 的半开区间行为([min, max))与错误码
* - 验证 Lcd_Invert 支持反向坐标输入(起点大于终点)
*
* 覆盖函数:
* - Lcd_FillRect
* - Lcd_Invert
* ------------------------------------------------------------------------- */
static int test_fillrect_and_invert(void)
{
Lcd_Init();
/* 填充 3x3 白色区域x=[2,4], y=[2,4](闭区间) */
ASSERT_EQ_INT(LCD_OK, Lcd_FillRect(2, 2, 4, 4, LCD_COLOR_WHITE));
/* 左上角在填充区内 */
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(2, 2));
/* 右下角也在填充区内,验证 FillRect 含边界 */
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(4, 4));
/* 区域外点保持黑色 */
ASSERT_EQ_INT(LCD_COLOR_BLACK, Lcd_GetPixel(5, 5));
/* Invert 采用 [min, max) 半开区间:会翻转 x=2..3, y=2..3 */
ASSERT_EQ_INT(LCD_OK, Lcd_Invert(2, 2, 4, 4));
/* 区域内像素从白翻转成 ~白 */
ASSERT_EQ_INT((uint8_t)~LCD_COLOR_WHITE, Lcd_GetPixel(2, 2));
ASSERT_EQ_INT((uint8_t)~LCD_COLOR_WHITE, Lcd_GetPixel(3, 3));
/* x=4/y=4 在半开区间外,不应被翻转 */
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(4, 4));
/* 反向坐标也应可处理:再次翻转回白色 */
ASSERT_EQ_INT(LCD_OK, Lcd_Invert(4, 4, 2, 2));
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(2, 2));
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(3, 3));
/* 越界参数应返回错误码 */
ASSERT_EQ_INT(LCD_ERR_OUT_OF_RANGE, Lcd_FillRect(0, 0, LCD_SIZE_X, 4, LCD_COLOR_WHITE));
ASSERT_EQ_INT(LCD_ERR_INVALID_COLOR, Lcd_FillRect(0, 0, 1, 1, 0x7F));
ASSERT_EQ_INT(LCD_ERR_OUT_OF_RANGE, Lcd_Invert(0, 0, LCD_SIZE_X + 1, 4));
return 0;
}
/* -------------------------------------------------------------------------
* 用例: test_lineh_linev
* 目标:
* - 验证 Lcd_LineH/Lcd_LineV 的绘制范围与线宽、错误码
* - 验证水平/垂直线的“结束坐标不包含”语义
* - 验证 Lcd_LineH 的反向坐标能力
*
* 覆盖函数:
* - Lcd_LineH
* - Lcd_LineV
* ------------------------------------------------------------------------- */
static int test_lineh_linev(void)
{
Lcd_Init();
/* 水平线x=[10,14), y=[20,22);线宽=2 */
ASSERT_EQ_INT(LCD_OK, Lcd_LineH(10, 14, 20, 2, LCD_COLOR_WHITE));
/* 起点应该被绘制 */
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(10, 20));
/* 范围内末端附近点x=13,y=21应为白 */
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(13, 21));
/* xEnd 不包含x=14 不应被画 */
ASSERT_EQ_INT(LCD_COLOR_BLACK, Lcd_GetPixel(14, 20)); /* xEnd 不包含 */
/* 垂直线x=[40,42), y=[30,35);线宽=2 */
ASSERT_EQ_INT(LCD_OK, Lcd_LineV(30, 35, 40, 2, LCD_COLOR_WHITE));
/* 起点应被绘制 */
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(40, 30));
/* 范围内末端附近点应为白 */
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(41, 34));
/* yEnd 不包含y=35 不应被画 */
ASSERT_EQ_INT(LCD_COLOR_BLACK, Lcd_GetPixel(40, 35)); /* yEnd 不包含 */
/* 水平线支持反向 X函数内部应自动交换起止坐标 */
ASSERT_EQ_INT(LCD_OK, Lcd_LineH(14, 10, 50, 1, LCD_COLOR_WHITE));
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(10, 50));
ASSERT_EQ_INT(LCD_COLOR_WHITE, Lcd_GetPixel(13, 50));
ASSERT_EQ_INT(LCD_COLOR_BLACK, Lcd_GetPixel(14, 50));
ASSERT_EQ_INT(LCD_ERR_INVALID_COLOR, Lcd_LineH(10, 14, 20, 1, 0x7F));
ASSERT_EQ_INT(LCD_ERR_OUT_OF_RANGE, Lcd_LineH(10, 14, LCD_SIZE_Y, 1, LCD_COLOR_WHITE));
ASSERT_EQ_INT(LCD_ERR_OUT_OF_RANGE, Lcd_LineV(10, 14, LCD_SIZE_X, 1, LCD_COLOR_WHITE));
return 0;
}
/* -------------------------------------------------------------------------
* 工具函数: count_white_in_rect
* 作用:
* 统计指定闭区间矩形内白色像素数量。
* 用途:
* 文本渲染(尤其字形)不便逐像素写期望值时,用“白点数 > 0”验证
* “确实有内容被渲染”这一最小正确性。
*
* 参数:
* x0,y0 : 左上角(包含)
* x1,y1 : 右下角(包含)
*
* 返回:
* 区域内等于 LCD_COLOR_WHITE 的像素个数。
* ------------------------------------------------------------------------- */
static int count_white_in_rect(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
int count = 0;
for (uint16_t y = y0; y <= y1; y++) {
for (uint16_t x = x0; x <= x1; x++) {
if (Lcd_GetPixel(x, y) == LCD_COLOR_WHITE) {
count++;
}
}
}
return count;
}
/* -------------------------------------------------------------------------
* 用例: test_utf8_next_decode
* 目标:
* - 覆盖 utf8_next 对 1/2/3 字节 UTF-8 的解码路径
* - 验证不支持或非法输入时返回 0
*
* 覆盖函数:
* - utf8_next
* ------------------------------------------------------------------------- */
static int test_utf8_next_decode(void)
{
uint32_t unicode = 0;
const unsigned char ascii[] = "A"; /* 单字节 ASCII */
const unsigned char u2[] = {0xC2, 0xA2, 0x00}; /* 两字节: U+00A2 */
const unsigned char u3[] = {0xE4, 0xBD, 0xA0, 0x00}; /* 三字节: U+4F60 */
const unsigned char invalid[] = {0xF0, 0x00}; /* 四字节起始(当前实现不支持) */
const unsigned char trunc2[] = {0xC2, 0x00}; /* 两字节截断 */
const unsigned char trunc3[] = {0xE4, 0xBD, 0x00}; /* 三字节截断 */
/* ASCII: 返回长度 1码点 0x41 */
ASSERT_EQ_INT(1, utf8_next(ascii, &unicode));
ASSERT_EQ_U32(0x41u, unicode);
/* 两字节 UTF-8 */
ASSERT_EQ_INT(2, utf8_next(u2, &unicode));
ASSERT_EQ_U32(0x00A2u, unicode);
/* 三字节 UTF-8 */
ASSERT_EQ_INT(3, utf8_next(u3, &unicode));
ASSERT_EQ_U32(0x4F60u, unicode);
/* 非支持输入返回 0 */
ASSERT_EQ_INT(0, utf8_next(invalid, &unicode));
ASSERT_EQ_INT(0, utf8_next(trunc2, &unicode));
ASSERT_EQ_INT(0, utf8_next(trunc3, &unicode));
return 0;
}
/* -------------------------------------------------------------------------
* 用例: test_showstr_ascii_utf8_newline_and_bounds
* 目标:
* - 验证 Lcd_ShowStr 对 ASCII/中文渲染均生效
* - 验证换行逻辑(行高 + 行间距)正确
* - 验证越界起始坐标时返回错误码
*
* 覆盖函数:
* - Lcd_ShowStr
* ------------------------------------------------------------------------- */
static int test_showstr_ascii_utf8_newline_and_bounds(void)
{
uint8_t text_a[] = "A";
uint8_t text_cn[] = "";
uint8_t text_nl[] = "A\nB";
uint8_t text_cn_overflow_x[] = "你你";
uint8_t text_nl_overflow_y[] = "A\nB";
/* ASCII 渲染: 返回 0 且字形区域应存在白点 */
Lcd_Init();
ASSERT_EQ_INT(0, Lcd_ShowStr(0, 0, text_a));
ASSERT_TRUE(count_white_in_rect(0, 0, 6, 11) > 0);
/* 中文渲染: 13x12 区域内应存在白点 */
Lcd_Init();
ASSERT_EQ_INT(0, Lcd_ShowStr(10, 10, text_cn));
ASSERT_TRUE(count_white_in_rect(10, 10, 22, 21) > 0);
/* 换行渲染: "A\nB" 中 B 应下移到 y = 12 + rowSpace(2) = 14 */
Lcd_Init();
ASSERT_EQ_INT(0, Lcd_ShowStr(0, 0, text_nl));
ASSERT_TRUE(count_white_in_rect(0, 0, 6, 11) > 0); /* A */
ASSERT_TRUE(count_white_in_rect(0, 14, 6, 25) > 0); /* B: 12 高 + 2 行距 */
/* 起始越界x 方向越界应返回 -1 */
ASSERT_EQ_INT(-1, Lcd_ShowStr(LCD_SIZE_X - 6, 0, text_a));
/* 起始越界y 方向越界应返回 -2 */
ASSERT_EQ_INT(-2, Lcd_ShowStr(0, LCD_SIZE_Y - 12, text_a));
/* 中文第 2 个字符越过右边界时应返回 -1 */
Lcd_Init();
ASSERT_EQ_INT(-1, Lcd_ShowStr(LCD_SIZE_X - 13, 0, text_cn_overflow_x));
/* 换行后若超出底部边界,应返回 -2 */
Lcd_Init();
ASSERT_EQ_INT(-2, Lcd_ShowStr(0, LCD_SIZE_Y - 13, text_nl_overflow_y));
/* 预留接口目前为桩函数,返回 0 */
ASSERT_EQ_INT(0, Lcd_ShowTest(0, 0, text_a));
return 0;
}
/* -------------------------------------------------------------------------
* 测试入口:
* 按固定顺序执行所有子用例,任一失败立即返回 1。
* 约定:
* 返回 0 表示该测试程序通过。
* ------------------------------------------------------------------------- */
int main(void)
{
if (test_lcd_init_and_pixel_rw() != 0) return 1;
if (test_fillrect_and_invert() != 0) return 1;
if (test_lineh_linev() != 0) return 1;
if (test_utf8_next_decode() != 0) return 1;
if (test_showstr_ascii_utf8_newline_and_bounds() != 0) return 1;
return 0;
}