Embedded Unit

来源:互联网 发布:淘宝支持信用卡代付吗 编辑:程序博客网 时间:2024/04/29 19:42

一、Embedded Unit 简介

  Embedded Unit(简称embUnit)是一个针对嵌入式C系统的单元测试框架。它不依赖于标准的C函数库,所有的对象都被静态编译链接。因此,可以比较方便地将其移植到嵌入式平台。

  下载地址:http://sourceforge.net/projects/embunit/files/

【备注】:Embedded Unit测试原理是通过将预期值与实际值进行比较来测试函数的逻辑,只能实现函数级别的单元测试而已,呵呵。

二、目标平台简介

  硬件平台:PowerPC

  操作系统:基于uclinux内核,但是所有系统调用都自己实现的一个精简操作系统;

三、移植思路

  1. 由于embUnit不依赖于标准的C函数库,因此,将我们的编译选项添加到embUnit中的Makefile中,将其代码编译成一个静态库,然后链接到我们原有的程序中;
  2. 额外创建一个源文件,用于编写测试代码,该文件也通过编译、链接,将其与原有的程序链接在一起;
  3. 在原有程序的main中(即程序入口处),调用embUnit这个框架提供的API函数,执行对函数的单元测试;

四、实战步骤

1. 下载的embUnit的源代码解压后,在解压形成的目录里面有一个embunit目录,这个目录就是embUnit的源代码所在的目录,首先我们分析这个目录下的makefile文件,文件内容如下:

View makefile
复制代码
CC = gccCFLAGS = -OAR = arARFLAGS = ruRANLIB = ranlibRM = rmOUTPUT = ../lib/TARGET = libembUnit.aOBJS = AssertImpl.o RepeatedTest.o stdImpl.o TestCaller.o TestCase.o TestResult.o TestRunner.o TestSuite.oall: $(TARGET)$(TARGET): $(OBJS)    $(AR) $(ARFLAGS) $(OUTPUT)$@ $(OBJS)    $(RANLIB) $(OUTPUT)$@.c.o:    $(CC) $(CFLAGS) $(INCLUDES) -c $<AssertImpl.o: AssertImpl.h stdImpl.hRepeatedTest.o: RepeatedTest.h Test.hstdImpl.o: stdImpl.hTestCaller.o: TestCaller.h TestResult.h TestListener.h TestCase.h Test.hTestCase.o: TestCase.h TestResult.h TestListener.h Test.hTestResult.o: TestResult.h TestListener.h Test.hTestRunner.o: TestRunner.h TestResult.h TestListener.h Test.h stdImpl.h config.hTestSuite.o: TestSuite.h TestResult.h TestListener.h Test.hclean:    -$(RM) $(OBJS) $(TARGET).PHONY: clean all
复制代码

分析可知,这个makefile会将当前目录下的源文件编译和链接成一个名为libembUnit.a的静态库文件。然后,我们只要修改相应的编译选项,去掉不用的选项,下面是修改的Makefile:

View Makefile
复制代码
include ../build/common.mk.c.o:    $(CC) $(CFLAGS) -c -o $*.o $<embu_OBJS = AssertImpl.o RepeatedTest.o stdImpl.o TestCaller.o TestCase.o TestResult.o TestRunner.o TestSuite.oembu_DIR = ../object/common/TARGET = libembUnit.aall: $(TARGET)$(TARGET): $(embu_OBJS)    $(AR) $(ARFLAGS) $(TARGET) $(embu_OBJS)    cp -f $(TARGET) $(embu_DIR).c.o:    $(CC) $(CFLAGS) $(INCLUDES) -c $<AssertImpl.o: AssertImpl.h stdImpl.hRepeatedTest.o: RepeatedTest.h Test.hstdImpl.o: stdImpl.hTestCaller.o: TestCaller.h TestResult.h TestListener.h TestCase.h Test.hTestCase.o: TestCase.h TestResult.h TestListener.h Test.hTestResult.o: TestResult.h TestListener.h Test.hTestRunner.o: TestRunner.h TestResult.h TestListener.h Test.h stdImpl.h config.hTestSuite.o: TestSuite.h TestResult.h TestListener.h Test.hclean:    -rm -f *.o *.a .PHONY: clean all
复制代码

仔细分析,修改的内容就是将编译选项修改了,然后将生成的libembUnit.a文件复制到一个公共目录下;

【备注】其中../build/common.mk文件是我们源程序中公共选项脚本文件,里面定义了编译和链接的一些选项而已,呵呵

