[嵌入式TDD实战]TDD开发AT26DF驱动模块(二):掌控通信

前言

上一章中,我们把模块最基础的测试框架搭了起来,接下来,在开始疯狂码代码前,我们要先设计下要开发成什么样,那就得了解下我们要开发的东西的基本特性,于是我们翻出了AT26DF321的Datasheet,这个就不给了,网上一搜一大堆,随便自己下一个。

芯片概览

首先,这是个Flash芯片。

先要明确的是我们现在要搞出的驱动是最底层的那种驱动,直接和硬件交互,和具体芯片耦合的那种。要求使用者明确的知道他在使用的是这款芯片,且对芯片本身特性有一定的了解。而一般情况下,给最终用户使用前还会再经过多层封装;最终用户(比如APP业务开发的人)使用应该是个很抽象的模块,比如操作文件的那种。

通过阅读datasheet,我们可以看到,这个Flash芯片是通过SPI来通信的。MCU是SPI的主机,其作为从机,而且使用的是SPI的mode 0和3,也就是SPI最常用的通信模式,那就很舒服了,不用太管SPI的配置啥的了。我们可以直接抽象出SPI的收发接口,模块直接调用就可。

接着往下看:一条命令是用SPI断言CS开始,然后要输出一个8Bit操作码,然后是一些附加参数啥的,然后取消断言来结束操作。

那就十分清晰了,我们的驱动提供的是对命令的抽象接口,这些接口会通过SPI接口去发送和接收各种指令和答复。

那首先先考虑下咱们的模块怎么知道调用哪个SPI接口的问题。

依赖于抽象

咱的模块需要去调用SPI接口发送和接收数据,一个很自然的写法就是模块直接依赖于SPI模块的接口,在自己初始化时初始化SPI模块,然后发送接收时直接调用对应发送接收函数。

但是这样直接的依赖/耦合也会导致很多问题:

  • 管的太宽泛了:SPI模块凭什么由你来初始化?人SPI该怎么初始化,多少波特率,各种设置啥的关你啥事,你个模块就好好的管你的收发就行了。要是多个模块都在自己内部初始化了SPI,那不乱套了?
  • 模块依赖太强:SPI模块可能不止一个,要是直接调用的话,模块还得知道我实际调用的是哪个SPI接口,或要传哪个参数来指定对应SPI。而且那要是突然改了要调用的模块,比如SPI口不够用了,要你改成I/O口模拟的;比如我搞了个TCPIP远程透传SPI;比如……,反正就是不调原来那个模块了你咋办?整个模块所有收发的地方都改掉?
  • 多个模块可能并发使用同一个SPI,那就只好通过锁等方式避免多线程问题,那你这锁往哪里加?要不改SPI模块,要不改每个模块。
  • 比如,虽然我有多个模块用SPI,但是我只想在里这个模块调用SPI时的前后干一些事情,比如打印一些信息啥的。那又咋搞。
  • ……

为了避免这些问题,在一些能预见有可能需要变动依赖的地方,一般并不会直接去依赖某个具体的模块,而是依赖于抽象。

具体点说,在这个情况下,依赖于接口。在C中的表现形式就是函数指针。

由初始化模块的人主动告知接口,模块在要完成特定功能时只需要按照约定调用接口就行,并不需要知道实现细节,这样就实现了解耦。

于是,我们先为我们的模块搞定一下SPI的抽象。

具体来说就是给各种注册函数指针的接口,然后把函数指针记录在内部,在需要的时候进行调用。具体细节就不说了,下面直接贴出具体实现:

AT26DF.h

