Unity之三:配置向导

本文译自Unity/docs/UnityConfigurationGuide.md,以及自己使用的总结。

Unity的官网:ThrowTheSwitch.org

获取最新版本的Unity,可以访问:ThrowTheSwitch/Unity

一、C标准/编译器/微控制器

嵌入式软件世界拥有自己的挑战。编译器支持不同修订版本的C标准。有时为了让语言在某些特殊方面更有用,他们忽略了一些要求。有时为了简化它们的支持,有时是由于他们目标微控制器的特殊性。模拟器的加入,使得事情更加复杂。

Unity被设计为可运行在几乎任何由C编译器支持的东西上。要是能够在零配置下完成这个目标,那就太棒了。尽管在有些目标上已经实现了这一梦想,但这并不普遍,你可能需要修改本文中提及的至少一两个配置项来实现这一梦想。

所有的Unity配置选项都是‘#defines’定义的,并且大部分都是简单的宏定义,只有少数是带有参数的宏。它们被组织在头文件unity_internals.h中。除非你真的需要,否则不必打开这个文件。这个文件证明了构建跨平台库是具有挑战性的;同时,它也证明了将大量复杂性的代码集中在一个地方,可以保持其他地方代码的一致性和简单性。

使用这些选项

不管你使用的是本地编译器、目标平台的编译器还是模拟器,你都有如下两个选择来配置这些选项:

  1. 因为这些选项都是通过C宏定义实现的,所以你可以通过命令行参数的方式,把大部分选项传递给你的编译器。即使你在使用一个只能使用集成开发环境(IDE)进行所有配置的嵌入式目标,那也总会有一个地方可以供你为编译器添加配置选项;
  2. 你可以自行创建一个unity_config.h配置文件(并把它放在你编译工具的搜索路径中),并在此文件中列出特定于你目标的宏和定义。你唯一要做的就是定义‘UNITY_INCLUDE_CONFIG_H’宏,Unity将从unity_config.h中寻找它可能用到的定义。

二、选项

2.1 整数类型

如果你是长期从事C的开发者,你可能已经知道C标准的整数会随目标平台而变化。C标准规定‘int’长度匹配目标微处理器的寄存器大小,还规定了‘int’与其他整形(short、long)大小的关系。一个目标平台上的‘int’可能是16位的,到了另一个平台上可能就成64位了。在兼容C99或者更高版本的编译器中有更多特殊类型,但当然不是每个编译器都这样。因此,Unity有许多可配置的选项的特性,来匹配你需要的整形大小,而且它默认自动完成这个匹配工作。

2.1.1 UNITY_EXCLUDE_STDINT_H

Unity猜测你类型做的第一件事情,就是去检查‘stdint.h’。这个文件包含了类似‘UINT_MAX’的定义,Unity可以通过它来了解你的系统。可能你并不想Unity做这件事,或者你的系统并不支持‘stdint.h’。如果是这样的话,你需要定义这个宏,这样的话,Unity就知道不要跳过这个文件,而你也就会不在编译时遇到错误了。示例:

#define UNITY_EXCLUDE_STDINT_H

2.1.2 UNITY_EXCLUDE_LIMITS_H

Unity猜测你类型做的第二件事情,就是去检查‘limits.h’。一些不支持‘stdint.h’的编译器,会包含‘limits.h’文件。如果你同样不想让Unity检查这个文件,可以定义这个宏告诉Unity跳过这个文件。示例:

#define UNITY_EXCLUDE_LIMITS_H

如果你把上面这两个头文件检查的功能都禁用了,那你就得自己进行配置了。不用担心,即使这样也没想得那么复杂:如果你不喜欢这些默认配置,只需要定义几个宏就好了。

2.1.3 UNITY_INT_WIDTH

通过定义这个宏来告诉Unity你系统上的‘int’占用多少个比特位。如果没有自动检测到,默认情况为32位。示例:

