前言
上一章中,我们详细的讲解了在用TDD的方式写完一个接口时的各个步骤。这一章中,我们继续写下一个,并体会下在有测试保障下代码重构,优化质量。
继续码
低频ReadArray
那既然已经写好了08那个ReadArray了,那就把03那个也顺带写了吧。
03这个是只用于低频的,和08的唯一区别就是不用多写那个随便什么字节。
所以写出来差不多:
AT26DF.h
……
#define AT26DF_OPC_READARRAY 0x0B
#define AT26DF_OPC_READARRAY_LOWFREQ 0x03
……
// description: Sequentially read a continuous stream of data begun from the address specified.
// parameter : addr the starting address
// buf buffer to store the result.
// len the length of data you want to read.
// return :
// note : this function can be used for lower frequency read operations up to the maximum
// specified by fRDLF
void AT26DF_readArrayLowFreq(AT26DF_ADDR addr, uint8_t *buf, uint16_t len);
AT26DFTest.c
……
TEST(AT26DF, ReadArrayLowFreq){
int i;
AT26DF_ADDR startAddr = 0x123456;
const uint8_t expectData[] = {0x12, 0x34, 0x56, 0x78, 0x9a};
uint8_t actualData[sizeof(expectData)];
const uint8_t len = sizeof(expectData);
SPI_SelectCS_Expect();
SPI_writeByte_Expect(AT26DF_OPC_READARRAY_LOWFREQ);
// address byte
SPI_writeByte_Expect(0x12);
SPI_writeByte_Expect(0x34);
SPI_writeByte_Expect(0x56);
for(i = 0; i < len; i++){
SPI_readByte_ExpectAndReturn(expectData[i]);
}
SPI_DeselectCS_Expect();
AT26DF_readArrayLowFreq(startAddr,actualData,len);
TEST_ASSERT_EQUAL_HEX8_ARRAY(expectData,actualData,len);
}
……
AT26DFTestRunner.c
#include "unity_fixture.h"
TEST_GROUP_RUNNER(AT26DF)
{
RUN_TEST_CASE(AT26DF, ReadArray);
RUN_TEST_CASE(AT26DF, ReadArrayLowFreq);
}
AT26DF.c
……
void AT26DF_readArrayLowFreq(AT26DF_ADDR addr, uint8_t *buf, uint16_t len){
_csSel();
_spiWrite(AT26DF_OPC_READARRAY_LOWFREQ);
_spiWrite((addr >> 16) & 0xFF);
_spiWrite((addr >> 8) & 0xFF);
_spiWrite(addr & 0xFF);
_spiBurstRead(buf,len);
_csDesel();
}
测试也很顺利的通过了:
但他和高频的那个几乎就一个样子嘛。所以我们要开始去冗余了。
重构
首先,在测试中,把冗余的 写地址Expect、公用的数组给提出来,AT26DFTest.c就成了这个样:
#include <string.h>
#include "unity_fixture.h"
#include "AT26DF.h"
#include "MockSPI.h"
TEST_GROUP(AT26DF);
static const uint8_t anyByte = 0xEE;
static int i;
static uint8_t expectData[] = {0x12, 0x34, 0x56, 0x78, 0x9a};
static uint8_t actualData[sizeof(expectData)];
static const uint8_t len = sizeof(expectData);
static void _WriteAddrExpect(AT26DF_ADDR addr){
SPI_writeByte_Expect(((addr >> 16) & 0xFF));
SPI_writeByte_Expect(((addr >> 8) & 0xFF));
SPI_writeByte_Expect((addr & 0xFF));
}
static void _WriteDummyByteExpect(void){
SPI_writeByte_Expect(anyByte);
SPI_writeByte_IgnoreArg_b();
}
TEST_SETUP(AT26DF){
MockSPI_Init();
AT26DF_regFuncSPI(SPI_readByte,SPI_writeByte);
AT26DF_regFuncCS(SPI_SelectCS,SPI_DeselectCS);
for(i = 0; i < len; i++)
++expectData[i];
memset(actualData,0,len);
}
TEST_TEAR_DOWN(AT26DF){
MockSPI_Verify();
MockSPI_Destroy();
}
TEST(AT26DF, ReadArray){
AT26DF_ADDR startAddr = 0x345254;
SPI_SelectCS_Expect();
SPI_writeByte_Expect(AT26DF_OPC_READARRAY);
_WriteAddrExpect(startAddr);
_WriteDummyByteExpect();
for(i = 0; i < len; i++)
SPI_readByte_ExpectAndReturn(expectData[i]);
SPI_DeselectCS_Expect();
AT26DF_readArray(startAddr,actualData,len);
TEST_ASSERT_EQUAL_HEX8_ARRAY(expectData,actualData,len);
}
TEST(AT26DF, ReadArrayLowFreq){
AT26DF_ADDR startAddr = 0x123456;
SPI_SelectCS_Expect();
SPI_writeByte_Expect(AT26DF_OPC_READARRAY_LOWFREQ);
_WriteAddrExpect(startAddr);
for(i = 0; i < len; i++)
SPI_readByte_ExpectAndReturn(expectData[i]);
SPI_DeselectCS_Expect();
AT26DF_readArrayLowFreq(startAddr,actualData,len);
TEST_ASSERT_EQUAL_HEX8_ARRAY(expectData,actualData,len);
}
甚至为了expectData不重复,我还在每个Setup时修改其值,并重置actualData的值。这样可以避免测试之间相互影响,出现一个测试的通过导致另一个测试的初始值就通过的现象。
还抽象了_WriteDummyByteExpect方法,使可读性更好。
这时记得运行下测试,保证没改出问题。
同样的,.c文件中也可以做类似的去冗余,我们把写地址给抽象出来,毕竟这是很多操作都要用到的:
……
static void _writeAddr(AT26DF_ADDR addr){
_spiWrite((uint8_t)(addr >> 16));
_spiWrite((uint8_t)(addr >> 8));
_spiWrite((uint8_t)addr);
}
void AT26DF_readArray(AT26DF_ADDR addr, uint8_t *buf, uint16_t len){
_csSel();
_spiWrite(AT26DF_OPC_READARRAY);
_writeAddr(addr);
_spiWrite(DONTCAREBYTE);
_spiBurstRead(buf,len);
_csDesel();
}
void AT26DF_readArrayLowFreq(AT26DF_ADDR addr, uint8_t *buf, uint16_t len){
_csSel();
_spiWrite(AT26DF_OPC_READARRAY_LOWFREQ);
_writeAddr(addr);
_spiBurstRead(buf,len);
_csDesel();
}
再次运行测试。
通过。
添加参数检查
很好,但是好像还有点瑕疵,我们只测了正常的情况,要是参数有问题怎么办?
我们的readArray的参数中,addr无所谓,反正只认第几位得了。buf最有可能出问题,必须要检查NULL的情况。然后len为0的时候其实应该直接返回,不应该做任何事对吧,反正没有读任何字节,为什么要浪费CPU去做任何事情?
我们为这些异常情况写一个测试用例,就叫Read0LenOrBufNULLDoNothing:
……
static const AT26DF_ADDR anyAddr = 0x122534;
……
TEST(AT26DF, Read0LenOrBufNULLDoNothing){
uint8_t buf[1];
AT26DF_readArray(anyAddr,buf,0);
AT26DF_readArrayLowFreq(anyAddr,buf,0);
AT26DF_readArray(anyAddr,NULL,1);
AT26DF_readArrayLowFreq(anyAddr,NULL,1);
}
记得到Runner中去安装新测试用例。
一运行测试,果然报错。测试那个NULL的则是程序直接崩溃:
参数检查添加起来也简单,两个接口都加这两行就得了
……
void AT26DF_readArray(AT26DF_ADDR addr, uint8_t *buf, uint16_t len){
if(buf == NULL || len == 0)
return;
_csSel();
……
再次运行测试。通过。
很好,现在我们的两个接口都有了基本的检查,还去除了明显的冗余。
在CW上验证下吧
我们已经写了这么多代码了,该到CW上测试下了。
打开CW,我们把新加的mock文件和接口定义文件加进去
然后点击运行:
很好,没有发现平台差异导致的问题。如果你愿意,还可以接上开发板在实际芯片上进行验证。
在进行嵌入式TDD双目标平台开发时,偶尔地我们要到仿真器或真机上跑一下测试。因为虽然不常见,偶尔还是会出现因为平台差异导致PC上成功的测试,到仿真器或真机上就失败的情况。
图:嵌入式TDD循环
结语
这一章中,我们又实现了一个接口,并对测试和实现代码都进行了重构。实际上,整个嵌入式TDD的流程都已经展示的差不多了,接下来我们加把劲写完整个模块。