#ifndef _AT26DF_H
#define _AT26DF_H
#include <stdint.h>
// description: Register SPI call back functions
// parameter  : spi_rb   callback function to read byte using SPI
//              spi_wb   callback function to write byte using SPI
// return     : 
// note       : you should register SPI functions through this interface before use the driver.
void ATD26DF_regFuncSPI(uint8_t (*spi_rb)(void), void (*spi_wb)(uint8_t wb));
// description: Register SPI call back functions
// parameter  : spi_rb   callback function to burst read byte using SPI
//                       NULL   if want to use the default function
//              spi_wb   callback function to burst write byte using SPI
//                       NULL   if want to use the default function
// return     : 
// note       : if you don't register any functions, the default functions will call the functions
//              registered by the ATD26DF_regFuncSPI.
void ATD26DF_regFuncSPIBurst(void (*spi_rb)(uint8_t* pBuf, uint16_t len), 
                             void (*spi_wb)(const uint8_t* pBuf, uint16_t len));
// description: Registers call back function for ATD26DF select & deselect.
// parameter  : cs_sel     callback function for WIZCHIP select
//              cs_desel   callback fucntion for WIZCHIP deselect
// return     : 
// note       : you should register CS functions through this interface before use the driver.
void ATD26DF_regFuncCS(void(*cs_sel)(void), void(*cs_desel)(void));

#endif

AT26DF.c

#include "common.h"
#include "AT26DF.h"

static uint8_t _spi_rb_default(void);
static void _spi_wb_default(uint8_t wb);
static void _spi_burst_rb_default(uint8_t* pBuf, uint16_t len);
static void _spi_burst_wb_default(const uint8_t* pBuf, uint16_t len);
static void _nullFunc(void);

static uint8_t (*_spiRead)(void) = _spi_rb_default;
static void (*_spiWrite)(uint8_t wb) = _spi_wb_default;
static void (*_spiBurstRead)(uint8_t* pBuf, uint16_t len) = _spi_burst_rb_default;
static void (*_spiBurstWrite)(const uint8_t* pBuf, uint16_t len) = _spi_burst_wb_default;
static void(*_csSel)(void) = _nullFunc;
static void(*_csDesel)(void) = _nullFunc;

static uint8_t _spi_rb_default(void){return 0;}
static void _spi_wb_default(uint8_t wb){(void)wb;}
static void _spi_burst_rb_default(uint8_t* pBuf, uint16_t len){
  while(len > 0){
    --len;
    *pBuf++ = _spiRead();
  }
}
static void _spi_burst_wb_default(const uint8_t* pBuf, uint16_t len){
  while(len > 0){
    --len;
    _spiWrite(*pBuf++);
  }
}

static void _nullFunc(void){}

void ATD26DF_regFuncSPI(uint8_t (*spi_rb)(void), void (*spi_wb)(uint8_t wb)){
  _spiRead = spi_rb;
  _spiWrite = spi_wb;
}

void ATD26DF_regFuncSPIBurst(void (*spi_rb)(uint8_t* pBuf, uint16_t len), void (*spi_wb)(const uint8_t* pBuf, uint16_t len)){
  if(spi_rb)
    _spiBurstRead = spi_rb;
  else
    _spiBurstRead = _spi_burst_rb_default;
  if(spi_wb)
    _spiBurstWrite = spi_wb;
  else
    _spiBurstWrite = _spi_burst_wb_default;
}

void ATD26DF_regFuncCS(void(*cs_sel)(void), void(*cs_desel)(void)){
  _csSel = cs_sel;
  _csDesel = cs_desel;
}

SPI的最基本功能就是CS引脚的断言和取消断言,以及读写因此接口也只需要提供这些功能。

监控调用

现在我们来点测试中的高级内容。

我们知道我们的模块后面会调用这些接口来传送各种指令,那我们要怎么保证那些字节是按照我们希望的那样传送的呢?

测试桩

假设我们的模块有一个接口:
.h

void ATD26DF_outputABC();

我们认为ATD26DF_outputABC应该要调用我们注册的写接口并依次输出‘A’、‘B’、“C”。
那我简单用测试桩的写法为它写个测试大概是这么写(先不用实验,大概看看就行):