#define UNITY_INT_WIDTH 16

2.1.4 UNITY_LONG_WIDTH

通过定义这个宏来告诉Unity你系统上的‘long’占用多少个比特位。如果没有自动检测到,默认情况为32位。这个宏用于表明你的系统支持哪种的64位,从而知道要获取一个64位的值是使用‘long’还是‘long long’。在16位的系统上,这个选项将被忽略。示例:

#define UNITY_LONG_WIDTH 16

2.1.5 UNITY_POINTER_WIDTH

通过定义这个宏来告诉Unity你系统上的指针类型占用多少个比特位。如果没有自动检测到,默认情况为32位。如果你的编译器警告说对指针进行了强制类型转换,那你需要查看一下这个宏。示例:

#define UNITY_POINTER_WIDTH 64

2.1.6 UNITY_SUPPORT_64

如果自动检测到支持64位,或者你定义的‘int’、‘long’或指针位宽大于32位,Unity将自动包含64位支持。如果前面说的这些并没有明确指出是否支持64位,而你的系统却支持,那么你可以定义这个宏来启用Unity的64位支持;但如果在小目标(不支持64位的系统)上启用64位支持,会对Unity的大小和速度有重大影响。因此,如果不需要,请不要定义它。示例:

#define UNITY_SUPPORT_64

2.2 浮点数类型

在嵌入式的世界里,遇到不支持浮点数或者只支持单精度浮点数运算的目标一点也不稀奇。我们能够动态猜测整数大小,是因为整形总是至少有一个字节大小可用。另一方面,有些平台根本就没有浮点数,在这些平台上尝试包含‘float.h’将导致错误。这使得手动配置成为唯一的选项。

UNITY_INCLUDE_FLOAT
UNITY_EXCLUDE_FLOAT
UNITY_INCLUDE_DOUBLE
UNITY_EXCLUDE_DOUBLE

默认情况下,Unity猜测你想要单精度浮点数支持,而不是双精度。使用这里的INCLUDE和EXCLUDE选项可以很容易地更改这两个选项:根据你的需要,你可以包含一个、两个,也可以两个都不包含。示例:

//what manner of strange processor is this?
#define UNITY_EXCLUDE_FLOAT
#define UNITY_INCLUDE_DOUBLE

若启用了浮点数支持,还可以使用以下的一些浮点选项。

2.2.1 UNITY_EXCLUDE_FLOAT_PRINT

Unity的目标是尽可能小的开销,并且尽可能避免调用标准库(一些嵌入式平台并没有标准库!)。正因为如此,它用于打印整数值的例程是极简的,并且是手工编码的。因此,在断言失败时显示浮点值是可选的。默认情况下,Unity将打印浮点断言失败的实际结果(例如“Expected 4.56 Was 4.68”)。若不想包含这个额外支持,你可以使用这个宏来把浮点断言失败消息替换为像“Values Not Within Delta”的输出。示例:

#define UNITY_EXCLUDE_FLOAT_PRINT

如果你想要更加详细的浮点断言失败信息,可使用下面这些选项来提供更加显示的失败信息。

2.2.2 UNITY_FLOAT_TYPE

如果定义了这个选项,Unity假定你想使用你自己的float类型断言而不是C标准的float。如果你的编译器支持特殊的浮点数类型,你可以通过使用这个宏定义重写。示例:

#define UNITY_FLOAT_TYPE float16_t

2.2.3 UNITY_DOUBLE_TYPE

如果定义了这个选项,Unity假定你想使用你自己的double类型断言而不是C标准的double。如果你想改变doubl,你可以通过这个宏指定为其他类型。比如,你可以将‘UNITY_DOUBLE_TYPE’定义为‘long double’,可以在64位处理器上启用庞大的浮点类型,而不是标准的‘double’。示例:

#define UNITY_DOUBLE_TYPE long double

2.2.4 UNITY_FLOAT_PRECISION

