我目前正在尝试重构一些嵌入式 C 代码,并尝试应用 James W. Grenning 在他的《嵌入式 C 测试驱动开发》一书中推荐的内容。为此,我将代码划分为每个抽象数据类型的模块,有点像 Java 中的类。 然而,我在这里遇到了一个“问题”。我有很多不需要 getter 或 setter 的 ADT。我需要对它们做的唯一事情是 1. 通过从字节数组中提取数据来构建结构,2. 在我的设备屏幕上显示存储在 ADT 中的提取数据。为了显示数据,我使用外部静态编译库,并访问屏幕的一些驱动程序。我现在想对从字节数组中提取数据并构建我的 ADT 的函数进行单元测试。但是,我没有 getter 或 setter 来访问我的结构的成员。因此,我真正对函数进行单元测试的唯一方法是调用显示函数,如果我不在模拟器上运行单元测试并模拟驱动程序,则该函数实际上不能进行单元测试。在这种情况下,如果 getter 和 setter 仅用于我的单元测试,那么实现它们是否“干净”? 为了更好地说明我的问题,假设我有一个代表 TLV(标签长度值)缓冲区的 ADT:
在
tlv.h
中,我有以下内容:
struct tlv typedef tlv_t;
// Builds a tlv_t struct from the tlv data stored in a byte buffer
tlv_t * extract_tlv(const uint8_t *buffer, size_t buffer_length);
// display the tlv data stored in the tlv structure on my device screen.
int display_tlv(const tlv_t *tlv);
在
tlv.c
中,我有以下内容:
#include "tlv.h"
typedef enum
{
TAG_A,
TAG_B,
...
} tlv_tag_t;
struct tlv {
tlv_tag_t tag;
size_t length;
uint8_t *value;
};
tlv_t * extract_tlv(const uint8_t *buffer, size_t buffer_length) {
tlv_t *tlv = (tlv_t *)calloc(1, sizeof(tlv_t));
if(!tlv)
{
return NULL;
}
// extract the tlv data in buffer and stores them in the tlv struct
...
return tlv;
}
int display_tlv(const tlv_t *tlv) {
// accesses the field of my tlv struct, and display them
...
}
图像我有以下缓冲区
0x00010004012345678
。标签和长度是缓冲区中的
uint16_t
值,因此,当使用上面的缓冲区调用 extract_tlv
时,我希望以以下 tlv 结构结束:tlv.tag = TAG_B, // 0x0001
tlv.length = 4, // 0x0004
tlv.value = {0x12, 0x34, 0x56, 0x78}, // 0x12345678
现在,我想对这个
extract_tlv
函数进行单元测试,以确保如果我发送上面的缓冲区,我会得到上面的结构作为输出。如果我没有 getter 和 setter,我怎样才能以干净的方式做到这一点?我认为仅为单元测试实现 getter 和 setter 并不是一个好的做法,因为它们不会进入生产代码,因此,它们应该在单元测试中使用。我们尝试过的另一种方法是将 tlv 结构的成员放在定义中,位于
tlv.h
文件中。在我们的测试文件中,我们创建一个 test_tlv
结构体,它使用定义来删除其成员,并且我们对 tlv_t
文件中的 tlv.c
结构体执行相同的操作。然后,我们在单元测试中将每个 tlv_t
结构体转换为 test_tlv_t
结构体,就像这样,我们可以在没有 getter 和 setter 的情况下访问成员:在tlv.h
:
#defin TLV_STRUCT_MEMBER \
tlv_tag_t tag; \
size_t length; \
uint8_t *value;
typedef enum
{
TAG_A,
TAG_B,
...
} tlv_tag_t;
struct tlv typedef tlv_t;
// Builds a tlv_t struct from the tlv data stored in a byte buffer
tlv_t * extract_tlv(const uint8_t *buffer, size_t buffer_length);
// display the tlv data stored in the tlv structure on my device screen.
int display_tlv(const tlv_t *tlv);
在
tlv.c
:
struct tlv {
TLV_STRUCT_MEMBER
};
tlv_t * extract_tlv(const uint8_t *buffer, size_t buffer_length) {
...
}
int display_tlv(const tlv_t *tlv) {
...
}
并在
test_tlv.c
#include "tlv.h"
struct test_tlv {
TLV_STRUCT_MEMBER
} typedef test_tlv_t;
TEST_EXTRACT_TLV() {
test_tlv_t *tlv = (test_tlv_t *)extract_tlv(...);
TEST_ASSERT_EQUAL(8, tlv.length);
...
}
但是这个解决方案有点老套,而且我不太喜欢将我的 ADT 转换为另一个解决方案,即使它们在技术上是相同的。
这里最好的“干净”做法是什么?有好的解决办法吗?
由于 ADT 的所有成员都是私有的,因此您不会测试他们的值。这与标准类型 FILE
的情况相同,我们只使用指向它的指针。就像您对 ADT 所做的那样。
”中指定的内容:提取函数扫描字节数组并填充结构。显示功能显示结构的值。唯一公开可见的数据流是从字节数组到显示输出,中间表示是不透明的。 你没有这么说,但函数中可能存在一些错误检测。也测试一下这些。您将需要模拟错误反应函数。
关于显示功能,是的,这意味着你需要模拟显示驱动程序。
OT:如果您将结构体的成员声明为私有,请勿在头文件中发布它们...