static uint8_t _actBuf[20];
static uint16_t _cnt = 0;
void _spi_wb_stub(uint8_t b){
  _actBuf[_cnt++] = b;
}

TEST(AT26DF, outputABC){
  ATD26DF_regFuncSPI(NULL, _spi_wb_stub);

  ATD26DF_outputABC();

  TEST_ASSERT_EQUAL_UINT16(3, _cnt);
  TEST_ASSERT_EQUAL_UINT8_ARRAY("ABC", _actBuf, 3);
}

测试桩的写法满足最简单的场景足够了,但稍微复杂点,或者要涉及多个函数之间调用顺序、检验复杂的输入参数等问题就会很吃力。

Mock

我们直接搞个高级的吧。mock一个SPI接口出来。

你既然看到了这一章,那我默认你已经学了CMock,并搭好了生成环境了。

根据模块的设计,我们需要mock出来的接口其实就4个:

SPI.h

#ifndef _SPIINTERFACE_H
#define _SPIINTERFACE_H
#include <stdint.h>
uint8_t SPI_readByte(void);
void SPI_writeByte(uint8_t b);
void SPI_SelectCS(void);
void SPI_DeselectCS(void);
#endif

因为你仔细看BrustRead和BrustWrite的默认实现,就是直接依次调用单个的那个接口,所以我们可以直接利用这个特性,只关注单字节的输入输出。

至于生成mock的配置,这样就好了:

SPI.yml

:cmock:
  :plugins:
    - :ignore
    - :ignore_arg
  :enforce_strict_ordering: true

enforce_strict_ordering为true是因为我们希望不同接口间的相互顺序也是严格的,换句话说,SelectCS当然得在所有SPI接口之前,DeselectCS必须得在所有SPI接口后。
ignore_arg是因为有些命令会要求写些dummybyte,也就是无所谓是什么,所以就会有不需要检查参数的情况。

实际在写的过程中我是写着写着才发现需要怎么修改mock生成参数的,这里不赘述了,反正要是随时发现有新的需求,就重新生成mock,然后覆盖过去就是。

然后我们到CMock下生成mock文件

ruby cmock.rb -oSPI.yml SPI.h


这样,就有了我们的SPI接口的mock文件

MockSPI.h

/* AUTOGENERATED FILE. DO NOT EDIT. */
#ifndef _MOCKSPI_H
#define _MOCKSPI_H

#include "SPI.h"

/* Ignore the following warnings, since we are copying code */
#if defined(__GNUC__) && !defined(__ICC) && !defined(__TMS470__)
#if __GNUC__ > 4 || (__GNUC__ == 4 && (__GNUC_MINOR__ > 6 || (__GNUC_MINOR__ == 6 && __GNUC_PATCHLEVEL__ > 0)))
#pragma GCC diagnostic push
#endif
#if !defined(__clang__)
#pragma GCC diagnostic ignored "-Wpragmas"
#endif
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
#pragma GCC diagnostic ignored "-Wduplicate-decl-specifier"
#endif

void MockSPI_Init(void);
void MockSPI_Destroy(void);
void MockSPI_Verify(void);




