Mock的基本概念和方法(续)

晨曦之光 发布于 2012/03/09 14:15
阅读 1K+
收藏 0

本博客(http://blog.csdn.net/livelylittlefish )贴出作者(三二一@小鱼)相关研究、学习内容所做的笔记,欢迎广大朋友指正!

Content

0.

1. 平台

2. 第三方库

3. 如何使用CMockUnity

3.1 修改业务代码

3.2 编写单元测试

3.3 如何Mock并测试?

(1) 生成mock文件

(2) 生成单元测试runner

(3) 拷贝或修改生成的mock文件

(4) build

(5) 讨论

4. 总结

Reference

Appendix 1: Cmock installation

Appendix 2: Unity installation

Appendix 3: Ruby installation

Appendix 4: Mockyour_file.c

Appendix 5: my_business_runner.c

 

 

0.

 

Mock的基本概念和方法一文中,笔者在结尾提出了一个问题,有没有一种比较好的方法或者工具,能自动产生一些文件或者目录,供测试使用?

 

有,一定有,因为,这个世界从来不缺聪明的发明人。

 

那么,本文仍以Mock的基本概念和方法一文中的例子为例,重点讲述使用CmockUnity等工具进行单元测试的方法。

注:本文的实验,CUnit不是必须的。

 

 

1. 平台

 

Cygwin, or Linux

 

工作目录:

Linux : # cd /usr/src/cmock_sample

Cygwin: $ cd /usr/src/cmock_sample

 

2. 第三方库

 

本文实验用到的第三方库分别为:

 

cmock_2_0_204.zip:http://sourceforge.net/projects/cmock/files/cmock/cmock2.0/cmock_2_0_204.zip

unity_2_0_113.zip:http://sourceforge.net/projects/unity/files/unity/unity2.0/unity_2_0_113.zip

ruby-1.9-stable.tar.gz:http://ftp.ruby-lang.org/pub/ruby/ruby-1.9-stable.tar.gz

 

实际上,cmock_2_0_204.zip在其vendor目录中包含了Unity

 

3. 如何使用CMockUnity

 

以下将以cygwin平台为例。

 

3.1 修改业务代码

 

该例子中的业务代码文件:my_business.c

为例使用CMockUnity工具,需要修改my_business.c文件,将其中的main函数删除,并增加my_business.h文件。如下。

/*

 * my bussiness.h

 */

 

int read_file();

my_business.c文件内容。

/*

 * my bussiness

 */

 

#include "my_business.h"

#include

 

int read_file()

{

    FILE* fp = your_file_open("data.txt");

    //assert(fp != 0);

 

    /*

     * here my business start, for example, read data from the file.

     * for test, only print the fp.

     */

    printf("%s, %d: file handle = 0x%x/n", __FUNCTION__, __LINE__, (unsigned int)fp);

 

    your_file_close(fp);

    return 0;

}

注:实际上,或许某些场合不需要该头文件,但为了本文说明问题的需要,提供该文件。后面的Unity工具将根据该文件自动生成mock文件。

 

3.2 编写单元测试

 

要对业务代码做单元测试,应该事先编写好单元测试代码,文件为my_business_unittest.c,如下。

/*

 * my bussiness unittest

 */

 

#include "my_business.h"

#include "unity.h"

 

void setUp(void)

{

}

 

void tearDown(void)

{

}

 

void test_read_file()

{

    TEST_ASSERT_EQUAL(0, read_file());

}

 

void test_read_file2()

{

    TEST_ASSERT_NOT_EQUAL(1, read_file());

}

3.3 如何Mock并测试?

 

(1) 生成mock文件

 

业务代码my_business.h/.c依赖your_file.h/.c,根据Cmock/Unity框架,要根据your_file.h自动生成mock文件。Cmock框架提供了该自动生成的功能,是用ruby写的scripts,在./cmock/lib目录下,在运行这些脚本时,使用ruby命令,因此需要第三方库ruby。完成该自动生成功能的脚本是./cmock/lib/cmock.rb,可以处理1个或多个.h文件。

$ mkdir mocks  //默认情况下,生成的mock文件保存在该目录,故需事先建立好

$ ruby /usr/src/cmock/lib/cmock.rb your_file.h  //运行该脚本生成mock文件

Creating mock for your_file...

$ cd mocks

$ ls

Mockyour_file.c  Mockyour_file.h  //自动生成的mock文件

$ cd ..

此处,笔者仅提供Mockyour_file.h的内容,Mockyour_file.c文件太大,请参考附录。

/* AUTOGENERATED FILE. DO NOT EDIT. */

#ifndef _MOCKYOUR_FILE_H

#define _MOCKYOUR_FILE_H

 

#include "your_file.h"

 

void Mockyour_file_Init(void);

void Mockyour_file_Destroy(void);

void Mockyour_file_Verify(void);

 

#define your_file_open_ExpectAndReturn(fname, cmock_retval) your_file_open_CMockExpectAndReturn(__LINE__, fname, cmock_retval)

void your_file_open_CMockExpectAndReturn(UNITY_LINE_TYPE cmock_line, char* fname, FILE* cmock_to_return);

#define your_file_close_Expect(fp) your_file_close_CMockExpect(__LINE__, fp)

void your_file_close_CMockExpect(UNITY_LINE_TYPE cmock_line, FILE* fp);

 

#endif

(2) 生成单元测试runner

 

Unity工具提供了根据单元测试文件自动生成runner文件的功能,该runner文件中包含main()函数,这就是单元测试程序启动的入口。这也是为什么要修改my_business.c,将其中的main()删除的原因。

 

What a great idea! Ruby真的是一门很好的语言,是时候学习了。

 

这将节省开发人员很多的时间,使开发人员将精力集中在单元测试本身和设计测试用例上。

$ ruby /usr/src/unity/auto/generate_test_runner.rb my_business_unittest.c my_business_runner.c

$ ls

data.txt  makefile  my_business.c  my_business_runner.c    your_file.c

make.bat  mocks     my_business.h  my_business_unittest.c  your_file.h

(3) 拷贝或修改生成的mock文件

 

因为生成的mock文件放在mocks目录,且在Mockyour_file.h中包含了your_file.h,但mocks目录中并没有your_file.h文件,而是在上一级目录中,因此需要将其拷贝到上一级目录,或者修改Mockyour_file.h文件中包含your_file.h的路径。笔者选择拷贝。

$ cp mocks/Mockyour_file.h Mockyour_file.h

$ cp mocks/Mockyour_file.c Mockyour_file.c

自此,要进行单元测试所需的.h/.c文件全部建立,小结一下,看看有哪些文件。

 

你的代码文件:your_file.h/.c        (该文件在测试时不再使用)

mocked文件 Mockyour_file.h/.c    (在测试时要依赖该文件)

业务代码文件:my_business.h/.c

业务测试文件:my_business_unittest.c

测试运行文件:my_business_runner.c  (main()函数即在其中)

 

(4) build

 

本文的实验,笔者编写了makefile(Linux平台/Cygwin环境)或者make.bat(win32平台),让build更加快速和可控。

 

使用CMokcUnity进行单元测试和Mock,一定会依赖CMockUnity,因此这两个工具的源代码cmock.cunity.c也一定要编译到并链接到目标文件。那么对于CMockUnity,我们需要或者依赖哪些文件呢?

 

不要被CMockUnity的内容吓到!虽然其中有很多目录和文件,但其核心(框架)文件很少,而这些核心文件正是build需要的。

 

CMock: ./cmock/src/cmock.c  //CMockcore文件在./cmock/src目录

       ./cmock/src/cmock.h

Unity: ./unity/src/unity.c  //Unitycore文件在./unity/src目录

       ./unity/src/unity.h

       ./unity/src/unity_internals.h

 

综上,编写的makefile文件和make.bat文件如下。makefile文件适用于cygwinLinux平台。

CXX = gcc

CXXFLAGS += -g -Wall -Wextra

 

TESTS = my_business

 

CMOCK_DIR = /usr/src/cmock

CMOCK_SRC = $(CMOCK_DIR)/src

UNITY_DIR = /usr/src/unity

UNITY_SRC = $(UNITY_DIR)/src

 

ifeq ($(OS), Windows_NT)

TARGET_EXTENSION=.exe

else

TARGET_EXTENSION=.out

endif

 

TARGET = $(TESTS)$(TARGET_EXTENSION)

 

CLEANUP = rm -f $(TARGET) *.o

 

all : $(TARGET)

 

clean :

$(CLEANUP)

 

unity.o: $(UNITY_SRC)/unity.c

$(CXX) $(CXXFLAGS) -I$(UNITY_SRC) -c $^

 

cmock.o: $(CMOCK_SRC)/cmock.c

$(CXX) $(CXXFLAGS) -I$(CMOCK_SRC) -I$(UNITY_SRC) -c $^

 

mockyour_file.o: mockyour_file.c

$(CXX) $(CXXFLAGS) -I$(CMOCK_SRC) -I$(UNITY_SRC) -c $^

 

my_business.o: my_business.c

$(CXX) $(CXXFLAGS) -c $^

 

my_business_unittest.o: my_business_unittest.c

$(CXX) $(CXXFLAGS) -I$(UNITY_SRC) -c $^

 

my_business_runner.o: my_business_runner.c

$(CXX) $(CXXFLAGS) -I$(UNITY_SRC) -c $^

 

$(TARGET): unity.o cmock.o mockyour_file.o my_business.o my_business_unittest.o my_business_runner.o

$(CXX) $(CXXFLAGS) $^ -o $@

(5) 讨论

 

这真的是一种前无古人的方法,CMock能自动生成mock文件,让你进行单元测试;Unity能自动生成单元测试runner,让你的单元测试跑起来。CMockUnity的出现,为广大开发者节省了宝贵的时间,使你的精力集中于单元测试本身和测试用例的设计上。

 

4. 总结

 

本文,笔者以一个例子讨论了使用CMockUnity进行mock和单元测试的方法和过程,主要关注如何使用这两个工具并buildbuild时,要注意CMockUnitycmock.cunity.c也要编译并链接到目标文件。

本文重点如下。

  • 生成mock文件
  • 生成单元测试runner
  • build

 

广大软件开发者们,如果要做C项目的单元测试,尤其是嵌入式软件的单元测试,那就去大胆尝试CMockUnity吧!

 

 

Reference

 

CMock简介:CMock Summary.pdf

Unity简介:Unity Summary.pdf

CMock设计原理:Functionality_and_Design_of_the_CMock_Framework.pdf

如何mocking嵌入式软件:Mocking the Embedded World - Test-Driven Development, Continuous Integration, and Design Patterns.pdf

 

http://sourceforge.net/apps/trac/cmock

http://sourceforge.net/apps/trac/unity

http://sourceforge.net/projects/unity/forums/forum/770030/topic/4067272

http://sourceforge.net/projects/unity/forums/forum/770030/topic/3795145

http://sourceforge.net/apps/trac/cmock/wiki/EclipseIde

http://sourceforge.net/apps/mediawiki/unity/index.php?title=Eclipse_IDE_Integration

http://en.wikipedia.org/wiki/List_of_unit_testing_frameworks (各种单元测试工具比较)

http://sourceforge.net/projects/unity/forums/forum/770030/topic/4067272 (CmockUnity的关系)

http://meekrosoft.wordpress.com/2010/01/29/unit-test-embedded-software-in-3-easy-steps (嵌入式软件单元测试的3个步骤)

 

http://www.lulu.com/product/paperback/embedded-testing-with-unity-and-cmock/14408590?productTrackingContext=search_results/search_shelf/center/1 , By Mark VanderVoord.

 

 

Appendix 1: Cmock installation

 

# cd /usr/src

# wget http://sourceforge.net/projects/cmock/files/cmock/cmock2.0/cmock_2_0_204.zip

# unzip -x cmock_2_0_204.zip

# cd cmock

编译需要ruby的构建系统rake,有待研究。

 

Appendix 2: Unity installation

 

# cd /usr/src

# wget http://sourceforge.net/projects/unity/files/unity/unity2.0/unity_2_0_113.zip

# unzip -x unity_2_0_113.zip

# cd unity

# make

# cd examples

# make

 

Appendix 3: Ruby installation

 

$ cd /usr/src

$ wget http://ftp.ruby-lang.org/pub/ruby/ruby-1.9-stable.tar.gz

$ tar.exe  -zxvf ruby-1.9-stable.tar.gz

$ cd ruby-1.9.2-p180

$ ./configure && make && make install

 

Appendix 4: Mockyour_file.c

/* AUTOGENERATED FILE. DO NOT EDIT. */
#include <string.h>
#include <stdlib.h>
#include <setjmp.h>
#include "unity.h"
#include "cmock.h"
#include "Mockyour_file.h"
typedef struct _CMOCK_your_file_open_CALL_INSTANCE
{
  UNITY_LINE_TYPE LineNumber;
  FILE* ReturnVal;
  char* Expected_fname;
} CMOCK_your_file_open_CALL_INSTANCE;
typedef struct _CMOCK_your_file_close_CALL_INSTANCE
{
  UNITY_LINE_TYPE LineNumber;
  FILE* Expected_fp;
} CMOCK_your_file_close_CALL_INSTANCE;
static struct Mockyour_fileInstance
{
  CMOCK_MEM_INDEX_TYPE your_file_open_CallInstance;
  CMOCK_MEM_INDEX_TYPE your_file_close_CallInstance;
} Mock;
extern jmp_buf AbortFrame;
void Mockyour_file_Verify(void)
{
  UNITY_LINE_TYPE cmock_line = TEST_LINE_NUM;
  UNITY_TEST_ASSERT(CMOCK_GUTS_NONE == Mock.your_file_open_CallInstance, cmock_line, "Function 'your_file_open' called less times than expected.");
  UNITY_TEST_ASSERT(CMOCK_GUTS_NONE == Mock.your_file_close_CallInstance, cmock_line, "Function 'your_file_close' called less times than expected.");
}
void Mockyour_file_Init(void)
{
  Mockyour_file_Destroy();
}
void Mockyour_file_Destroy(void)
{
  CMock_Guts_MemFreeAll();
  memset(&Mock, 0, sizeof(Mock));
}
FILE* your_file_open(char* fname)
{
  UNITY_LINE_TYPE cmock_line = TEST_LINE_NUM;
  CMOCK_your_file_open_CALL_INSTANCE* cmock_call_instance = (CMOCK_your_file_open_CALL_INSTANCE*)CMock_Guts_GetAddressFor(Mock.your_file_open_CallInstance);
return 0x9048120;
  Mock.your_file_open_CallInstance = CMock_Guts_MemNext(Mock.your_file_open_CallInstance);
  UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, "Function 'your_file_open' called more times than expected.");
  cmock_line = cmock_call_instance->LineNumber;
  UNITY_TEST_ASSERT_EQUAL_STRING(cmock_call_instance->Expected_fname, fname, cmock_line, "Function 'your_file_open' called with unexpected value for argument 'fname'.");
  return cmock_call_instance->ReturnVal;
}
void CMockExpectParameters_your_file_open(CMOCK_your_file_open_CALL_INSTANCE* cmock_call_instance, char* fname)
{
  cmock_call_instance->Expected_fname = fname;
}
void your_file_open_CMockExpectAndReturn(UNITY_LINE_TYPE cmock_line, char* fname, FILE* cmock_to_return)
{
  CMOCK_MEM_INDEX_TYPE cmock_guts_index = CMock_Guts_MemNew(sizeof(CMOCK_your_file_open_CALL_INSTANCE));
  CMOCK_your_file_open_CALL_INSTANCE* cmock_call_instance = (CMOCK_your_file_open_CALL_INSTANCE*)CMock_Guts_GetAddressFor(cmock_guts_index);
  UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, "CMock has run out of memory. Please allocate more.");
  Mock.your_file_open_CallInstance = CMock_Guts_MemChain(Mock.your_file_open_CallInstance, cmock_guts_index);
  cmock_call_instance->LineNumber = cmock_line;
  CMockExpectParameters_your_file_open(cmock_call_instance, fname);
  cmock_call_instance->ReturnVal = cmock_to_return;
}
void your_file_close(FILE* fp)
{
  UNITY_LINE_TYPE cmock_line = TEST_LINE_NUM;
  CMOCK_your_file_close_CALL_INSTANCE* cmock_call_instance = (CMOCK_your_file_close_CALL_INSTANCE*)CMock_Guts_GetAddressFor(Mock.your_file_close_CallInstance);
  Mock.your_file_close_CallInstance = CMock_Guts_MemNext(Mock.your_file_close_CallInstance);
  UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, "Function 'your_file_close' called more times than expected.");
  cmock_line = cmock_call_instance->LineNumber;
  UNITY_TEST_ASSERT_EQUAL_MEMORY((void*)(cmock_call_instance->Expected_fp), (void*)(fp), sizeof(FILE), cmock_line, "Function 'your_file_close' called with unexpected value for argument 'fp'.");
}
void CMockExpectParameters_your_file_close(CMOCK_your_file_close_CALL_INSTANCE* cmock_call_instance, FILE* fp)
{
  cmock_call_instance->Expected_fp = fp;
}
void your_file_close_CMockExpect(UNITY_LINE_TYPE cmock_line, FILE* fp)
{
  CMOCK_MEM_INDEX_TYPE cmock_guts_index = CMock_Guts_MemNew(sizeof(CMOCK_your_file_close_CALL_INSTANCE));
  CMOCK_your_file_close_CALL_INSTANCE* cmock_call_instance = (CMOCK_your_file_close_CALL_INSTANCE*)CMock_Guts_GetAddressFor(cmock_guts_index);
  UNITY_TEST_ASSERT_NOT_NULL(cmock_call_instance, cmock_line, "CMock has run out of memory. Please allocate more.");
  Mock.your_file_close_CallInstance = CMock_Guts_MemChain(Mock.your_file_close_CallInstance, cmock_guts_index);
  cmock_call_instance->LineNumber = cmock_line;
  CMockExpectParameters_your_file_close(cmock_call_instance, fp);
}

 