2. 修改完Makefile后,如果编译,会提示stdio.h这个头文件找不到。原因是embUnit的源代码中的config.h这个文件include了<stdio.h>这个C库文件,而由于我们的操作系统完全自己实现,并且没有提供stdio.h这个头文件,因此,将其注释掉即可;然后再执行make,就可以编译通过了,虽然会有一些警告,不过可以忽略;

3. 书写测试代码

View Code
复制代码
/* include local files *//* include embUnit include */#include "../embUnit/embUnit.h"#include "../embUnit/AssertImpl.h"#include "../embUnit/config.h"#include "../embUnit/HelperMacro.h"#include "../embUnit/RepeatedTest.h"#include "../embUnit/stdImpl.h"#include "../embUnit/Test.h"#include "../embUnit/TestCaller.h"#include "../embUnit/TestCase.h"#include "../embUnit/TestListener.h"#include "../embUnit/TestResult.h"#include "../embUnit/TestRunner.h"#include "../embUnit/TestSuite.h"#include "../rts_include/test.h"int iFile = -1;static void setUp(void){    /* initialize */    iFile = open("testFile.txt", O_WRONLY|O_CREAT|O_APPEND|O_NAND);}static void tearDown(void){    /* terminate */    close(iFile);}static void testFile(void){    char buff[4] = "abcd";    TEST_ASSERT_EQUAL_INT(6, write(iFile, buff, 4));}/*embunit:impl=+ *//*embunit:impl=- */TestRef testFile_tests(void){    EMB_UNIT_TESTFIXTURES(fixtures) {        /*embunit:fixtures=+ */        /*embunit:fixtures=- */        new_TestFixture("testFile", testFile),    };    EMB_UNIT_TESTCALLER(test,"test",setUp,tearDown,fixtures);    return (TestRef)&test;};
复制代码

首先注意测试代码一方面要引用embUnit的头文件,另一方面也要引用原有程序相应的头文件,这样才能既使用embUnit提供的API函数,又能使用原有程序提供的接口函数;

上述代码的解释如下:

  • 上述代码的目标是对write函数进行测试,方法就是先open一个文件,然后写入固定的字节,判断写入的字节数是否正确,最后关闭文件。
  • setUp函数的作用是:提供待测试函数的前端输入。譬如,上述代码要测试write函数,必须要先用open打开一个文件,那么就可以在setUp这个函数中调用open函数去创建要write的文件;
  • tearDown函数的作用是:提供待测试函数的后端处理。譬如,上述代码,写入文件后,要关闭文件,那么就可以在tearDown这个函数调用close函数关闭文件;
  • testFile函数(函数名其实可以自己定义)的作用是:提供测试逻辑。譬如,上述代码,调用embUnit提供的断言宏,进行判断write函数是否执行成功。
  • testFile_tests函数的作用是:将上述几个函数整合到一个测试suite中,以供后续程序调用;

对测试代码编译(仍然采用目标平台的编译选项进行编译),使其生成的.a(如test.a)文件,并将.a文件复制到公共的库文件目录下;

4. 在原有函数的入口出执行测试程序

代码如下:

View Code
复制代码
#include "../embUnit/embUnit.h"#include "../embUnit/AssertImpl.h"#include "../embUnit/config.h"#include "../embUnit/HelperMacro.h"#include "../embUnit/RepeatedTest.h"#include "../embUnit/stdImpl.h"#include "../embUnit/Test.h"#include "../embUnit/TestCaller.h"#include "../embUnit/TestCase.h"#include "../embUnit/TestListener.h"#include "../embUnit/TestResult.h"#include "../embUnit/TestRunner.h"#include "../embUnit/TestSuite.h"void main(void){    printf("***********************************************************\n");    printf("**************      Unit Test Start         ***************\n");    printf("***********************************************************\n");    /* 将测试结果按照编译格式输出 */    TestRunner_start();    TestRunner_runTest(testFile_tests());    TestRunner_end();    printf("***********************************************************\n");    printf("**************       Unit Test End          ***************\n");    printf("***********************************************************\n");}
复制代码

在上述代码中,可以看到语句TestRunner_runTest(testFile_tests());调用的就是在测试代码中声明的testFile_tests函数;
需要的注意的是,在该程序中也要引用embUnit提供的头文件,否则无法编译通过。

5. 将上述代码也编译成.a文件,并且与刚才编译生成的libembUnit.atest.a文件,以及原有程序的其他.a文件链接一个可执行文件,并将其烧录到目标机器中,然后即可看到运行结果。下图是本人的运行结果:

由于写入的是4个字节,但是测试代码的预期值是6个字节,因此,测试没有通过。