#define SPI_readByte_IgnoreAndReturn(cmock_retval) SPI_readByte_CMockIgnoreAndReturn(__LINE__, cmock_retval)
void SPI_readByte_CMockIgnoreAndReturn(UNITY_LINE_TYPE cmock_line, uint8_t cmock_to_return);
#define SPI_readByte_ExpectAndReturn(cmock_retval) SPI_readByte_CMockExpectAndReturn(__LINE__, cmock_retval)
void SPI_readByte_CMockExpectAndReturn(UNITY_LINE_TYPE cmock_line, uint8_t cmock_to_return);
#define SPI_writeByte_Ignore() SPI_writeByte_CMockIgnore()
void SPI_writeByte_CMockIgnore(void);
#define SPI_writeByte_Expect(b) SPI_writeByte_CMockExpect(__LINE__, b)
void SPI_writeByte_CMockExpect(UNITY_LINE_TYPE cmock_line, uint8_t b);
#define SPI_writeByte_IgnoreArg_b() SPI_writeByte_CMockIgnoreArg_b(__LINE__)
void SPI_writeByte_CMockIgnoreArg_b(UNITY_LINE_TYPE cmock_line);
#define SPI_SelectCS_Ignore() SPI_SelectCS_CMockIgnore()
void SPI_SelectCS_CMockIgnore(void);
#define SPI_SelectCS_Expect() SPI_SelectCS_CMockExpect(__LINE__)
void SPI_SelectCS_CMockExpect(UNITY_LINE_TYPE cmock_line);
#define SPI_DeselectCS_Ignore() SPI_DeselectCS_CMockIgnore()
void SPI_DeselectCS_CMockIgnore(void);
#define SPI_DeselectCS_Expect() SPI_DeselectCS_CMockExpect(__LINE__)
void SPI_DeselectCS_CMockExpect(UNITY_LINE_TYPE cmock_line);

#if defined(__GNUC__) && !defined(__ICC) && !defined(__TMS470__)
#if __GNUC__ > 4 || (__GNUC__ == 4 && (__GNUC_MINOR__ > 6 || (__GNUC_MINOR__ == 6 && __GNUC_PATCHLEVEL__ > 0)))
#pragma GCC diagnostic pop
#endif
#endif

#endif

MockSPI.c

/* AUTOGENERATED FILE. DO NOT EDIT. */
#include <string.h>
#include <stdlib.h>
#include <setjmp.h>
#include "unity.h"
#include "cmock.h"
#include "MockSPI.h"

static const char* CMockString_SPI_DeselectCS = "SPI_DeselectCS";
static const char* CMockString_SPI_SelectCS = "SPI_SelectCS";
static const char* CMockString_SPI_readByte = "SPI_readByte";
static const char* CMockString_SPI_writeByte = "SPI_writeByte";
static const char* CMockString_b = "b";

typedef struct _CMOCK_SPI_readByte_CALL_INSTANCE
{
  UNITY_LINE_TYPE LineNumber;
  uint8_t ReturnVal;
  int CallOrder;

} CMOCK_SPI_readByte_CALL_INSTANCE;

typedef struct _CMOCK_SPI_writeByte_CALL_INSTANCE
{
  UNITY_LINE_TYPE LineNumber;
  int CallOrder;
  uint8_t Expected_b;
  int IgnoreArg_b;

} CMOCK_SPI_writeByte_CALL_INSTANCE;

typedef struct _CMOCK_SPI_SelectCS_CALL_INSTANCE
{
  UNITY_LINE_TYPE LineNumber;
  int CallOrder;

} CMOCK_SPI_SelectCS_CALL_INSTANCE;

typedef struct _CMOCK_SPI_DeselectCS_CALL_INSTANCE
{
  UNITY_LINE_TYPE LineNumber;
  int CallOrder;

} CMOCK_SPI_DeselectCS_CALL_INSTANCE;

static struct MockSPIInstance
{
  int SPI_readByte_IgnoreBool;
  uint8_t SPI_readByte_FinalReturn;
  CMOCK_MEM_INDEX_TYPE SPI_readByte_CallInstance;
  int SPI_writeByte_IgnoreBool;
  CMOCK_MEM_INDEX_TYPE SPI_writeByte_CallInstance;
  int SPI_SelectCS_IgnoreBool;
  CMOCK_MEM_INDEX_TYPE SPI_SelectCS_CallInstance;
  int SPI_DeselectCS_IgnoreBool;
  CMOCK_MEM_INDEX_TYPE SPI_DeselectCS_CallInstance;
} Mock;

extern jmp_buf AbortFrame;
int GlobalExpectCount;
int GlobalVerifyOrder;