2.2.5 UNITY_DOUBLE_PRECISION

如果你在《Unity断言参考文档》(UnityAssertionsReference.md)查阅了‘UNITY_ASSERT_EQUAL_FLOAT’和‘UNITY_ASSERT_EQUAL_DOUBLE’,你会发现它们实际上不是真正的断言两个值相等,而是“足够接近(close enough)”到相等,“足够接近”由这两个精确配置选项控制。如果您使用的是32位float和/或64位double(大部分处理器上都是这样),那么你不需要修改这两个选项:它们都被设置为一个有效bit位的误差,float的精度是0.00001,dobule的是10~12。想了解它们如何工作的细节,请查看Unity断言参考文档:UnityAssertionsReference.md。示例:

#define UNITY_FLOAT_PRECISION 0.001f

2.3 其他杂项

2.3.1 UNITY_EXCLUDE_STDDEF_H

Unity使用默认定义在‘stddef.h’中的NULL宏,它定义了一个null指针的常量值。如果你想为此提供自己的宏,可通过添加这个宏定义来排除包含‘stddef.h’文件。示例:

#define UNITY_EXCLUDE_STDDEF_H

2.3.2 UNITY_INCLUDE_PRINT_FORMATTED

Unity提供了一个简单(而且非常基本)的类似printf的字符串输出实现,它能够打印一个经过如下格式化修饰符修饰的字符串:

- __%d__ - signed value (decimal)
- __%i__ - same as __%i__
- __%u__ - unsigned value (decimal)
- __%f__ - float/Double (if float support is activated)
- __%g__ - same as __%f__
- __%b__ - binary prefixed with "0b"
- __%x__ - hexadecimal (upper case) prefixed with "0x"
- __%X__ - same as __%x__
- __%p__ - pointer (same as __%x__ or __%X__)
- __%c__ - a single character
- __%s__ - a string (e.g. "string")
- __%%__ - The "%" symbol (escaped)

示例:

#define UNITY_INCLUDE_PRINT_FORMATTED

int a = 0xfab1;
UnityPrintFormatted("Decimal   %d\n", -7);
UnityPrintFormatted("Unsigned  %u\n", 987);
UnityPrintFormatted("Float     %f\n", 3.1415926535897932384);
UnityPrintFormatted("Binary    %b\n", 0xA);
UnityPrintFormatted("Hex       %X\n", 0xFAB);
UnityPrintFormatted("Pointer   %p\n", &a);
UnityPrintFormatted("Character %c\n", 'F');
UnityPrintFormatted("String    %s\n", "My string");
UnityPrintFormatted("Percent   %%\n");
UnityPrintFormatted("Color Red \033[41mFAIL\033[00m\n");
UnityPrintFormatted("\n");
UnityPrintFormatted("Multiple (%d) (%i) (%u) (%x)\n", -100, 0, 200, 0x12345);

2.4 定制工具集

除了前面列出的选项,还有许多其他选项可以方便地为特定的工具链定制Unity的行为。你可能不需要使用这些东西。。。但在某些特定的平台上,特别是在模拟器中运行的平台,可能需要些额外的东西才能保证正常运行,下面这些宏有助于实现这种情况。

2.4.1 定制结果输出方式

UNITY_OUTPUT_CHAR(a)
UNITY_OUTPUT_FLUSH()
UNITY_OUTPUT_START()
UNITY_OUTPUT_COMPLETE()

默认情况下,Unity将运行结果输出到‘stdout’。在大多数使用本地编译器进行测试的情况下,这都可以很好地工作。在已经将‘stdout’路由回命令行的模拟器上,也能正常的工作。但是,有时候模拟器会缺少对转储结果的支持,或者您可能会因为其他原因希望将结果路由到其他地方。在这种情况下,你应该定义‘UNITY_OUTPUT_CHAR’宏,它一次接受一个字符(字符类型为‘int’,因为这是C标准‘putchar’函数中最常用的参数类型)。你可以用任意你喜欢的函数调用来替换他。