Appendix 5: my_business_runner.c

/* AUTOGENERATED FILE. DO NOT EDIT. */
//=======Test Runner Used To Run Each Test Below=====
#define RUN_TEST(TestFunc, TestLineNum) /
{ /
  Unity.CurrentTestName = #TestFunc; /
  Unity.CurrentTestLineNumber = TestLineNum; /
  Unity.NumberOfTests++; /
  if (TEST_PROTECT()) /
  { /
      setUp(); /
      TestFunc(); /
  } /
  if (TEST_PROTECT() && !TEST_IS_IGNORED) /
  { /
    tearDown(); /
  } /
  UnityConcludeTest(); /
}
//=======Automagically Detected Files To Include=====
#include "unity.h"
#include <setjmp.h>
#include <stdio.h>
//=======External Functions This Runner Calls=====
extern void setUp(void);
extern void tearDown(void);
extern void test_read_file();
extern void test_read_file2();
//=======Test Reset Option=====
void resetTest()
{
  tearDown();
  setUp();
}
//=======MAIN=====
int main(void)
{
  Unity.TestFile = "my_business_unittest.c";
  UnityBegin();
  RUN_TEST(test_read_file, 16);
  RUN_TEST(test_read_file2, 21);
  return (UnityEnd());
}


Technorati 标签:


原文链接:http://blog.csdn.net/livelylittlefish/article/details/6361467
加载中
返回顶部
顶部