RT_Thread的测试框架使用及分析

RT_Thread的测试框架使用及分析

为了行文的简单,RT_Thread将简称为RTT。

一个最简单的测试用例(不使用TestCase框架)

RTT的finsh组件,提供了非常强大的调试功能,我们可以将函数输出到finsh上,然后就可以在finsh中手动的调用这个函数,这一点非常的强大。可以说finsh是RTT的调试利器,善用finsh,可以极大的提高开发的效率。

关于finsh的详细的介绍,可以参考《RT_Thread实时操作系统编程指南》的 Finsh Shell系统一章,这里先只是简单的举一个例子。

在application.c中的最后添加如下代码


int testfunc(void)
{
	rt_kprintf("hello, rt-thread!\n");
}

#include 
FINSH_FUNCTION_EXPORT(testfunc, just a test function);

不要忘记在rtconfig.h中使能 RT_USING_FINSH宏.
我们编写了名为 testfunc的函数,然后希望在finsh中挑用它,那么我们需要使用FINSH_FUNCTION_EXPORT宏带两个参数,第一个参数是函数名,第二个参数是对函数的说明,可以是任意字符串,用户可以根据自己的喜好编写,不过建议对函数做客观的说明或介绍。
FINSH_FUNCTION_EXPORT 会函数testfunc输出给finsh,这样finsh就会“知道”有一个函数名为testfunc。

接下来,重新编译工程,启动串口工具,比如putty,windows系统自带的超级终端,或者SecureCRT。
复位开发板,你就可以看到如下信息:

 \ | /
- RT -     Thread Operating System
 / | \     1.1.0 build Apr 15 2012
 2006 - 2012 Copyright by rt-thread team

finsh />


此时,按下Tab键,可以看到如下语句

--function:
testfunc         -- just a test function
list_mem         -- list memory usage information
list_date        -- list date
hello            -- say hello world
version          -- show RT-Thread version information
list_thread      -- list thread
list_sem         -- list semaphone in system
list_event       -- list event in system
list_mutex       -- list mutex in system
list_mailbox     -- list mail box in system
list_msgqueue    -- list message queue in system
list_mempool     -- list memory pool in system
list_timer       -- list timer in system
list_device      -- list device in system
list             -- list all symbol in system
--variable:
dummy            -- dummy variable for finsh
finsh />

第一句就是我们刚刚编写的函数,左边是函数名,右边即函数说明。

接下来,在finsh上输入 t 然后按下Tab键,finsh会自动地为我们补全testfunc,然后我们手动输入 ()后回车,可以看到如下运行效果:

finsh />testfunc()
hello, rt-thread!
        0, 0x00000000
finsh />


注:finsh跟linux下的shell很像,都具有tab键自动补全的功能,但是又有些不同,finsh的命令是C语言风格的,命令即函数调用语句。

可以看到,我们在finsh中成功的手动调用了刚刚编写的testfunc函数。这个特性在调试程序时非常有用。可见可以通过这种方式来编写一些测试程序,并通过finsh来手动的调用这些测试用例。

实际上,在正式的项目开发中,测试是非常重要的环节,是程序质量的重要保障。我们可能为此编写大量的测试用例,此时一个一个在finsh中手动的输入函数名来运行测试用例的方式显然后有些原始,这个工作没有任何技术含量,枯燥乏味且令人恼火,那么这种情况下,改怎么办呢?

别担心,RTT为我们提供了所谓的 测试用例(TestCase, 简称为TC) 框架。TC框架可以帮助我们依次,自动的调用多个测试用例函数。

下一节,我们将添加一个testfunc2函数,跟testfunc函数一起采用TC框架,首先准备两个测试用例代码(为了代码的清晰,我么将测试用例放在独立的c文件中).

在当前BSP主目录下,创建一个名为testcase的文件夹。
将下面代码保存为 testfunc1.c ,存放到testcase/下。

#include 

int testfunc1(void)
{
	rt_kprintf("hello, rt-thread! this is testfunc1\n");
}

#include 
FINSH_FUNCTION_EXPORT(testfunc1, just a test function);

将下面代码保存为 testfunc2.c 存放到testcase/下。

#include 

int testfunc2(void)
{
	rt_kprintf("hello, rt-thread! this is testfunc2\n");
}

#include 
FINSH_FUNCTION_EXPORT(testfunc2, just a test function);

编写基于TC框架的测试用例

1. 修改rtconfig.h

使能如下两个宏

#define RT_USING_TC
#define RT_USING_FINSH

2.添加TC框架源码以及测试用例源码

因为TC测试框架需要如下这两个文件,
tc_comm.c
tc_comm.h
这两个文件位于RTT源码包examples/kernel/目录下,此目录下出这两个文件以外,还包含其他很多文件,那些文件是RTT官方提供基于TC框架的测试用例,它们用于测试RTT内核的基本功能。

这里,笔者要忍不住吐槽两句,当前RTT的文件放置仍然有些混乱,tc_comm.c和tc_comm.h不应该与测试用例放在一起。我们为其他的组件编写的测试用例放在什么地方呢?

将上面这两个文件复制到当前BSP目录下的testcase目录下。

将下面代码 testcase/SConscript文件

from building import *

src = Glob("*.c")

group = DefineGroup('testcase', src, depend = ['RT_USING_TC'])

Return('group')

修改当前BSP目录下的SConstuct文件,添加如下代码

if GetDepend('RT_USING_TC'):
	objs = objs + SConscript(GetCurrentDir() + '/testcase/SConscript', variant_dir='build/tc', duplicate=0)

3. 修改 testfunc1.c 和 testfunc2.c