示例:假设你被迫在没有‘stdout’输出的嵌入式处理器上运行测试,你决定将您的测试结果输出路由到自定义串行‘RS232_putc()’函数,那么你可以这样定义:

#include "RS232_header.h"
...
#define UNITY_OUTPUT_CHAR(a)    RS232_putc(a)
#define UNITY_OUTPUT_START()    RS232_config(115200,1,8,0)
#define UNITY_OUTPUT_FLUSH()    RS232_flush()
#define UNITY_OUTPUT_COMPLETE() RS232_close()

注意:UNITY_OUTPUT_FLUSH()宏可以通过指定为‘UNITY_USE_FLUSH_STDOUT’来方便的指定为标准输出的flush()函数。除了上面这些选项,没有其他需要定义的了。

2.4.2 定制函数弱属性

UNITY_WEAK_ATTRIBUTE
UNITY_WEAK_PRAGMA
UNITY_NO_WEAK

对于一些目标平台,Unity可以使setUp()和tearDown()函数变成可选的。因为setUp()和tearDown()函数常常什么都不做,这对于测试编写者十分方便。如果你使用的是gcc或clang,Unity会自动帮你定义这些选项。如果其他编译器支持称为弱(weak)函数的C特性,那么它们也可以支持这种行为。weak函数是指:除非在其它地方定义了non-weak的、相同的函数,否则就使用弱函数编译进可执行文件中,如果发现了一个non-weak版本,weak版本的函数将被忽略,就好像weak版本的函数不存在一样。如果你的编译器支持这个特性,你可以通过定义UNITY_WEAK_ATTRIBUTE或UNITY_WEAK_PRAGMA来指出一个weak函数的属性,从而让Unity知道。如果你的编译器不支持weak函数,那你就得每次都定义setUp()和tearDown()函数(尽管他们经常为空)。当然,你也可以通过定义‘UNITY_NO_WEAK’来强制Unity不使用weak函数。这个特性常用的配置如下所示:

#define UNITY_WEAK_ATTRIBUTE weak
#define UNITY_WEAK_ATTRIBUTE __attribute__((weak))
#define UNITY_WEAK_PRAGMA
#define UNITY_NO_WEAK

2.4.3 UNITY_PTR_ATTRIBUTE

有些编译器需要为指针分配自定义的属性,比如‘near’或‘far’。在这种情况下,可以使用你想要的属性来定义这个选项,从而为Unity提供一个安全的默认值。示例:

#define UNITY_PTR_ATTRIBUTE __attribute__((far))
#define UNITY_PTR_ATTRIBUTE near

2.4.4 UNITY_PRINT_EOL

默认情况下,Unity在每一行的末尾输出‘\n’。这样很容易通过脚本、Ceeding等进行解析,但这并不一定适用于你的系统。通过这个宏,您可以随意覆盖它,并使它成为您想要的任何东西。示例:

#define UNITY_PRINT_EOL { UNITY_OUTPUT_CHAR('\r'); UNITY_OUTPUT_CHAR('\n') }

2.4.5 UNITY_EXCLUDE_DETAILS

如果你必须为你的系统节约每一个字节的内存,你可以使用这个选项。Unity存储了一组用于传递额外详细信息的内部便签簿,用于像CMock这样的系统以报告是哪个函数或参数导致的错误。如果你不使用CMock,也不把这些细节用于其他情况,你可以忽略它们。示例:

#define UNITY_EXCLUDE_DETAILS

2.4.6 UNITY_EXCLUDE_SETJMP

如果你的嵌入式系统不支持标准库的setjmp,你可以通过定义这个宏来排除Unity对setjmp的依赖。不过,排除这个依赖是有代价的:你将无法为你的测试使用定制的helper函数/脚本,也无法使用CMock之类的工具。但话说回来,如果你的编译器连setjmp都不支持,那你很可能连存储那些信息的内存都没有。所以这个选项是为这样的情况而存在的。示例:

