CppUnit介绍

CppUnit 介绍
 
为什么要写代码的测试代码
       测试代码人人都写过:
              1 )写完就扔
              2 )开发过程中重复使用( unit test 是其中一种)
      
       写测试代码的目的:
验证结构、功能、性能等。
      
       测试 分类:
              1
                     白盒(结构性)测试
                     黑盒(功能性)测试
                            单元测试属于结构性测试,白盒测试。
              2
                     Programer tests
                     Acceptance tests
                            单元测试适用于这两者。
      
常用的检查程序错误的方式:
              1 )依赖编译器信息
              2 )借助调试器
              3 )代码复查
              4 xUnit ,写好一次,总能自动检查
 
什么是 U ni t T est F ramework
       1 )单元测试框架是编写和运行单元测试的软件工具,用来构建测试、运行测试、报告测试结果。
       2   unit test famework  的历史 以及 CppUnit
              JUnit -> xUnit (含 CppUnit
 
       3 xUnit 家族
              Junit - The reference implementation of xUnit
              CppUnit - The C++ port of JUnit
              NUnit - The xUnit for .NET
              PyUnit - The Python version of xUnit
              MinUnit - minimal but functional C Languages unit test framework
              XmlUnit – for XML contents
              ...
什么是 CppUnit
CppUnit 是个基于 LGPL 的开源项目,最初版本移植自 JUnit ,是一个非常优秀的开源测试框架。 CppUnit JUnit 一样主要思想来源于极限编程( XProgramming )。主要功能就是对单元测试进行管理,并可进行自动化测试。
 
CppUnit 架构:
       namespace:
  
      
       key classes:
             
       classes to collect test results:
             
       outputter classes for printing test results:
             
 
       key assert methods:
                  CPPUNIT_ASSERT (condition)
 
CPPUNIT_ASSERT_MESSAGE(message,condition)
Test that passes if condition is true.
 
CPPUNIT_FAIL(message)
Test that always fails.
 
CPPUNIT_ASSERT_EQUAL(expected,actual)
 
CPPUNIT_ASSERT_EQUAL_MESSAGE(message,expected,actual)
Test that passes if expected and actual are equal. It supports arguments of most common data types and std::string.
 
CPPUNIT_ASSERT_DOUBLES_EQUAL(expected,actual,delta)
Test that passes if expected and actual are equal within a tolerance of delta. The arguments are of type double.
 
 
产品代码与测试代码关系
       1 )产品代码和测试代码的目录结构示意图
      
2 )产品代码与测试框架关系示意图

Unit Test TDD (测试驱动开发)
       1 测试驱动开发精髓:
           a )维护详尽的程序员编写的测试程序组
            b )除非有相关的测试,否则代码不应被加入产品( 极限 编程 ,因为测试是重要的,所以对几乎所有代码都要有测试)
              c )测试先行
              d )测试决定你需要写的代码
      
       2 )测试驱动 (TDD) 开发的重要原则:
