如何在 C 中没有 getter 和 setter 的情况下对抽象数据类型进行单元测试?

问题描述 投票:0回答:1

我目前正在尝试重构一些嵌入式 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 转换为另一个解决方案,即使它们在技术上是相同的。

这里最好的“干净”做法是什么?有好的解决办法吗?

c unit-testing embedded tdd abstract-data-type
1个回答
0
投票
(澄清后...)

由于 ADT 的所有成员都是私有的,因此您不会测试他们的值。这与标准类型 FILE 的情况相同,我们只使用指向它的指针。就像您对 ADT 所做的那样。

相反,测试“

contract

”中指定的内容:提取函数扫描字节数组并填充结构。显示功能显示结构的值。唯一公开可见的数据流是从字节数组到显示输出,中间表示是不透明的。 你没有这么说,但函数中可能存在一些错误检测。也测试一下这些。您将需要模拟错误反应函数。

关于显示功能,是的,这意味着你需要模拟显示驱动程序。

OT:如果您将结构体的成员声明为私有,请勿在头文件中发布它们...

© www.soinside.com 2019 - 2024. All rights reserved.