RT_Thread的测试框架使用及分析

来源:互联网 发布:java empty string 编辑:程序博客网 时间:2024/04/27 22:44

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.h>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 teamfinsh />


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

--function:testfunc         -- just a test functionlist_mem         -- list memory usage informationlist_date        -- list datehello            -- say hello worldversion          -- show RT-Thread version informationlist_thread      -- list threadlist_sem         -- list semaphone in systemlist_event       -- list event in systemlist_mutex       -- list mutex in systemlist_mailbox     -- list mail box in systemlist_msgqueue    -- list message queue in systemlist_mempool     -- list memory pool in systemlist_timer       -- list timer in systemlist_device      -- list device in systemlist             -- list all symbol in system--variable:dummy            -- dummy variable for finshfinsh />

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

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

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


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

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

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

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

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

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

#include <rtthread.h>int testfunc1(void){rt_kprintf("hello, rt-thread! this is testfunc1\n");}#include <finsh.h>FINSH_FUNCTION_EXPORT(testfunc1, just a test function);

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

#include <rtthread.h>int testfunc2(void){rt_kprintf("hello, rt-thread! this is testfunc2\n");}#include <finsh.h>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 <rtthread.h>#include "tc_comm.h"int testfunc1(void){rt_kprintf("hello, rt-thread! this is testfunc1\n");}#include <finsh.h>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 <rtthread.h>#include "tc_comm.h"int testfunc2(void){rt_kprintf("hello, rt-thread! this is testfunc2\n");}#include <finsh.h>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, 0x3001f408finsh />Run TestCase: testfunc1hello, rt-thread! this is testfunc1TestCase[testfunc1] passedRun TestCase: testfunc2hello, rt-thread! this is testfunc2TestCase[testfunc2] passedRT-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 <rtthread.h>#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;}elsetc_stat(TC_STAT_END | TC_STAT_FAILED);return 0;}#ifdef RT_USING_TCstatic 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);#elseint rt_application_init(){sample_init();return 0;}#endif
未完待续....
原创粉丝点击