#define UNITY_EXCLUDE_SETJMP

2.4.7 UNITY_OUTPUT_COLOR

如果你想要使用ANSI转义码为输出添加颜色,你可以使用这个宏。示例:

#define UNITY_OUTPUT_COLOR

三、深入虎穴

在某些情况下,上面的这些选项仍无法满足你的需求。对于使用本地工具链编译和执行测试(比如Mac上的clang),它们完全足够了;它们应该也能够应对绝大多数你从本地命令行运行目标模拟器进行测试的问题。但如果你必须在你的目标硬件上运行你的测试套件,那么你的Unity配置将需要特别的帮助。这个特别的帮助通常位于两个地方之一:‘main()’函数或‘RUN_TEST()’宏。下面我们来看看他们吧。

3.1 main()

每个测试模块都是独立编译和运行的,它们独立于项目中的其他测试模块。因此,每个测试文件都有一个‘main’函数。这个‘main’函数将包含初始化你的系统到可工作状态所需要的任何代码,对于必须为测试结果的输出设置内存映射或初始化通信通道的情况尤其如此。

一个简单的main函数看起来像这样:

int main(void) {
    UNITY_BEGIN();
    RUN_TEST(test_TheFirst);
    RUN_TEST(test_TheSecond);
    RUN_TEST(test_TheThird);
    return UNITY_END();
}

你可以看到,我们的main函数不需要任何参数。对于最基本的情况,我们永远不需要参数,因为我们每次都运行所有的测试。我们从调用‘UNITY_BEGIN’开始,然后我们运行每个测试(按照我们希望的任何顺序),最后我们调用‘UNITY_END’并将其结果(失败的总数)作为main()的返回值。

很容易看出来,你可以在所有测试用例运行之前或者所有测试用例完成之后添加代码。这使得你可以在特殊情况下进行任何必要的系统setup或teardown操作。

3.2 RUN_TEST()

每个测试用例函数都会使用调用RUN_TEST宏来调用。它的工作是展开执行单个测试用例函数所需的各种setup和teardown,这包括捕获失败,调用测试模块的‘setUp()’和‘tearDown()’函数,以及调用‘UnityConcludeTest()’。如果使用了CMock或者测试覆盖率,还会使用额外的桩。一个非常简单的RUN_TEST宏看起来像这样:

#define RUN_TEST(testfunc) \
    UNITY_NEW_TEST(#testfunc) \
    if (TEST_PROTECT()) { \
        setUp(); \
        testfunc(); \
    } \
    if (TEST_PROTECT() && (!TEST_IS_IGNORED)) \
        tearDown(); \
    UnityConcludeTest();

所以这真TM是个宏啊!它让您了解:对每一个测试用例,Unity必须处理的事情。对于每个测试用例,我们声明它是一个新的测试,然后运行‘setUp’和测试函数。它们运行在一个‘TEST_PROTECT’语句块中,‘TEST_PROTECT’语句块用于处理测试期间发生的故障。然后,假设我们的测试仍然在运行且没有被忽略,我们运行‘tearDown()’。无论如何,我们的最后一步都是:在继续下一个测试之前完成这个测试。

假设你需要添加一个‘fsync’调用,以便在每次测试后强制将所有输出的数据flush到一个文件,你可以很容易地在你的‘UnityConcludeTest()’调用之后插入这个;也许你想在每个测试结果集前后写一个xml标签,同样地,你可以通过为这个宏添加相应的行来实现。当您需要在整个测试套件的每个测试用例之前或之后执行某个操作时,可以更新这个宏。

四、愉快的移植

本指南的宏和定义应该可以帮助你将Unity移植到我们想得到的任何C目标上。如果你遇到一两个问题,不要害怕在论坛上寻求帮助,我们喜欢挑战!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值