前言
在嵌入式开发中,测试是很重要的一个环节,但是开发人员往往会忽视它。所以把自动化测试与代码紧密结合在一起是这篇文章的主题。
我们开发人员平时维护代码的时候使用最多的是版本库工具,很多时候代码修改完了,本地一编译,通过就提交了。但是却忽视了一个问题:
程序编译通过就一定能正常运行了?显然是不对的。
前期准备
这里需要准备以下工具:
1,linux -- 这里我使用了ubuntu
2,gcc -- ubuntu是默认自带的
3,hg -- 版本库管理工具
4,unity -- 测试夹具
5,scons -- 一个自动化编译工具(你也可以使用makefile)
创建测试工程
首先我们需要在服务器端搭建一个版本库,
>>mkdir hg_hook
>>cd hg_hook
>>hg init
>>touch main.c
>>touch Sconstruct
然后我们修改main.c,代码如下:
#include <stdio.h>
int main(void)
{
printf("hello world!\r\n");
return 0;
}
修改Sconstruct文件,代码如下:
import os
import sys
BUILD = 'debug'
# toolchains
CC = 'gcc'
if BUILD == 'debug':
CFLAGS = ' -Wall -g -O0'
else:
CFLAGS = ' -O2'
CFLAGS += ' -I'+os.getcwd() + '/'
TARGET = 'main.out'
env = Environment(CC=CC,
CCFLAGS=CFLAGS)
env.Program(TARGET, 'main.c')
env.Command('-r', TARGET, './'+TARGET)
Scons是一个类似于makefile的自动编译工具,大家如果有什么以后可以网上google一下,很多Scons的使用介绍。
这里我主要介绍最后一行:
env.Command('-r', TARGET, './'+TARGET)
这是自定义一个命令,在我们编译好以后自动执行我们生成的可执行文件。便于我们测试程序运行结果。
我们在终端输入:scons -r,可以看到输出结果。
提交版本库
上面的工作完成以后,我们可以把源文件进行一次提交。
>>hg add .
>>hg commit -m "packed init"
这样我们就有了一个版本库。
修改版本库配置
因为我们需要用到的是hg的hook功能,所以我们需要修改hg的配置文件。
>>thg
是hg的自带图形界面,进入File->Setting;
点击编辑文件,输入:
[hooks]
changegroup = update
update = scons -r
第一行表示当有用户提交代码的时候,自动调用update。
第二行表示执行hg update的时候执行scons -r。
点击ok。然后输入:
点击ok。然后输入:
>>hg serve
启动版本库服务器。
用户提交代码
首先我们从服务器端clone代码,然后本地修改main.c代码:
#include <stdio.h>
int main(void)
{
printf("hello world!\r\n");
x=6; // 添加的错误代码
return 0;
}
本地commit一下,然后pull到服务器,在log窗口可以看到如下信息:
我们可以看到remote返回了很多信息,其中把编译结果也返回了回来。就可以看出我们提交的代码首先是编译不正确的。
到了这里,我们基本上可以通过版本库的hook自动编译代码,来检测程序的编译阶段的错误。
添加unity测试夹具
添加unity源代码到hg-hook目录下,并在unity目录下添加Sconscript文件,内容如下:
objs = Object(Glob('*.c'))
Return('objs')
修改hg-hook目录下的Sconstruct文件,添加几行代码:
import os
import sys
BUILD = 'debug'
# toolchains
CC = 'gcc'
if BUILD == 'debug':
CFLAGS = ' -Wall -g -O0'
else:
CFLAGS = ' -O2'
CFLAGS += ' -I'+os.getcwd() + <span style="font-family: Arial, Helvetica, sans-serif;">' -I'+os.getcwd() + ‘/unity’</span>
MY_ROOT = os.path.normpath(os.getcwd()) # 添加的代码:获取当前根目录
TARGET = 'main.out'
env = Environment(CC=CC,
CCFLAGS=CFLAGS)
objs = env.Object(Glob("*.c")) # 获取当前的源文件对象
objs += SConscript(['unity/Sconscript']) # 获取unity文件夹内的所有源文件对象
env.Program(TARGET, objs)
env.Command('-r', TARGET, './'+TARGET)
移动源程序
我们把hello world代码从main里面移除出去,单独创建hello-world.c,hello-world.h2个源文件;也就是我们需要测试的程序
hello-world.c
#include <hello-world.h>
#include <stdio.h>
void hello_world(void)
{
printf("hello world\r\n");
}
hello-world.h
#ifndef __HELLO_WORLD_H__
#define __HELLO_WORLD_H__
void hello_world(void);
#endif
然后我们修改main.c为:
#include <unity_fixture.h>
static void run_all_test(void)
{
RUN_TEST_GROUP(hello_world);
}
static char *arg_string[] =
{
"main",
"-v",
};
int main(int argc, char *argv[])
{
argc = sizeof(arg_string)/sizeof(char *);
argv = arg_string;
return UnityMain(argc, argv, run_all_test);
}
这个是我们测试夹具的框架。下面添加测试用例。
添加测试用例
我们添加hello-world-test.c文件,用于我们测试hello-world的函数。
#include "unity_fixture.h"
#include "hello-world.h"
TEST_GROUP(hello_world);
TEST_SETUP(hello_world)
{
printf("\r\nstart\r\n");
}
TEST_TEAR_DOWN(hello_world)
{
printf("end\r\n");
}
TEST(hello_world, test1)
{
hello_world();
}
添加hello-world-runner.c文件,用于测试夹具的启动:
#include "unity_fixture.h"
TEST_GROUP_RUNNER(hello_world)
{
RUN_TEST_CASE(hello_world, test1);
}
接着我们运行hg commit,hg push,我们可以看到:
unity测试夹具对hello-world的测试通过了。