void MockSPI_Verify(void)
{
  UNITY_LINE_TYPE cmock_line = TEST_LINE_NUM;
  if (Mock.SPI_readByte_IgnoreBool)
    Mock.SPI_readByte_CallInstance = CMOCK_GUTS_NONE;
  UNITY_SET_DETAIL(CMockString_SPI_readByte);
  UNITY_TEST_ASSERT(CMOCK_GUTS_NONE == Mock.SPI_readByte_CallInstance, cmock_line, CMockStringCalledLess);
  if (Mock.SPI_writeByte_IgnoreBool)
    Mock.SPI_writeByte_CallInstance = CMOCK_GUTS_NONE;
  UNITY_SET_DETAIL(CMockString_SPI_writeByte);
  UNITY_TEST_ASSERT(CMOCK_GUTS_NONE == Mock.SPI_writeByte_CallInstance, cmock_line, CMockStringCalledLess);
  if (Mock.SPI_SelectCS_IgnoreBool)
    Mock.SPI_SelectCS_CallInstance = CMOCK_GUTS_NONE;
  UNITY_SET_DETAIL(CMockString_SPI_SelectCS);
  UNITY_TEST_ASSERT(CMOCK_GUTS_NONE == Mock.SPI_SelectCS_CallInstance, cmock_line, CMockStringCalledLess);
  if (Mock.SPI_DeselectCS_IgnoreBool)
    Mock.SPI_DeselectCS_CallInstance = CMOCK_GUTS_NONE;
  UNITY_SET_DETAIL(CMockString_SPI_DeselectCS);
  UNITY_TEST_ASSERT(CMOCK_GUTS_NONE == Mock.SPI_DeselectCS_CallInstance, cmock_line, CMockStringCalledLess);
}

void MockSPI_Init(void)
{
  MockSPI_Destroy();
}

void MockSPI_Destroy(void)
{
  CMock_Guts_MemFreeAll();
  memset(&Mock, 0, sizeof(Mock));
  GlobalExpectCount = 0;
  GlobalVerifyOrder = 0;
}

uint8_t SPI_readByte(void)
{
  UNITY_LINE_TYPE cmock_line = TEST_LINE_NUM;
  CMOCK_SPI_readByte_CALL_INSTANCE* cmock_call_instance;
  UNITY_SET_DETAIL(CMockString_SPI_readByte);
  cmock_call_instance = (CMOCK_SPI_readByte_CALL_INSTANCE*)CMock_Guts_GetAddressFor(Mock.SPI_readByte_CallInstance);
  Mock.SPI_readByte_CallInstance = CMock_Guts_MemNext(Mock.SPI_readByte_CallInstance);
  if (Mock.SPI_readByte_IgnoreBool)
  {
    UNITY_CLR_DETAILS();
    if (cmock_call_instance == NULL)
      return Mock.SPI_readByte_FinalReturn;
    Mock.SPI_readByte_FinalReturn = cmock_call_instance->ReturnVal;
    return cmock_call_instance->ReturnVal;
  }
  UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, CMockStringCalledMore);
  cmock_line = cmock_call_instance->LineNumber;
  if (cmock_call_instance->CallOrder > ++GlobalVerifyOrder)
    UNITY_TEST_FAIL(cmock_line, CMockStringCalledEarly);
  if (cmock_call_instance->CallOrder < GlobalVerifyOrder)
    UNITY_TEST_FAIL(cmock_line, CMockStringCalledLate);
  UNITY_CLR_DETAILS();
  return cmock_call_instance->ReturnVal;
}

