单元测试和实现之间的重复代码

Posted

技术标签:

【中文标题】单元测试和实现之间的重复代码【英文标题】:Duplicate code between unit test and implementation 【发布时间】:2014-11-20 12:16:19 【问题描述】:

我目前正在用纯 C 语言为嵌入式平台开发一些低级驱动程序。我使用 unity+cmock 作为单元测试框架。

但是,在编写低级内容时,我经常遇到以下模式:

测试:

void test_mcp2515_read_register(void)

    spi_frame_t expected_frame = 0;
    expected_frame.tx_length = 2;
    expected_frame.rx_length = 3;
    expected_frame.tx_data[0] = MCP2515_READ_CMD;
    expected_frame.tx_data[1] = TEST_ADDR;
    expected_frame.callback = callback_test;

    spi_transmit_ExpectAndReturn(expected_frame, true);

    mcp2515_read_register(TEST_ADDR, callback_test);

实施:

void mcp2515_read_register(uint8_t addr, spi_callback callback)

    spi_frame_t frame = 0;
    frame.tx_length = 2;
    frame.rx_length = 3;
    frame.tx_data[0] = MCP2515_READ_CMD;
    frame.tx_data[1] = addr;
    frame.callback = callback;

    spi_transmit(frame);

如您所见,测试和实现之间的代码有很多重复。

这是个问题吗?我写错了我的测试吗?或者我不应该为这些低级的东西编写测试?

【问题讨论】:

【参考方案1】:

测试代码的效率通常并不重要。这取决于您要测试的内容,但重复的代码可能表明存在设计缺陷。

在您的情况下,您或许可以将 mcp2515_read_register 函数分为两部分:一个用于创建结构,另一个用于处理 SPI 传输。

最佳的面向对象程序设计可能涉及以下模块:

SPI 驱动只关心实际的通信。 CAN 控制器驱动程序只关心控制器的细节。 调用者(“main”或其他)。 CAN 控制器驱动程序的测试代码:替换 main。

SPI 驱动程序将spi_frame_t 声明为不透明类型,这是一个只与SPI 数据和通信有关的结构。 SPI 驱动程序之外的任何人都不知道或需要知道此结构的内容。我不知道回调函数是做什么的,但它看起来不像与 SPI 驱动程序相关的东西。它看起来更像是与调用 SPI 驱动程序的代码相关的东西。

CAN 控制器驱动程序包括 SPI 驱动程序。它从 SPI 驱动程序调用“构造函数”来创建帧,然后将帧传递给 SPI 通信例程。 CAN 控制器驱动程序与 SPI 功能没有紧密耦合。例如,重写你的 CAN 控制器代码是没有意义的,因为你在 SPI 中发现了一个错误。如果您想在另一个项目中重复使用 SPI 驱动程序,您也不需要 CAN 控制器来使用 SPI。

测试用例替换调用者(“main”)或 CAN 控制器代码,具体取决于您要测试的程序部分。它使用与生产代码完全相同的功能。

【讨论】:

这几乎就是应用程序的构建方式。当(异步)SPI 传输完成时,回调函数是必需的。因此 spi_frame_t 独立于 SPI 实现,仅用于指示必须传输哪些数据。我原来的问题仍然没有回答。我如何避免在测试和实现之间重复代码。如果我的实现几乎是我测试中 90% 的复制/粘贴,那么测试有什么用处。 @WillemMelching 如果SPI帧与SPI无关,为什么要以SPI前缀命名?要么你给这个类型一个令人困惑的名字,要么你的 CAN 控制器代码正在做它不应该关心的事情。 @WillemMelching 不管怎样,SPI 帧属于哪里并不重要,因为您仍然可以使用不透明类型和构造函数来实现。这样一来,框架的创建与传输是分开的,您的测试代码可以像生产代码函数一样调用框架构造函数。您的应用程序没有这样做,您使用的是普通的“过程编程结构”,您的应用程序代码与结构数据类型紧密耦合。这就是为什么你必须在测试用例中重复代码。 如果您将测试中的内容复制并粘贴到代码中,那么您做错了。显然,在这种情况下测试会通过。我建议阅读一些单元测试方法,请参阅 xunitpatterns.com/Behavior Verification.html 和可能的 martinfowler.com/articles/mocksArentStubs.html @Ross 同样,这取决于他想测试什么。你不能说复制/粘贴是错误的,没有上下文。

以上是关于单元测试和实现之间的重复代码的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot APP - 单元测试[重复]

《单元测试的艺术》读书笔记----优秀单元测试的特性

数据库基础单元测试策略:在单元测试和测试数据之间截断表

使用 FakeItEasy 的私有方法和属性的单元测试用例 [重复]

角度单元测试 - 注入失败[重复]

单元测试方法以及实例