到此,整个移植过程结束。不过,上述测试结果显示,不是那么友好,很幸运的是embUnit提供的源码中还有一个格式化测试结果的工具的源代码,我们就照葫芦画瓢将这个工具也移植过来;

五、移植embUnit的格式化测试结果工具

在刚才下载的压缩包解压后的根目录下有个textui的目录,这个目录提供的就是格式化测试结果的工具的源代码,移植的方法与上面类似,现简介如下:

1. 修改makefile

textui原有的makefile如下所示:

View makefile
复制代码
CC = gccCFLAGS = -OINCLUDES = ..LIBS = ../libAR = arARFLAGS = ruRANLIB = ranlibRM = rmOUTPUT = ../lib/TARGET = libtextui.aOBJS = TextUIRunner.o XMLOutputter.o TextOutputter.o CompilerOutputter.oall: $(TARGET)$(TARGET): $(OBJS)    $(AR) $(ARFLAGS) $(OUTPUT)$@ $(OBJS)    $(RANLIB) $(OUTPUT)$@.c.o:    $(CC) $(CFLAGS) -I$(INCLUDES) -c $<TextUIRunner.o: TextUIRunner.h XMLOutputter.h TextOutputter.h CompilerOutputter.h Outputter.hXMLOutputter.o: XMLOutputter.h Outputter.hTextOutputter.o: TextOutputter.h Outputter.hCompilerOutputter.o: CompilerOutputter.h Outputter.hclean:    -$(RM) $(TARGET) $(OBJS).PHONY: clean all
复制代码

很明显,原有的makefile是将所有的源文件编译生成一个libtextui.a的文件,现将这个makefile修改如下:

View Makefile

与前述一样,只是修改的编译选项,并且将生成的libtextui.a文件复制到一个公共的目录下;

2. 编译textui

  修改完Makefile文件后,此时编译会提示:stdout未定义

  原因是:textui中使用了fprintf将信息输出的stdout(即标准输出,一般指屏幕),但是我们的目标机不支持显示器等标准输出,也没有对stdout进行定义。

  解决办法是:在Outputter.h文件中,重新定义printf,即,忽略stdout,使用printf代替fprintf,具体如下所示:

#define fprintf(stdout, formats, args...)    printf(formats, ##args)

  解决上述问题后,编译时,仍然会提示错误,主要是头文件找不到的错误;

  原因是:在textui的源文件中,需要引用embUnit的头文件,但是其采用的是#include <>方式,当然找不到对应的头文件了;

  解决办法:我们将这些头文件的引用方式改为#include " "方式,并且指明对应头文件的相对路径;

3. 修改在程序入口处的调用测试程序的代码,具体如下所示:

View Code
复制代码
void main(void){    printf("***********************************************************\n");    printf("**************      Unit Test Start         ***************\n");    printf("***********************************************************\n");    /* 将测试结果按照编译格式输出 */    TestRunner_start();    TestRunner_runTest(testFile_tests());    TestRunner_end();    /* 将测试结果按文本格式输出 */    TextUIRunner_setOutputter(TextOutputter_outputter());    TextUIRunner_start();    TextUIRunner_runTest(testFile_tests());    TextUIRunner_end();    /* 将测试结果按照XML格式输出 */    TextUIRunner_setOutputter(XMLOutputter_outputter());    TextUIRunner_start();    TextUIRunner_runTest(testFile_tests());    TextUIRunner_end();    printf("***********************************************************\n");    printf("**************       Unit Test End          ***************\n");    printf("***********************************************************\n");}
复制代码

  从上述代码可知,注册了两种测试结果输出方式:TextUIRunner_setOutputter(TextOutputter_outputter()); TextUIRunner_setOutputter(XMLOutputter_outputter());

4. 全编译所有代码,并链接成可执行程序,烧到目标机器上,即可看到测试结果,下图就是测试结果:

 到此,整个移植过程结束。

六、经验总结

  1. 不同平台间的代码移植原理其实很简单,就是将待移植的代码使用目标平台的编译环境进行编译,然后将其链接到原有的程序中;
  2. 待移植的代码一般会使用#include<>方式引用头文件,因此,移植的时候需要修改待移植程序的源文件的关于头文件引用方式;
  3. 待移植的代码还可能会使用一些目标平台不支持的API函数或这系统调用,因此,移植的时候应该重新封装这些接口;
  4. 尽量将待移植的代码独立成一个单独的模块,以静态库的方式链接到原有程序中,以便在不需要这些东东的时候,只要不链接其静态库即可;


参考资料:

[1].http://embunit.sourceforge.net/


转载:http://www.cnblogs.com/cnpirate/archive/2012/09/27/2704548.html