void SPI_readByte_CMockIgnoreAndReturn(UNITY_LINE_TYPE cmock_line, uint8_t cmock_to_return)
{
  CMOCK_MEM_INDEX_TYPE cmock_guts_index = CMock_Guts_MemNew(sizeof(CMOCK_SPI_readByte_CALL_INSTANCE));
  CMOCK_SPI_readByte_CALL_INSTANCE* cmock_call_instance = (CMOCK_SPI_readByte_CALL_INSTANCE*)CMock_Guts_GetAddressFor(cmock_guts_index);
  UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, CMockStringOutOfMemory);
  memset(cmock_call_instance, 0, sizeof(*cmock_call_instance));
  Mock.SPI_readByte_CallInstance = CMock_Guts_MemChain(Mock.SPI_readByte_CallInstance, cmock_guts_index);
  Mock.SPI_readByte_IgnoreBool = (int)0;
  cmock_call_instance->LineNumber = cmock_line;
  cmock_call_instance->ReturnVal = cmock_to_return;
  Mock.SPI_readByte_IgnoreBool = (int)1;
}

void SPI_readByte_CMockExpectAndReturn(UNITY_LINE_TYPE cmock_line, uint8_t cmock_to_return)
{
  CMOCK_MEM_INDEX_TYPE cmock_guts_index = CMock_Guts_MemNew(sizeof(CMOCK_SPI_readByte_CALL_INSTANCE));
  CMOCK_SPI_readByte_CALL_INSTANCE* cmock_call_instance = (CMOCK_SPI_readByte_CALL_INSTANCE*)CMock_Guts_GetAddressFor(cmock_guts_index);
  UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, CMockStringOutOfMemory);
  memset(cmock_call_instance, 0, sizeof(*cmock_call_instance));
  Mock.SPI_readByte_CallInstance = CMock_Guts_MemChain(Mock.SPI_readByte_CallInstance, cmock_guts_index);
  Mock.SPI_readByte_IgnoreBool = (int)0;
  cmock_call_instance->LineNumber = cmock_line;
  cmock_call_instance->CallOrder = ++GlobalExpectCount;
  cmock_call_instance->ReturnVal = cmock_to_return;
  UNITY_CLR_DETAILS();
}

void SPI_writeByte(uint8_t b)
{
  UNITY_LINE_TYPE cmock_line = TEST_LINE_NUM;
  CMOCK_SPI_writeByte_CALL_INSTANCE* cmock_call_instance;
  UNITY_SET_DETAIL(CMockString_SPI_writeByte);
  cmock_call_instance = (CMOCK_SPI_writeByte_CALL_INSTANCE*)CMock_Guts_GetAddressFor(Mock.SPI_writeByte_CallInstance);
  Mock.SPI_writeByte_CallInstance = CMock_Guts_MemNext(Mock.SPI_writeByte_CallInstance);
  if (Mock.SPI_writeByte_IgnoreBool)
  {
    UNITY_CLR_DETAILS();
    return;
  }
  UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, CMockStringCalledMore);
  cmock_line = cmock_call_instance->LineNumber;
  if (cmock_call_instance->CallOrder > ++GlobalVerifyOrder)
    UNITY_TEST_FAIL(cmock_line, CMockStringCalledEarly);
  if (cmock_call_instance->CallOrder < GlobalVerifyOrder)
    UNITY_TEST_FAIL(cmock_line, CMockStringCalledLate);
  if (!cmock_call_instance->IgnoreArg_b)
  {
    UNITY_SET_DETAILS(CMockString_SPI_writeByte,CMockString_b);
    UNITY_TEST_ASSERT_EQUAL_HEX8(cmock_call_instance->Expected_b, b, cmock_line, CMockStringMismatch);
  }
  UNITY_CLR_DETAILS();
}

void CMockExpectParameters_SPI_writeByte(CMOCK_SPI_writeByte_CALL_INSTANCE* cmock_call_instance, uint8_t b)
{
  cmock_call_instance->Expected_b = b;
  cmock_call_instance->IgnoreArg_b = 0;
}

void SPI_writeByte_CMockIgnore(void)
{
  Mock.SPI_writeByte_IgnoreBool = (int)1;
}