将 testfunc1.c 修改为:

#include 
#include "tc_comm.h"

int testfunc1(void)
{
	rt_kprintf("hello, rt-thread! this is testfunc1\n");
}

#include 
FINSH_FUNCTION_EXPORT(testfunc1, just a test function);

static void _tc_cleanup()
{
	tc_done(TC_STAT_PASSED);
}

int _tc_testfunc1()
{
	/* 设置TestCase清理回调函数 */
	tc_cleanup(_tc_cleanup);
	
	testfunc1();

	/* 返回TestCase运行的最长时间 */
	return 100;
}
/* 输出函数命令到finsh shell中 */
FINSH_FUNCTION_EXPORT(_tc_testfunc1, a dynamic thread example);

同样,将 testfunc2.c 修改为:

#include 
#include "tc_comm.h"

int testfunc2(void)
{
	rt_kprintf("hello, rt-thread! this is testfunc2\n");
}

#include 
FINSH_FUNCTION_EXPORT(testfunc2, just a test function);

static void _tc_cleanup()
{
	tc_done(TC_STAT_PASSED);
}

int _tc_testfunc2()
{
	/* 设置TestCase清理回调函数 */
	tc_cleanup(_tc_cleanup);
	
	testfunc2();

	/* 返回TestCase运行的最长时间 */
	return 100;
}
/* 输出函数命令到finsh shell中 */
FINSH_FUNCTION_EXPORT(_tc_testfunc2, a dynamic thread example);

4. 编译运行

在命令行下执行scons 编译,然后启动finsh,按下tab键,可以看到 testfunc1和testfunc2.
除此之外,还多出来三个函数,分别是

  1. tc_start
  2. tc_stop
  3. list_tc

在tc_comm.h中有这三个函数的原型,这三个函数就是TC框架提供给我们的函数,我们只需要执行 tc_start(“test”),TC框架就会自动的依次调用 testfunc1 和 testfunc2函数。效果如下所示。

finsh />tc_start("test")
        805434376, 0x3001f408
finsh />Run TestCase: testfunc1
hello, rt-thread! this is testfunc1
TestCase[testfunc1] passed
Run TestCase: testfunc2
hello, rt-thread! this is testfunc2
TestCase[testfunc2] passed
RT-Thread TestCase Running Done!

finsh />

这里简要说明一下 tc_start(“test”)的含义,我们编写两个测试函数的函数名分别为

  1. testfunc1
  2. testfunc2

显然这两个函数具有共同的前缀字符串,这里我输入的是“test”, 当然你也可以输入 “testf”, “testfun”等等。

扩展阅读

关于Finsh Shell的更多介绍请参考《RT_Thread实时操作系统编程指南》 的“Finsh Shell系统”章。

运行RTT官方自带的TC用例

RTT的官方源码包中已经包含了一些测试用例,它们位于example/目录下。读者需要注意,只有example/kernel目录下的源码文件是基于TC的测试用例,其他子目录下的文件并不是基于TC框架框架编写的。

修改当前BSP目录下的SConstuct文件,将如下代码

if GetDepend('RT_USING_TC'):
	objs = objs + SConscript(GetCurrentDir() + '/testcase/SConscript', variant_dir='build/tc', duplicate=0)

修改为

if GetDepend('RT_USING_TC'):
	objs = objs + SConscript((RTT_ROOT + '/example/kernel/SConscript', variant_dir='build/tc/kernel', duplicate=0)

编译运行,启动finsh后,按下TAB键,可以出现很多函数, 这些函数就是输出到Finsh上的测试用例。

你可以执行
tc_start(“thread”) 对kernel做线程相关的测试
tc_start(“timer”) 对kernel做定时器相关的测试
tc_start(“semahore”) 对kernel做信号量相关的测试
...等等

下一节将剖析RTT的TestCase框架源码。

TestCase框架原理分析

在example/kernel/下提供了一个tc_sample.c,这个是RTT官方提供的TC测试用例的样本代码。我们将研究此文件来分析TestCase框架。

TC框架所涉及到文件为
examples/kernel/tc_comm.c
examples/kernel/tc_comm.h
examples/kernel/tc_sample.c

其中tc_sample.c源码如下:

#include 
#include "tc_comm.h"

static rt_thread_t tid = RT_NULL;
static void sample_thread(void* parameter)
{
	rt_kprintf("I'm sample!\n");
}
static void sample_thread_cleanup(struct rt_thread *p)
{
	tid = RT_NULL;
	tc_done(TC_STAT_PASSED);
}

int sample_init()
{
	tid = rt_thread_create("t",
		sample_thread, RT_NULL,
		THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE);
	if (tid != RT_NULL)
	{
		rt_thread_startup(tid);
		tid->cleanup = sample_thread_cleanup;
	}
	else
		tc_stat(TC_STAT_END | TC_STAT_FAILED);

	return 0;
}

#ifdef RT_USING_TC
static void _tc_cleanup()
{
	/* lock scheduler */
	rt_enter_critical();
	/* delete thread */
	if (tid != RT_NULL)
	{
		rt_kprintf("tid1 is bad\n");
		tc_stat(TC_STAT_FAILED);
	}
	/* unlock scheduler */
	rt_exit_critical();
}

int _tc_sample()
{
	/* set tc cleanup */
	tc_cleanup(_tc_cleanup);
	sample_init();

	return 25;
}
FINSH_FUNCTION_EXPORT(_tc_sample, a thread testcase example);
#else
int rt_application_init()
{
	sample_init();

	return 0;
}
#endif

未完待续....

你可能感兴趣的:(rt-thread)