我目前正在尝试重构我的一些嵌入式C代码,并试图应用詹姆斯W。Grenning在他的Test-Driven Development for Embedded C书中推荐。为此,我将代码划分为每个抽象数据类型的模块,有点像Java中的类。然而,我在这里遇到了一个“问题”。我有很多ADT,我不需要getter和setter。我唯一需要做的就是1。通过从字节数组和2中提取数据来构建结构。在我的设备屏幕上显示ADT中存储的提取数据。为了显示数据,我使用了一个外部静态编译库,并访问了我的屏幕的一些驱动程序。现在,我想对从字节数组中提取数据并构建ADT的函数进行单元测试。但是,我没有getter和setter来访问我的结构的成员。因此,真正对函数进行单元测试的唯一方法是调用display函数,如果不在模拟器上运行单元测试并模拟驱动程序,则display函数实际上是不可测试的。在这种情况下,如果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
...
}
型
图I具有以下缓冲区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结构体的成员放在一个define中,位于tlv.h
文件中。在我们的测试文件中,我们创建了一个test_tlv
结构,它使用defines来删除它的成员,我们对tlv.c
文件中的tlv_t
结构执行了相同的操作。然后,在单元测试中,我们将每个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转换成另一个,尽管它们在技术上是一样的。
这里最好的“干净”做法是什么?是否有好的解决方案?
2条答案
按热度按时间monwx1rj1#
FILE
的情况相同,我们只使用指针。就像你和你的ADT一样。相反,测试你的“contract"中指定的内容:extract函数扫描字节数组并填充结构。display函数显示结构的值。唯一公开可见的数据流是从字节数组到显示输出,中间表示是不透明的。
你没有这么说,但在函数中可能有一些错误检测。把这些也测试一下。您需要模拟错误React函数。
关于显示功能,是的,这意味着您需要模拟显示驱动程序。
OT:如果你声明一个结构的成员是私有的,不要在头文件中发布它们…
wrrgggsh2#
单元测试实际上是白色盒测试,而不是黑盒测试。没有人会阻止您访问这些测试的内部。这里还有两个选项:
test_tlv.c
字符串
tlv_privtypes.h
中的struct tlv
类型,通常只包含在tlv.c
中,但对于您的测试,您可以在test_tlv.c
中额外包含tlv_privtypes.h
:tlv_privtypes.h
型
tlv.h
型
tlc.c
型
test_tlv.c
型
main.c -普通用户文件
型