void SPI_writeByte_CMockExpect(UNITY_LINE_TYPE cmock_line, uint8_t b)
{
  CMOCK_MEM_INDEX_TYPE cmock_guts_index = CMock_Guts_MemNew(sizeof(CMOCK_SPI_writeByte_CALL_INSTANCE));
  CMOCK_SPI_writeByte_CALL_INSTANCE* cmock_call_instance = (CMOCK_SPI_writeByte_CALL_INSTANCE*)CMock_Guts_GetAddressFor(cmock_guts_index);
  UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, CMockStringOutOfMemory);
  memset(cmock_call_instance, 0, sizeof(*cmock_call_instance));
  Mock.SPI_writeByte_CallInstance = CMock_Guts_MemChain(Mock.SPI_writeByte_CallInstance, cmock_guts_index);
  Mock.SPI_writeByte_IgnoreBool = (int)0;
  cmock_call_instance->LineNumber = cmock_line;
  cmock_call_instance->CallOrder = ++GlobalExpectCount;
  CMockExpectParameters_SPI_writeByte(cmock_call_instance, b);
  UNITY_CLR_DETAILS();
}

void SPI_writeByte_CMockIgnoreArg_b(UNITY_LINE_TYPE cmock_line)
{
  CMOCK_SPI_writeByte_CALL_INSTANCE* cmock_call_instance = (CMOCK_SPI_writeByte_CALL_INSTANCE*)CMock_Guts_GetAddressFor(CMock_Guts_MemEndOfChain(Mock.SPI_writeByte_CallInstance));
  UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, CMockStringIgnPreExp);
  cmock_call_instance->IgnoreArg_b = 1;
}

void SPI_SelectCS(void)
{
  UNITY_LINE_TYPE cmock_line = TEST_LINE_NUM;
  CMOCK_SPI_SelectCS_CALL_INSTANCE* cmock_call_instance;
  UNITY_SET_DETAIL(CMockString_SPI_SelectCS);
  cmock_call_instance = (CMOCK_SPI_SelectCS_CALL_INSTANCE*)CMock_Guts_GetAddressFor(Mock.SPI_SelectCS_CallInstance);
  Mock.SPI_SelectCS_CallInstance = CMock_Guts_MemNext(Mock.SPI_SelectCS_CallInstance);
  if (Mock.SPI_SelectCS_IgnoreBool)
  {
    UNITY_CLR_DETAILS();
    return;
  }
  UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, CMockStringCalledMore);
  cmock_line = cmock_call_instance->LineNumber;
  if (cmock_call_instance->CallOrder > ++GlobalVerifyOrder)
    UNITY_TEST_FAIL(cmock_line, CMockStringCalledEarly);
  if (cmock_call_instance->CallOrder < GlobalVerifyOrder)
    UNITY_TEST_FAIL(cmock_line, CMockStringCalledLate);
  UNITY_CLR_DETAILS();
}

void SPI_SelectCS_CMockIgnore(void)
{
  Mock.SPI_SelectCS_IgnoreBool = (int)1;
}

void SPI_SelectCS_CMockExpect(UNITY_LINE_TYPE cmock_line)
{
  CMOCK_MEM_INDEX_TYPE cmock_guts_index = CMock_Guts_MemNew(sizeof(CMOCK_SPI_SelectCS_CALL_INSTANCE));
  CMOCK_SPI_SelectCS_CALL_INSTANCE* cmock_call_instance = (CMOCK_SPI_SelectCS_CALL_INSTANCE*)CMock_Guts_GetAddressFor(cmock_guts_index);
  UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, CMockStringOutOfMemory);
  memset(cmock_call_instance, 0, sizeof(*cmock_call_instance));
  Mock.SPI_SelectCS_CallInstance = CMock_Guts_MemChain(Mock.SPI_SelectCS_CallInstance, cmock_guts_index);
  Mock.SPI_SelectCS_IgnoreBool = (int)0;
  cmock_call_instance->LineNumber = cmock_line;
  cmock_call_instance->CallOrder = ++GlobalExpectCount;
  UNITY_CLR_DETAILS();
}

