打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
C/C++第31讲——别害怕单元测试

  说到单元测试或者白盒测试,很多人的条件反射是:

  1)复杂:需要学习大量的测试理论,需要学会使用复杂的工具软件;

  2)费事:编写测试程序需要耗费大量的时间;

  3)无效:做了单元测试也没啥用处。

  其实单元测试、白盒测试的特点,与大家所想象的正好相反,它们的特点是:

  1)简单:不需要学习测试理论、甚至不需要学习工具软件就可以实施单元测试;

  2)轻松:在编写业务代码的同时,可以轻易地完成测试代码的编写;

  3)有效:据统计,80%的故障可以在单元测试阶段排除掉,极大提升软件质量,为企业创造巨大收益。

  其实,大家编写C/C++软件时,尤其是编写嵌入式代码时,不进行单元测试,原因可以用一个词来形容:害怕!

  

  这里,我们介绍一下单元测试、白盒测试的概念,然后提供一个可以运行的简单程序,不使用第三方工具,实现单元测试,让您开心地在工作中运用起来。

  

  单元测试:对程序的最小单元进行测试,例如:C语言的最小单元是函数;

  白盒测试:根据源代码执行测试的方法。

  如果不纠结测试理论,我们可以这样简单地认为:“单元测试是最有效的白盒测试”,“要做白盒测试,做好单元测试基本上也就够了!”

  下面,我们用实际的例子,执行单元测试。

  建立一个service目录,作为软件的根目录。

  在service目录下,建立src、test、obj、bin四个子目录,功能分别为:

  src:存放业务功能代码;

  test:存放单元测试代码;

  obj:存放编译生成的目标文件;

  bin:存放编译输出的可执行文件。

  这里,Func.c和Func.h就是我们要开发的业务功能。

  Func.c文件的源码如下:

  #include "Func.h"

  int IsCapitalChar(char cLetter)

  {

  if (cLetter >='a' && cLetter <='z')

  {

  return 0;

  }

  return 1;

  }

  Func.h文件的源代码如下:

  #ifndef __FUNC_H__

  #define __FUNC_H__

  int IsCapitalChar(char cLetter);

  #endif//__FUNC_H__

  这两个文件,功能就是判断一个字母是否为大写字母。

  #ifndef __DEBUG_DEFINE_H__

  #define __DEBUG_DEFINE_H__

  //#define DEBUG_MODE

  #endif//__DEBUG_DEFINE_H__

  DebugDefine.h文件的功能是定义调试开关:

  1)对于测试版本,需要进行瘦身单元测试,去掉DEBUG_MODE前面的注释符,让DEBUG_MODE被定义;

  2)对于发行版本,不需要进行单元测试,将DEBUG_MODE的宏定义注释掉,让DEBUG_MODE不被定义。

  Assert.c和Assert.h是我们单元测试的记录中心,并提供执行单元测试的通用工具函数。

  Assert.c文件的源代码为:

  #include

  #include "../src/DebugDefine.h"

  #include "Assert.h"

  #ifdef DEBUG_MODE

  struct AssertInfo

  {

  int m_iTotalTests;

  int m_iPassedTests;

  int m_iUnpassedTests;

  };

  static struct AssertInfo s_assertInfo;

  void InitAssertInfo()

  {

  s_assertInfo.m_iTotalTests=0;

  s_assertInfo.m_iPassedTests=0;

  s_assertInfo.m_iUnpassedTests=0;

  }

  void Assert(const char* pcFileName, int iFileLine, int iPassed)

  {

  s_assertInfo.m_iTotalTests++;

  if (iPassed)

  {

  s_assertInfo.m_iPassedTests++;

  }

  else

  {

  s_assertInfo.m_iUnpassedTests++;

  }

  printf("%s[%d] %s Total: %d, Passed: %d, Unpassed:%d

  ", pcFileName, iFileLine, iPassed?" SUCCESSFUL ":" FAILED ", s_assertInfo.m_iTotalTests, s_assertInfo.m_iPassedTests, s_assertInfo.m_iUnpassedTests);

  }

  #endif//DEBUG_MODE

  Assert.h文件的源代码为:

  #ifndef __ASSERT_H__

  #define __ASSERT_H__

  #include

  #include "../src/DebugDefine.h"

  #ifdef DEBUG_MODE

  void InitAssertInfo();

  void Assert(const char* pcFileName, int iFileLine, int iPassed);

  #define ASSERT_TEST(iPassed) Assert(__FILE__, __LINE__, iPassed)

  #else//DEBUG_MODE

  #define InitAssertInfo() NULL

  #define Assert() NULL

  #define ASSERT_TEST(iPassed) NULL

  #endif//DEBUG_MODE

  #endif//__ASSERT_H__

  FuncTest.c和FuncTest.h为单元测试用例代码。

  FuncTest.c的源代码为:

  #include "../src/Func.h"

  #include "Assert.h"

  #include "FuncTest.h"

  void TestIsCapitalChar()

  {

  ASSERT_TEST(IsCapitalChar('A'));

  ASSERT_TEST(!IsCapitalChar('a'));

  ASSERT_TEST(IsCapitalChar('Z'));

  ASSERT_TEST(!IsCapitalChar('z'));

  ASSERT_TEST(!IsCapitalChar('0'));

  ASSERT_TEST(!IsCapitalChar('#'));

  }

  FuncTest.h的源代码为:

  #ifndef __FUNC_TEST_H__

  #define __FUNC_TEST_H__

  void TestIsCapitalChar();

  #endif//__FUNC_TEST_H__

  MainTest.c和MainTest.h是单元测试的总入口。

  MainTest.c文件的源代码为:

  #include "../src/DebugDefine.h"

  #include "Assert.h"

  #include "FuncTest.h"

  #include "MainTest.h"

  #ifndef DEBUG_MODE

  void TestMain()

  {

  }

  #else//DEBUG_MODE

  void TestMain()

  {

  InitAssertInfo();

  TestIsCapitalChar();

  }

  #endif//DEBUG_MODE

  MainTest.h文件的源代码为:

  #ifndef __MAIN_TEST_H__

  #define __MAIN_TEST_H__

  void TestMain();

  #endif//__MAIN_TEST_H__

  Main.c文件定义main函数。

  Main.c文件的源代码为:

  #include

  #include "Func.h"

  #include "../test/MainTest.h"

  int main()

  {

  TestMain();

  if (IsCapitalChar('A'))

  {

  printf("A is capital.

  ");

  }

  return 0;

  }

  Makefile文件的内容如下:

  CC=gcc

  TARGETFILE=bin/service.bin

  OBJFILES=obj/Main.o \

  obj/Func.o \

  obj/Assert.o \

  obj/MainTest.o \

  obj/FuncTest.o

  APPLICATION_SOURCE_DIR=-I src/ -I test/

  INCLUDEFILES=src/Func.h \

  src/DebugDefine.h \

  test/Assert.h \

  test/FuncTest.h \

  test/MainTest.h

  .PHONY: build

  build: $(TARGETFILE)

  @echo " build $(TARGETFILE) successfully."

  @echo

  clean:

  rm -f obj/*.o

  $(TARGETFILE): $(OBJFILES)

  $(CC) $(INCLUDEDIRS) -o $(TARGETFILE) $(OBJFILES) $(LINKLIBS)

  obj/Main.o: src/Main.c $(INCLUDEFILES)

  $(CC) $(INCLUDEDIRS) -c src/Main.c -o obj/Main.o

  obj/Func.o: src/Func.c $(INCLUDEFILES)

  $(CC) $(INCLUDEDIRS) -c src/Func.c -o obj/Func.o

  obj/FuncTest.o: test/FuncTest.c $(INCLUDEFILES)

  $(CC) $(INCLUDEDIRS) -c test/FuncTest.c -o obj/FuncTest.o

  obj/Assert.o: test/Assert.c $(INCLUDEFILES)

  $(CC) $(INCLUDEDIRS) -c test/Assert.c -o obj/Assert.o

  obj/MainTest.o: test/MainTest.c $(INCLUDEFILES)

  $(CC) $(INCLUDEDIRS) -c test/MainTest.c -o obj/MainTest.o

  执行单元测试,只需要打开DEBUG_MODE开关。下面是DebugDefine.h文件内容、程序的编译过程、可执行文件大小、以及程序运行时的完整图片。

  

  可以看到,当我们打开DEBUG_MODE开关后,生成的service.bin文件大小为8992字节,执行程序时,会执行单元测试,显示单元测试的情况。

  发布正式版本,只需要关闭DEBUG_MODE开关。下面是DebugDefine.h文件内容、编译过程、可执行文件大小、以及程序运行时的完整图片。

  

  可以看到,当我们关闭DEBUG_MODE开关后,生成的service.bin文件大小为8832字节,执行程序时,没有单元测试的内容了。

  上面的代码,可以分成下面几类:

  Main.c、Func.c、Func.h是我们正常开发需要编写的业务代码;

  DebugDefine.h、Assert.c、Assert.h是单元测试框架,我们直接拿过来用即可,不需要每次都开发;

  MainTest.c、MainTest.h、FuncTest.c、FuncTest.h是单元测试用例代码,随着业务代码的增加,单元测试代码也会增加,但是编写难度很低。

  上面使用的单元测试框架,完全是我简单编写的,没有使用任何第三方的测试工具。通过上面的例子,当我们执行单元测试时,可以轻松地发现程序中的错误。

  看完本文,是不是觉得编写单元测试代码、执行白盒测试是特别简单、轻松的事?

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Debug.Assert-断言 有什么用
高效能编程的七个好习惯
C语言单元测试
盘点Go中的开发神器
lua
关于bin和obj文件夹。debug 和release的区别(转)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服