kenny_yu的专栏

余志昌ID:kenny_yu
[修改头像]
18682次访问,排名5341(1)好友0人,关注者0
kenny_yu的文章
原创 9 篇
翻译 1 篇
转载 0 篇
评论 6 篇
kenny_yu的公告
技术兴趣:(1)VoIP相关的SIP/RTP等协议; (2)嵌入式Linux C/C++编程; (3)以脚本语言Python为基础的软件构建、测试、事务跟踪等。
最近评论
deltatang:DING。正在研究pycvs。thx
dellfox:看起来比较麻烦。太麻烦的测试方式,对边开发边测试来说是不现实的,程序员写代码的时候,注意力集中在如何实现代码功能上,这是很需要专注的工作,自然把麻烦的测试方法扔到九霄云外去了。
试试Visual Unit 2吧:分割、打桩、插桩、测试代码生成、统计,等等,所有麻烦事都是自动完成的,只有用例的输入输出是人工定义的(因为工具不可能自动了解代码的功能,VU并不是全自动工具,而是能自动的自……
Mingjwan:union{char a[16];
int i;
}u;
int *p=(int *)&(u.a[1]);
*p=17;

但是程序中遇到这样的问题,特别是协议处理时更容易遇到,该怎么办呢?
lauxp:代码能给我一份?lauxp1126@hotmail.com

谢谢
knight730:你好
最近正做ser相关的东西有几个问题想请教一下,
> 我做的问题很简单就是一个blind call forwarding,什么认证,NAT,其他都不需要,只要能把所有经过这个ser的呼叫都转移到一个pc sip phone上就行,ser.cfg应该怎么配置,必须要用mysql吗?
要用到哪个函数,是不是必须用avpops。so
软件项目交易
订阅我的博客
XML聚合  FeedSky
订阅到鲜果
订阅到Google
订阅到抓虾
订阅到BlogLines
订阅到Yahoo
订阅到GouGou
订阅到飞鸽
订阅到Rojo
订阅到newsgator
订阅到netvibes
文章分类
    收藏
      相册
      存档

      原创 白盒测试心得

      新一篇: yum研究笔记

      我维护的一个C编写的协议栈,运行于嵌入式Linux,缺乏文档和测试。以前没有这方面的经验,所以考察了ACE(自适应通信环境,http://www.cs.wustl.edu/~schmidt/ACE.html)的测试设计。以下是几点心得。

      1.嵌入式应用软件如何测试?

      嵌入式软件,如果是应用层的,则应当尽量少的修改移植到Linux PC上。这样就能做好单元测试、集成测试,也能充分利用DDD、Valgrind之类的工具定位BUG。《嵌入式软件测试策略》一文指出:“所有单元级测试都可以在主机环境上进行,除非少数情况,特别具体指定了单元测试直接在目标环境进行。最大化在主机环境进行软件测试的比例,通过尽可能小的目标单元访问所有目标指定的界面。软件集成也可在主机环境上完成,在主机平台上模拟目标环境运行,当然在目标环境上重复测试也是必须的,在此级别上的确认测试将确定一些环境上的问题,比如内存定位和分配上的一些错误。”

      2. 测试框架,需要吗?

      单元测试和集成测试没有严格界限。影响最大的单元测试框架是OO语言Java的JUnit。针对C++的CppUnit及其他OO语言的测试框架都很大程度上参考了JUnit的设计。CppUnit测试框架还是非常容易使用的,理解了测试用例、测试集之间的Composite模式(参考GoF《设计模式》)就差不多了。几个小时之内就能像模像样地用起来。

      由于C++语言特性导致维护C++测试代码花费更多精力:在Java中,你写的测试类的所有以test开头的方法会被自动加入到测试集中。但C++没有反射,所以CppUnit每个测试集(TestSuite)要定义一个成员函数以包含所有的测试方法。

      另一个麻烦是:我以前写的每个测试类都声明在一个对应头文件中,这个头文件被main以及相应测试类.cpp文件两者include。要增删测试方法,要改动至少两个文件;而要增删测试类也要改动至少两个文件。

      如果TDD(测试驱动开发)方式开发软件,测试代码和产品代码的修改非常频繁。修改-测试迭代周期拖长了就会导致开发速度减慢、测试少(相应地,BUG必然多)等问题。CppUnit定义了一些宏简化上述操作。下面给出的范例,不需要为测试类编写头文件,而是在Makefile中由链接关系决定那些测试类被编译入测试集。

      //测试类模板:FooTest.cpp
      #include <cppunit/extensions/HelperMacros.h>
      #include <cppunit/TestCase.h>
      class FooTest : public CppUnit::TestCase
      {
          CPPUNIT_TEST_SUITE(FooTest);
          CPPUNIT_TEST(testFoo1);
          CPPUNIT_TEST(testFoo2);
          CPPUNIT_TEST_SUITE_END();
      public:
          void setUp(){...}
          void tearDown(){...}
          void testFoo1(){
              CPPUNIT_ASSERT(expr);
              CPPUNIT_ASSERT_EQUAL(expected, actual);
              CPPUNIT_ASSERT_EQUAL_MESSAGE(msg, expected, actual);
              ASSERT_STR_EQUAL(expected, actual);
              ASSERT_STR_EQUAL_MESSAGE(msg, expected, actual);
          }
          void testFoo2(){...}
      }
      CPPUNIT_TEST_SUITE_REGISTRATION(FooTest);

      //测试主文件模板:unitmain.cpp
      #include <cppunit/extensions/TestFactoryRegistry.h>
      #include <sipxunit/TestRunner.h>
      int main( int argc, char* argv[] )
      {
          TestRunner runner;
          CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry();
          runner.addTest(registry.makeTest());
          bool wasSucessful = runner.run();
          return !wasSucessful;
      }

      //Makefile模板:Makefile
      testsuite_MAIN = unitmain.cpp
      testsuite_SOURCES = FooTest.cpp BarTest.cpp
      SOURCES = $(testsuite_MAIN) $(testsuite_SOURCES)
      all: dotest
      dotest: SOURCES
          $(CC) $(CFLAGS) -o $@ $^

      CppUnit是针对C++的,不适合用来测C代码:通过一番努力才能让测试代码调用产品代码,但反过来让产品代码调用测试代码(有时需要使用类似“桩”的技术)怎么也做不到。CUnit(http: //cunit.sourceforge.net/)是针对C的测试框架,比较合用。

      测试框架最核心的功能就是把测试用例组织起来,其他的像收集测试结果、输出报告、GUI方式启动等花哨功能绝大部分时候不需要。不要迷信权威或者懒惰:其实自己写个脚本管理测试用例是简单、可行的。

      我考察了ACE的白盒测试,发现它是用Perl来做的。docs/run_test.txt 是ACE自动化测试唯一的文档。ACE的自动测试是由十多个目录下的Perl脚本run_test.pl来做的,它读取文件run_test.lst获知所有的测试用例及其平台要求。所有的run_test.pl都基于一个自定义的 Perl模块PerlACE。bin/PerlACE目录下几个.pm(Perl模块)文件实现了PerlACE。docs/run_test.txt结合例子讲解了PerlACE的用法以及run_test.pl的书写方法。PerlACE以Perl脚本封装的可执行文件的启动、等待、杀死功能,其中最重要的模块是PerlACE::Run_Test。这儿的测试用例都是单个可执行文件(当然,执行期间可以用ACE_OS::fork创建Client和Server等进程,例如SOCK_Dgram_Test.cpp),每个用例都用Perl过程run_program来执行:

      sub run_program ($)
      {
      ......
          if ($config_list->check_config ('Valgrind')) {
            $P = new PerlACE::Process ($program);
            $P->IgnoreExeSubDir(1);
           }
          else {
            $P = new PerlACE::Process ($program);
            ### Try to run the program
            if (! -x $P->Executable ()) {
                print STDERR "Error: " . $P->Executable () .
                             " does not exist or is not runnable\n";
                return;
                  }
           }
          print "auto_run_tests: tests/$program\n";
          my $start_time = time();
          $status = $P->SpawnWaitKill (400);
          my $time = time() - $start_time;
          ### Check for problems
          if ($status == -1) {
              print STDERR "Error: $program FAILED (time out)\n";
              $P->Kill ();
              $P->TimedWait (1);
          }
          elsif ($status != 0) {
              print STDERR "Error: $program FAILED with exit status $status\n";
          }

          print "\nauto_run_tests_finished: test/$program Time:$time"."s Result:$status\n";
      ......
      }

      docs/run_test.txt还介绍了如何用PerlACE来启动Client和Server两个进程进行测试:

      # 准备工作。不要问我eval这句话有什么作用,我也不熟悉Perl。
      eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}'
          & eval 'exec perl -S $0 $argv:q'
          if 0;
      use lib '../../../bin';
      use PerlACE::Run_Test;
      $status = 0;
      $server_ior = PerlACE::LocalFile ("server.ior");
      unlink $server_ior;
      # 以指定的可执行文件名、参数列表构建进程。
      $SV = new PerlACE::Process ("server", "-o $server_ior");
      $CL = new PerlACE::Process ("client", " -k file://$server_ior ");
      # 启动Server进程。 "The PerlACE::waitforfile_timed method waits until the file is created.  In this way, we know when to start the client.  If no IOR file is used, then you'd need to use Perl's sleep method."
      $SV->Spawn ();
      if (PerlACE::waitforfile_timed ($server_ior, 5) == -1) {
          print STDERR "ERROR: cannot find file <$server_ior>\n";
          $SV->Kill ();
          exit 1;
      }
      # 启动Client进程并限时等待其结束。 " SpawnWaitKill will start the process and wait for the specified number of seconds for the process to end.  If the time limit is reached, it will kill the process and return -1. The return value of SpawnWaitKill is the return value of the process, unless it timed out."
      $client = $CL->SpawnWaitKill (60);
      if ($client != 0) {
          print STDERR "ERROR: client returned $client\n";
          $status = 1;
      }
      # 停止Server。"Servers are usually terminated either by TerminateWaitKill or just WaitKill. TerminateWaitKill is used when the server doesn't shut down itself.  WaitKill is used when it does (such as when the client calls a shutdown method)."
      $server = $SV->TerminateWaitKill (5);
      if ($server != 0) {
          print STDERR "ERROR: server returned $server\n";
          $status = 1;
      }
      # 以上任何步骤返回值非0表示本用例执行失败。无论成功与否都在退出前删除测试过程中创建的文件。
      unlink $server_ior;
      exit $status;

      在我的实际项目中,我把每个TestCast都编译成一个可执行文件,用一个Python脚本管理了它们的编译和启动。虽然比PerlACE少了进程控制功能,但对我来说也够用了。我将测试代码与产品代码分离,为测试代码编写适当的Makefile或者SCons配置文件(不需要对产品代码中的Makefile或SCons配置文件做任何改动),使得测试代码的编译和运行动作总是绑定的:一个命令就可以将测试代码与最新的产品代码编译链接、运行测试。

      3. 网络化、多线程应用程序如何测试?

      我以前做的单元测试都非常简单,不过是对基本的数据结构的各种操作方法测来测去(我见到的CppUnit和JUnit所有资料上也是如此),不涉及到网络、多线程的复杂环境。我以前做SNMPv3引擎时,为管理者和代理分别做了个例子程序,然后手工启动并观察运行效果。这样的测试一点也不自动化、一点也不充分。

      test/Barrier_Test.cpp测试了ACE栅栏同步机制。进行ACE_MAX_ITERATIONS轮测试,每一轮创建ACE_MAX_ITERATIONS个线程,每个线程进行ACE_MAX_ITERATIONS次栅栏同步。

      test/Message_Queue_Test.cpp有以下4个作用:
      0) a test that ensures key ACE_Message_Queue features are working properly, including timeouts and priorities.
      1) a simple test of the ACE_Message_Queue that illustrates how to use the forward and reverse iterators;
      2) a simple performance measurement test for both single-threaded (null synch) and thread-safe ACE_Message_Queues, and ACE_Message_Queue_Vx, which wraps VxWorks message queues; and
      3) a test/usage example of ACE_Message_Queue_Vx.

      test/Semaphor_Test.cpp测试了ACE_Thread_Semaphore的功能。创建了10个线程,每个线程10次获取锁并持有一会儿后释放它,测试超时次数。

      test/SOCK_Dgram_Test.cpp测试了UDP插口ACE_SOCK_Dgram类。它用ACE_OS::fork创建了两个进程分别作为Client和Server运行定制的函数。在open之后进行了一轮send/recv然后close。

      test/SOCK_Send_Recv_Test.cpp 测试了TCP插口ACE_SOCK_Stream、ACE_SOCK_Connector、ACE_SOCK_Acceptor共3个类。它用 ACE_OS::fork创建了两个进程分别作为Client和Server运行定制的函数。进行了5组数据的收发测试,双方都验证了收到的每组数据每个字节的正确性。

      test/Thread_Mutex_Test.cpp测试了进程范围内的锁(相当于pthread_mutex) ACE_Thread_Mutex。创建了ACE_MAX_THREADS个线程,线程进行多次迭代,每次迭代都尝试获得锁再释放锁。获得锁的两种方式(指定或不指定超时)都做了测试。

      examples/Reactor/WFMO_Reactor/run_test.pl对应着 WFMO_Reactor的测试用例(不含3个交互式的)。这儿的测试用例都是单个可执行文件就搞定的。WFMO_Reactor仅针对Win32平台,所以这儿把测试用例列表写在run_test.pl中而不是另一个文件中。

      examples/Reactor/TP_Reactor/run_test.pl对应着TP_Reactor的测试用例。测试用例只有1个,是CS结构的,C端和S端各一个可执行文件。它创建一个Server进程和2个Client进程。

      可见,网络化、多线程应用程序的测试并没有什么神秘的:与之前相同的是仍然要编写断言来判断用例成功与否,不同的是用例中一般需要创建若干线程并构造好交互的场景。

      4. 测试桩和驱动

      Java的EasyMock配合JUnit使得做测试桩非常容易。C++的模拟对象有mockpp(mockpp.sourceforge.net/)等,都不如Java的EasyMock好用。自己编写C测试桩的工作量太大。所以在我的项目实际中,我没有编写测试桩,也就没有区分单元测试和集成测试。做白盒测试时,测试代码的编译能很好地反映出模块之间的依赖关系,这对精炼模块接口是很有好处的!

      发表于 @ 2007年03月11日 11:11:00|评论(loading...)|编辑

      评论

      #dellfox 发表于2007-12-24 21:48:53  IP: 59.42.132.*
      看起来比较麻烦。太麻烦的测试方式,对边开发边测试来说是不现实的,程序员写代码的时候,注意力集中在如何实现代码功能上,这是很需要专注的工作,自然把麻烦的测试方法扔到九霄云外去了。
      试试Visual Unit 2吧:分割、打桩、插桩、测试代码生成、统计,等等,所有麻烦事都是自动完成的,只有用例的输入输出是人工定义的(因为工具不可能自动了解代码的功能,VU并不是全自动工具,而是能自动的自动,需人工的人工)。由于具有自动补齐未定义符号、自动隔离复杂的底层代码、在用例中随意控制底层函数行为等功能,适应能力比人工编写代码强多了,例如,20万行代码的项目要随便挑几个源文件做单元测试,由于代码耦合,人工是做不到的,但用VU2却可以轻松做到。
      发表评论  


      当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
      Csdn Blog version 3.1a
      Copyright © kenny_yu