• 移植Embedded Unit到PowperPC平台


    一、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 = gcc
    CFLAGS = -O
    AR = ar
    ARFLAGS = ru
    RANLIB = ranlib
    RM = rm
    OUTPUT = ../lib/
    TARGET = libembUnit.a
    OBJS = AssertImpl.o RepeatedTest.o stdImpl.o TestCaller.o TestCase.o TestResult.o TestRunner.o TestSuite.o
    
    all: $(TARGET)
    
    $(TARGET): $(OBJS)
        $(AR) $(ARFLAGS) $(OUTPUT)$@ $(OBJS)
        $(RANLIB) $(OUTPUT)$@
    
    .c.o:
        $(CC) $(CFLAGS) $(INCLUDES) -c $<
    
    AssertImpl.o: AssertImpl.h stdImpl.h
    RepeatedTest.o: RepeatedTest.h Test.h
    stdImpl.o: stdImpl.h
    TestCaller.o: TestCaller.h TestResult.h TestListener.h TestCase.h Test.h
    TestCase.o: TestCase.h TestResult.h TestListener.h Test.h
    TestResult.o: TestResult.h TestListener.h Test.h
    TestRunner.o: TestRunner.h TestResult.h TestListener.h Test.h stdImpl.h config.h
    TestSuite.o: TestSuite.h TestResult.h TestListener.h Test.h
    
    clean:
        -$(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.o
    embu_DIR = ../object/common/
    TARGET = libembUnit.a
    
    all: $(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.h
    RepeatedTest.o: RepeatedTest.h Test.h
    stdImpl.o: stdImpl.h
    TestCaller.o: TestCaller.h TestResult.h TestListener.h TestCase.h Test.h
    TestCase.o: TestCase.h TestResult.h TestListener.h Test.h
    TestResult.o: TestResult.h TestListener.h Test.h
    TestRunner.o: TestRunner.h TestResult.h TestListener.h Test.h stdImpl.h config.h
    TestSuite.o: TestSuite.h TestResult.h TestListener.h Test.h
    
    clean:
        -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 = gcc
    CFLAGS = -O
    INCLUDES = ..
    LIBS = ../lib
    AR = ar
    ARFLAGS = ru
    RANLIB = ranlib
    RM = rm
    OUTPUT = ../lib/
    TARGET = libtextui.a
    OBJS = TextUIRunner.o XMLOutputter.o TextOutputter.o CompilerOutputter.o
    
    all: $(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.h
    XMLOutputter.o: XMLOutputter.h Outputter.h
    TextOutputter.o: TextOutputter.h Outputter.h
    CompilerOutputter.o: CompilerOutputter.h Outputter.h
    
    clean:
        -$(RM) $(TARGET) $(OBJS)
    
    .PHONY: clean all

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

    View Makefile
    include ../build/common.mk
    
    .c.o:
        $(CC) $(CFLAGS) -c -o $*.o $<
    
    textui_OBJS = TextUIRunner.o XMLOutputter.o TextOutputter.o CompilerOutputter.o
    textui_DIR = ../object/common/
    TARGET = libtextui.a
    
    all: $(TARGET)
    
    $(TARGET): $(textui_OBJS)
        $(AR) $(ARFLAGS) $(TARGET) $(textui_OBJS)
        cp -f $(TARGET) $(textui_DIR)
    
    TextUIRunner.o: TextUIRunner.h XMLOutputter.h TextOutputter.h CompilerOutputter.h Outputter.h
    XMLOutputter.o: XMLOutputter.h Outputter.h
    TextOutputter.o: TextOutputter.h Outputter.h
    CompilerOutputter.o: CompilerOutputter.h Outputter.h
    
    clean:
        -rm -f *.o *.a 
    
    
    .PHONY: clean all

    与前述一样,只是修改的编译选项,并且将生成的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. 尽量将待移植的代码独立成一个单独的模块,以静态库的方式链接到原有程序中,以便在不需要这些东东的时候,只要不链接其静态库即可;

    【备注】:文字描述不清楚的地方还望见谅,咨询交流请发邮件至zhaixing@qq.com,^_^

    参考资料:

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

  • 相关阅读:
    Git本地库在哪
    Git&GitHub-添加提交以及查看状态
    linux命令——find
    正则表达式
    再访JavaScript对象(原型链和闭包)
    RabbitQM(消息队列)
    Java泛型(T)与通配符?
    Linux设置文件权限和归属
    英语单词
    RabbitQM使用笔记
  • 原文地址:https://www.cnblogs.com/cnpirate/p/2704548.html
Copyright © 2020-2023  润新知