void SPI_DeselectCS(void)
{
  UNITY_LINE_TYPE cmock_line = TEST_LINE_NUM;
  CMOCK_SPI_DeselectCS_CALL_INSTANCE* cmock_call_instance;
  UNITY_SET_DETAIL(CMockString_SPI_DeselectCS);
  cmock_call_instance = (CMOCK_SPI_DeselectCS_CALL_INSTANCE*)CMock_Guts_GetAddressFor(Mock.SPI_DeselectCS_CallInstance);
  Mock.SPI_DeselectCS_CallInstance = CMock_Guts_MemNext(Mock.SPI_DeselectCS_CallInstance);
  if (Mock.SPI_DeselectCS_IgnoreBool)
  {
    UNITY_CLR_DETAILS();
    return;
  }
  UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, CMockStringCalledMore);
  cmock_line = cmock_call_instance->LineNumber;
  if (cmock_call_instance->CallOrder > ++GlobalVerifyOrder)
    UNITY_TEST_FAIL(cmock_line, CMockStringCalledEarly);
  if (cmock_call_instance->CallOrder < GlobalVerifyOrder)
    UNITY_TEST_FAIL(cmock_line, CMockStringCalledLate);
  UNITY_CLR_DETAILS();
}

void SPI_DeselectCS_CMockIgnore(void)
{
  Mock.SPI_DeselectCS_IgnoreBool = (int)1;
}

void SPI_DeselectCS_CMockExpect(UNITY_LINE_TYPE cmock_line)
{
  CMOCK_MEM_INDEX_TYPE cmock_guts_index = CMock_Guts_MemNew(sizeof(CMOCK_SPI_DeselectCS_CALL_INSTANCE));
  CMOCK_SPI_DeselectCS_CALL_INSTANCE* cmock_call_instance = (CMOCK_SPI_DeselectCS_CALL_INSTANCE*)CMock_Guts_GetAddressFor(cmock_guts_index);
  UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, CMockStringOutOfMemory);
  memset(cmock_call_instance, 0, sizeof(*cmock_call_instance));
  Mock.SPI_DeselectCS_CallInstance = CMock_Guts_MemChain(Mock.SPI_DeselectCS_CallInstance, cmock_guts_index);
  Mock.SPI_DeselectCS_IgnoreBool = (int)0;
  cmock_call_instance->LineNumber = cmock_line;
  cmock_call_instance->CallOrder = ++GlobalExpectCount;
  UNITY_CLR_DETAILS();
}

把这两个文件加入咱的工程中。因为这是对SPI的mock,其实我更倾向于把它们放到SPI/test目录下,注意添加include目录。当然你要放到AT26DF/test下也没有问题。记得把上面那个SPI.h文件也放过去。

注:如果后面编译时发现报错无法解析的外部符号。

打开MockSPI.c,把GlobalExpectCount和GlobalVerifyOrder前的extern删掉就好。

那这样子,我们的那个小测试就变成这样写了:

#include "MockSPI.h"
……
TEST(AT26DF, outputABC){
  MockSPI_Init();
  ATD26DF_regFuncSPI(SPI_readByte, SPI_writeByte);
  ATD26DF_regFuncCS(SPI_SelectCS, SPI_DeselectCS);
  SPI_writeByte_Expect('A');
  SPI_writeByte_Expect('B');
  SPI_writeByte_Expect('C');

  ATD26DF_outputABC();
  
  MockSPI_Verify();
  MockSPI_Destroy();
}

在每个单元测试里,我们把我们的mock接口注册给模块使用,设置我们的期望,最后验证接口是否如期望那样运作。

mock不仅可以监视特定函数怎么被调用的,还能监视不同函数间按什么顺序被调用,每次调用时返回什么值,这样会精细的操作。

结语

这一章中,我们为模块设计了一个依赖于抽象的通信方案,造好了监控模块通信细节的工具。但其实我们还没真正开始写测试用例,后面,我们来看看整个TDD写测试的过程。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值