“Test twice, code once” - 测试两次,编码一次。
步骤: (TDD 循环 )
a )编写新代码的测试,查看是否失败
b )编写新代码,以最简方式实现
c )再次测试是否成功,重构代码
 
 
三个 例子:(使用 CppUnit Framework
 
// Example 1: comes from Unit Test Framework
介绍:测试 Book 类。
 
步骤 0: 建立单元测试框架
       如果不采用既有的单元测试框架,则可以简单写个测试框架类。因为我们用 CppUnit ,所以不推荐自己建立这样的框架,这里仅作为例子。在 Example 2 中,我们将直接使用 Cppunit 框架。
     UnitTest
runTest()
assertTrue()
num_test_sucess:int
// UnitTest.h
#define UT_ASSERT( condition ) /
        assertTrue(condition,__FILE__,__LINE_ _,#condition)
class UnitTest 
{
public:
   virtual ~UnitTest( ) {}
   virtual void runTest( ) = 0;
 protected:
   void assertTrue(bool condition, const char *file, 
                  int line, const char *msg);
   static int num_test_success;
};
 
 
// UnitTest.cpp
#include 
#include 
#include "UnitTest.h"
 
int UnitTest::num_test_success = 0;
 
void UnitTest::assertTrue(bool condition, 
                              const char *file, int line, 
                           const char *msg) 
{
     if (!(condition)) 
     {
          printf("FAILURE!/n");
         printf("%s:%d:%s/n", file, line, msg);
          exit(1);
     }
 
      ++num_test_success; 
}
 
编译: g++ -c UnitTest.cpp
 
步骤 1 :建立单元测试
       从抽象类 UnitTest 派生一个单元测试类 BookTest 用来测试产品代码 Book
       UnitTest <------ BookTest
 
//BookTest.h
#include "UnitTest.h"
#include "Book.h" //
 
class BookTest : public UnitTest 
{
public:
   void runTest( )  // override
{
      Book book("Cosmos");
      UT_ASSERT(!strcmp(book.title, "Cosmos"));
  }
};
 
主程序
//TestRunner.cpp
#include "stdio.h"
#include "BookTest.h"
 
int main( ) 
{
   BookTest test;
   test.runTest( ); // exit via assert if test fails.
   printf("SUCCESS!/n");
   return 0;
}
 
如果编译 TestRunner.cpp ,报错,无 Book.h 文件。
 
因此建立 Book.h 的初始版本
//Book.h
#include "string.h"
 
class Book 
{
public:
   Book(const char* title) {}
   char title[255];
};
有了这个初始版本, TestRunner.cpp 就可以编译通过了。
 
此时编译整个测试程序: g++ -o TestRunner TestRunner.o UnitTest.o
运行 ./TestRunner 报错如下 (注:这里是 TDD 所谓的第一次测试)
 
FAILURE!
BookTest.h:9:!strcmp(book.title, "Cosmos")
 
步骤 2 :正确建立 Book  ( 注:这里是 TDD 所谓的编写一次 )
//Book.h
#include "string.h"
class Book
{
public:
   Book(const char* title) {
      strcpy(this->title, title);
   }
 
   char title[255];
};
 
步骤三:再次测试   (注:这里是 TDD 所谓的第二次测试)
再编译
运行,结果正确,显示如下:
 
SUCCESS!
 
现在, Book 代码可以被签入 (check in) 代码仓库。
如果有必要,可以进行重构。
以后任何对 Book 代码的更动,都必须使得测试程序能够通过。
 
 
// Example 2: Cppunit 框架改写 Example1
--------- 
1
CppUnitTestCaseUnitTest
//Book.h
using std::string;
class Book {
 public:
   Book( string const &title )
      : m_title( title ) {}
 
   string getTitle( ) { return m_title; }
 
 private:
   string m_title;
};
--------
//BookTest.h
#include "cppunit/TestCase.h"
#include "Book.h"
using std::string;
 
class BookTest : public CppUnit::TestCase {
 public:
   BookTest( string const &name ) : CppUnit::TestCase( name ) {}
   void runTest( ) {               //override
      Book book( "Cosmos" );
      CPPUNIT_ASSERT( book.getTitle( ) == "Cosmos" );
   }
};
----------
//test.cpp
#include 
#include "BookTest.h"
 
int main( ) {
   try {
      BookTest test( "BookTest" );
      test.runTest( );
      std::cout << "SUCCESS!" << std::endl;
      return 0;
   } catch ( ... ) {
      std::cout << "FAILURE!/n" << std::endl;
      return 1;
   }
}
 
2
另外,如果需要运行多个测试而不是放在单个 runTest() 中,则需要引入TestFixture
Book.h
using std::string;
class Book {
 public:
   Book(string const &title, string const &author)
      : m_title( title ), m_author( author ) {}
   string getTitle( ) { return m_title; }
   string getAuthor( ) { return m_author; }
 private:
   string m_title;
   string m_author//
};
---------
BookTest.h
#include "cppunit/TestCase.h"
#include "Book.h"
using std::string;
 
// 
从TestFixture派生;
增加作为fixture一部分的成员变量;
覆盖setUp(),teardown()
class BookTest : public CppUnit::TestFixture {
 
private:
   Book *book;
 
public:
   void setUp( ) { // override
      book = new Book( "Cosmos", "Carl Sagan" );
   }
 
 void tearDown( ) { // , override
      delete book;
   }
 
   void testTitle( ) { // 
      CPPUNIT_ASSERT_EQUAL( string("Cosmos"), book->getTitle( ) );
   }
 
   void testAuthor( ) { // 
      CPPUNIT_ASSERT_EQUAL( string("Carl Sagan"), book->getAuthor( ) );
   }
};
 
----------
//test.cpp
#include "cppunit/ui/text/TestRunner.h"
#include "cppunit/TestCaller.h"
#include "BookTest.h"
 
int main( ) {
   CppUnit::TextUi::TestRunner runner//text TestRunner
   runner.addTest( new CppUnit::TestCaller(
                   "testTitle",
                   &BookTest::testTitle ) );
   runner.addTest( new CppUnit::TestCaller(
                   "testAuthor",
                   &BookTest::testAuthor ) );
   if ( runner.run( ) ) // true
      return 0;
   else
      return 1;
}
---------
$ ./test
OK (2 tests)
如果象下面这样改变测试条件,则报错 :
CPPUNIT_ASSERT_EQUAL( string("Anonymous"), book->getAuthor( ) );

报告错误的细节 assertion that failed:
$ ./test
..F
!!!FAILURES!!!
Test Results:
Run: 2   Failures: 1   Errors: 0
1) test: testAuthor (F) line: 25 BookTest.h
expected: Anonymous
but was: Carl Sagan

3
使用 TestSuite, main() 中的 addTest() 转移到 suite()
//BookTest.h
//BooKTestsuite()Helper Macros
//  (4)
   static CppUnit::Test* suite()
   {
      CppUnit::TestSuite *suite
         = new CppUnit::TestSuite( "BookTest" );
      suite->addTest( new CppUnit::TestCaller(
                      "testTitle",
                      &BookTest::testTitle ) );
      suite->addTest( new CppUnit::TestCaller(
                      "testAuthor",
                      &BookTest::testAuthor ) );
      return suite;
   }
 
//test.cpp
#include "cppunit/ui/text/TestRunner.h"
#include "cppunit/TestCaller.h"
#include "BookTest.h"
int main( ) {
   CppUnit::TextUi::TestRunner runner;
   runner.addTest( BookTest::suite( ) );
  // ::suite() 
   // runner.addTest(OtherTest::suite());
   if ( runner.run( ) ) // 
      return 0;
   else
      return 1;
}
----------
 
错误信息的构成
  • The name of the test case that failed
  • The name of the source file that contains the test
  • The line number where the failure occurred
  • All of the text inside the call to CPPUNIT_ASSERT() which detected the failure
 
(4)
由于对每个测试类都要重复编写 suite() 静态函数,容易出错,所以
使用 Helper Macros 来替换手工编写 suite() 静态函数,和注册函数
在测试类的定义中写入:
CPPUNIT_TEST_SUITE( BookTest );
CPPUNIT_TEST( testTitle );
CPPUNIT_TEST( testAuthor );
CPPUNIT_TEST_SUITE_END( ) ;
等价于在测试类 BookTest 中声明并实现
public:
    static CppUnit::Test* suite() {…}
 
对测试函数抛出异常的测试,用下面这个宏:
       CPPUNIT_TEST_EXCEPTION( testFunction, exception );
        异常抛出,则测试通过
 
使用 TestFactoryRegistry
       避免忘记向 testRunner 中注册 fixture suite ( runner.addTest ( BookTest::suite( ) ) )
    由于头文件包含所致的编译瓶颈
.cpp 文件中使用
#include <cppunit/extensions/HelperMacros.h>
CPPUNIT_TEST_SUITE_REGISTRATION( ComplexNumber ); // 
 
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TestRunner.h>
 
int main( int argc, char *argv[])
{
 CppUnit::TextUi::TestRunner runner;
 CppUnit::TestFactoryRegistry ®istry = CppUnit::TestFactoryRegistry::getRegistry();
 runner.addTest( registry.makeTest() );
 if (runner.run()) // run
        return 0;
 else
         return 1;
}
 
 
// Example3, 一个完整的例子,使用 Helper Macros. 来自 CppUnit Cookbook
----------------
// compile: g++ -o test1-by-macro test1-by-macro.cpp -lcppunit -ldl
#include
#include
#include
#include
//#include
//#include
//#include
//#include
//#include
//#include
 
class Complex
{
public:
       Complex(double r, double i)
              : real(r), imaginary(i)
              {
              }
      
       double real;
       double imaginary;
};
 
bool operator==(const Complex& a, const Complex& b)
{
       return ((a.real == b.real) && (a.imaginary == b.imaginary));
}
 
Complex operator+(const Complex& lhs, const Complex& rhs)
{
       return Complex(lhs.real + rhs.real, lhs.imaginary + rhs.imaginary);
}
 
class ComplexNumberTest : public CPPUNIT_NS::TestFixture
{
public: // here 'public' can be ignored
      
       CPPUNIT_TEST_SUITE( ComplexNumberTest );
       CPPUNIT_TEST( testEquality );
       CPPUNIT_TEST( testAddition );
       CPPUNIT_TEST_SUITE_END();
 
/* static CppUnit::Test *suite()
 {
    CppUnit::TestSuite *suiteOfTests = new CppUnit::TestSuite( "ComplexNumberTest" );
    suiteOfTests->addTest( new CppUnit::TestCaller(
                                   "testEquality",
                                   &ComplexNumberTest::testEquality ) );
    suiteOfTests->addTest( new CppUnit::TestCaller(
                                   "testAddition",
                                   &ComplexNumberTest::testAddition ) );
    return suiteOfTests;
 }
 
*/
public:// have 'public'
       void setUp()
       {
              m_10_1 = new Complex(10, 1);
              m_1_1 = new Complex(1, 1);
              m_11_2 = new Complex(11, 2);
       }
 
       void tearDown()
       {
              delete m_10_1;
              delete m_1_1;
              delete m_11_2;
       }
      
       void testEquality()
       {
              CPPUNIT_ASSERT(*m_10_1 == *m_10_1);
              CPPUNIT_ASSERT((*m_10_1 == *m_11_2));
       }
 
       void testAddition()
       {
              CPPUNIT_ASSERT(*m_10_1 + *m_1_1 == *m_11_2);
       }
private:
       Complex* m_10_1;
       Complex* m_1_1;
       Complex* m_11_2;
};
 
// Register test class ComplexNumberTest
CPPUNIT_TEST_SUITE_REGISTRATION( ComplexNumberTest );
 
int main()
{
/*
      CPPUNIT_NS::TextUi::TestRunner runner;
       runner.addTest(ComplexNumberTest::suite());
       runner.run();
*/
 
       CppUnit::TextUi::TestRunner runner;
     CppUnit::TestFactoryRegistry ®istry = CppUnit::TestFactoryRegistry::getRegistry();
     runner.addTest( registry.makeTest() );
     if (runner.run())
              return 0; // 返回值可用于自动化测试
       else
            return 1;
}
 
 
 
 
 
 
 
--------------------------
介绍:测试 Complex 类的构造及它的 == + 操作
    ComplexNumberTest Complex 的测试类
    Complex 产品 程序中的类
 
// Example 2 .
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
 
class Complex
{
public:
    Complex(double r, double i)
        : real(r), imaginary(i)
    {
    }
    // 为简便起见,此处为 public
    double real;
    double imaginary;
};
 
bool operator==(const Complex& a, const Complex& b)
{
    return ((a.real == b.real) && (a.imaginary == b.imaginary));
}
 
Complex operator+(const Complex& lhs, const Complex& rhs)
{
    return Complex(lhs.real + rhs.real, lhs.imaginary + rhs.imaginary);
}
 
// CPPUNIT_NS 可以写成 CppUnit
class ComplexNumberTest : public CPPUNIT_NS::TestFixture
{
public:
    static CppUnit::Test *suite()
  {
         CppUnit::TestSuite *suiteOfTests = new CppUnit::TestSuite( "ComplexNumberTest" );
  
        suiteOfTests->addTest( new CppUnit::TestCaller(
                                   "testEquality",
                                   &ComplexNumberTest::testEquality ) );
suiteOfTests->addTest( new CppUnit::TestCaller(
                                   "testAddition",
                                   &ComplexNumberTest::testAddition ) );
         return suiteOfTests;
    }
 
    void setUp()
    {
        m_10_1 = new Complex(10, 1);
        m_1_1 = new Complex(1, 1);
        m_11_2 = new Complex(11, 2);
    }
 
    void tearDown()
    {
        delete m_10_1;
        delete m_1_1;
        delete m_11_2;
    }
   
    void testEquality()
    {
        CPPUNIT_ASSERT(*m_10_1 == *m_10_1);
        CPPUNIT_ASSERT((*m_10_1 == *m_11_2));
    }
 
    void testAddition()
    {
        CPPUNIT_ASSERT(*m_10_1 + *m_1_1 == *m_11_2);
    }
private:
    Complex* m_10_1;
    Complex* m_1_1;
    Complex* m_11_2;
};
 
int main()
{
    CPPUNIT_NS::TextUi::TestRunner runner;
    runner.addTest(ComplexNumberTest::suite());
    runner.run();
    return 0;
}
 
CppUnit 用法 - 测试的对象及对策,参见测试驱动开发!
  全局函数的测试   全局变量的测试?
  具体类测试
 public,protected private 函数测试
  抽象类 / 接口测试
 mock 对象:
  性能测试:  
 
 
参考:
       Test-Driven Development by Examples
       Unit Test Framework
       CppUnit Cookbook
 

你可能感兴趣的:(软件过程)