UML软件工程组织

 

 

ShortCUT - 一个简短的c++单元测试框架
 

2008-01-30 来源:spaces.live.com

 

介绍

这个测试框架只有125行的单个头文件。它的目的是提供一种简单的方式来对c++进行单元测试。因为简单,它还能狗容易定制。一些测试框架都需要链接分离的库文件或者经过多次跳转才能工作,这就使得写case是一件非常难得事情了。

其中一种情况是开发人员经常碰到当他们需要写单元测试用例的时候他们已经工作于项目上了。如果这个项目不应独立的库而坏掉,这就很难写出独立的单元测试用例。另外,一些程序的一些核心function不能简单的被破坏成一个单独可执行的用例。实际上,一个开发人员应该写一些单元测试用例,在一个程序内部调用自己的function,可以简单的使用#define。

这看起来不像一种很好的软件工程方法,但是,这些都是尽早测试,通常比其它方法更好,用最小的努力来写测试,最早的完成。测试集合随着项目前进而增长,由于时间限制测试就能集合到一个单独的库和可执行的东东。

我开始寻找一个现成的解决方案。在互联网上有很多xUnit的测试框架,比如Smalltalk SUnit,JUnit,NUnit,CppUnit等等

但是很多框架都有一个共同的就是能够让它的属性在一个function中run起来,或者让其自动的跑起来。C++程序员没有那么幸运,因为需要做很多手工的工作比如宏,模板之类的,尽管不是大的工作。

设计

在评估一些测试框架后,我决定它们都不是能够满足我的简单的而且能够使用修改的要求。我决定写一个测试框架,写到一个头文件中,尽量只包含尽量少的代码。这里列出设计原则:

  •  能够在一个单独的头文件中(没有模块或库)
  • 几百行代码
  • 易于修改扩展
  • 信息输出
  • 可选宏
  • 不要模板
  • 不要动态内存分配
  • 用于嵌入式系统中
  • 用兼容与低级别的C++ compilers (没有超炫的功能)

使用Code

  • 建立case有三件事需要做
  • 一个测试用例test case
  • 一个测试集合test suite
  • 测试集合需要添加到runner然后被调用

写一个case

这个例子中,我们从TestCase基类派生出我们的测试用例TestCase,像其他的框架的一样,是一个结构体,能够帮助我们避免一些公共访问。

测试code被加到一个测试方法中,TestSuite类含有一切能够让所有测试用例能够访问的任何数据,能够传递到每个测试用例的函数。当测试失败事件是能够输出有意义的输出。

struct TestAccountWithdrawal : TestCase
{
    const char* name() { return "Account withdrawal test"; }
 
    void test(TestSuite* suite) 
    {
        TestAccountSuite* data = (TestAccountSuite*)suite;
 
        data->account->Deposit(10);
 
        bool succeeded = data->account->Withdraw(11);
 
        T_ASSERT(succeeded == false);
        T_ASSERT(data->account->Balance() == 10);
    }
};

添加一个测试集test suite

测试集包含一组相关的测试用例,他的目的像其他的框架中的test suite和test fixture共同的功能。有两个关键的方法(都是可选的)是setup和teardown,它们总是成对出现。
struct TestAccountSuite : TestSuite
{
    const char* name() { return "Account suite"; }
 
    void setup()
    {
        account = new Account();
    }
 
    void teardown() 
    {
        delete account;
    }
 
    Account* account;
};

把它们组织到一起

一旦一个测试集和至少一个测试用例完成之后我们可以将它们放到一个runner中执行。
#include <stdio.h>
#include "shortcut.h"
#include "tests/account.h"
int main(int argc, char* argv[])
{
    TestRunner runner;
    TestAccountSuite accountSuite;
    TestAccountWithdrawal accountWithdrawalTest;
 
    accountSuite.AddTest(&accountWithdrawalTest);
    runner.AddSuite(&accountSuite);
    runner.RunTests();
    
    return 0;
}

这个很小的系统的好处是能够都在一个头文件中。这就防止不同类的声明定义重复。因为之用一个头文件,所以只需要要一个驱动(driver),比如在main函数中。

这个系统可以很容易添加一个测试用例到现有的程序中。比如,一些用例被#ifdef DEBUG宏控制块,在Release版本就不会输出到二进制文件中,不会连接到其他单元测试库上。

显然这不是长效的解决方法,尽管是一个好的方法的开始。开发人员能够在开发时间内分离测试和code才是

基类

所有的测试用例派生与一个基类TestCase.
struct TestCase
{
    TestCase() : next(0) {}
    
    virtual void test(TestSuite* suite) {}
    virtual const char* name() { return "?"; }
    
    TestCase* next;
};

有个名字的方法用来记录错误的。用一个指针指向测试用例的列表。它本身是个虚函数能够派生。

测试集test suite具有相同的结构,除了包含一系列case和两个重载函数setup和teardown

+struct TestSuite
{
    TestSuite() : next(0), tests(0) {}
 
    virtual void setup() {}
    virtual void teardown() {}
    virtual const char* name() { return "?"; }
 
    void AddTest(TestCase* tc)
    {
        tc->next = tests;
        tests = tc;
    }
 
    TestSuite* next;
    TestCase* tests;
};

像开始提及的一样这个test suite类扮演着其他框架中的 test suite和test fixture类的角色. 这在写框架中,当fixture提供setup/teardown机制时,suite常常扮演测试组织的角色,因为 ShortCUT是一个简单的框架,就不需要创建这些复杂的类了。当开发需要一个类似的功能是很容易定制的加上。

执行Runner

一个测试的runner是测试的主体,当然也是一个非常直接的他的主线是调用测试的方法,run每个suite。
struct TestRunner
{
    ...
    void RunSuite(TestSuite* suite, int& testCount, int& passCount)
    {
        TestCase* test = suite->tests;
        while (test)
        {
            try
            {
                suite->setup();
                test->test(suite);
                passCount++;
            }
            catch (TestException& te)
            {
                log->write("FAILED '%s': %s\n", test->name(), te.text());
            }
            catch (...)
            {
                log->write("FAILED '%s': unknown exception\n", test->name());
            }
 
            try
            {
                suite->teardown();
            }
            catch (...)
            {
                log->write("FAILED: teardown error in suite '%s'\n", suite->name());
            }
 
            test = test->next;
            testCount++;
        }
    }
    ...
}

这个关键点要注意,首先记录类执行在框架之外。这就容易让结果输出到另外一个目标中,像一个窗体。第二点,烦恼的是测试集合和测试用例像个链子一样,这就像一个LIFO的规则一样,反向与添加时候的顺序。

这就会有一个简单的方式能够修复这个问题,但是为了框架的简单性我删除了它。

这个框架的主要目标是是个尽量简单的解决方案。像TestLog类可以加到上面来帮助满足需求,尽管框架是简单的,但也不会失去基本的机动性。

头文件有200行代码,四分之一是不需要的,希望能能够组建一个能够剪裁的系统、能够很容易使用修改定制的系统。
附录:
/*
 * Copyright (c) 2003-2004  Pau Arum?& David Garc韆
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#ifndef MiniCppUnit_hxx
#define MiniCppUnit_hxx

/**
 * @mainpage
 * miniCppUnit
 * (C) 2003-2006 Pau Arumi & David Garcia
 *
 * @version 2.5 2006-03-14
 *   - MS Visual compatibility: SConstruct ccflags, usage example, #ifdefs
 * @version 2.4 2006-03-14
 *   - exit test case after first failure
 *   - double and float comparison with fuzzy equals (using scalable epsilon)
 *   - have into account not a numbers
 *   - new ASSERT_EQUALS_EPSILON macro
 *   - more colors, and disabled when comiled in MS Visual
 *   - removed catalan location.
 *   - UsageExample.cxx now uses all macros and features
 * @version 2.3 2006-02-13 added usage example and SConstruct
 * @version 2.2 2004-11-28 code in english and tests suites
 * @version 2.1 2004-11-04 char* especialization
 * @version 2.0 2004-10-26 TestsFactory
 * @version 1.0 2003-10-28 initial
 *
 * Example of use:
 *
 * @code
 * #include "MiniCppUnit.hxx"
 * class MyTests : public TestFixture<MyTests>
 * {
 *  public:
 *   TEST_FIXTURE( MyTests )
 *  {
 *   CAS_DE_TEST( testAddition );
 *   // etc
 *  }
 *  void testAddition()
 *  {
 *   ASSERT_EQUALS( 4, 1+1+2 );
 *  } 
 *  // etc
 * };
 *
 * REGISTER_FIXTURE( MyTests );
 * @endcode
 * @code
 * int main()
 * {
 * return TestFixtureFactory::theInstance().runTests() ? 0 : -1;
 * }
 * @endcode
 * Good things:
 *
 *   - it's a tiny framework made up of two or three src files.
 *     => no need to install as a library
 *   - object oriented and makes use of several GoF patterns
 *   - very simple usage. Just needs to learn very few C macros
 *   - string asserts are simpler to use than cppunit
 *   - string asserts are enhanced with coloured diffs
 *   - concrete test classes are totally decoupled via static factory
 *     => no src file have to include them all.
 *   - it have test suite hierarchies
 *   - compatible with non-standard compliant VisualC6
 *     (though not necessary good ;)
 */

#include <iostream>
#include <string>
#include <sstream>
#include <list>

#if _MSC_VER < 1300
/** necesary for Visual 6 which don't define std::min */
namespace std
{
 template<typename T>
 min(const T& a, const T& b) { return a < b ? a: b; }
}
#endif

/**
 * A singleton class.
 * Receives tests results and stores messages to the test log
 * for later listing.
 * It's a singleton for an easy global access from the 'Asserts'
 * methods but it is probably asking for a refactoring in order to limit
 * access only to TestFixtures
 */
class TestsListener
{
public:
 /** accessor to the global (static) singleton instance */
 static TestsListener& theInstance();
 std::stringstream& errorsLog();
 std::string logString();
 void currentTestName( std::string& name);
 static void testHasRun();
 static void testHasFailed();
 static void testHasThrown();
 /** the human readable summary of run tests*/
 std::string summary();
 /** returns wheather all run tests have passed */
 static bool allTestsPassed();
 
private:
 static const char* errmsgTag_nameOfTest() { return "Test failed: "; }
 
 /** constructor private: force the singleton to be wellbehaved ! */
 TestsListener() : _currentTestName(0)
 {
  _executed=_failed=_exceptions=0;
 }
 
 std::string* _currentTestName;
 std::stringstream _log;
 unsigned _executed;
 unsigned _failed;
 unsigned _exceptions;
};

class TestFailedException
{
};

/**
 * Abstract class with interface that allows run a test. That is runTest
 * and name. It is implemented by TestFixture and TestCase
 *
 * It does the 'Component' role in the 'Composite' patten
 **/
class Test
{
public:
 virtual ~Test(){}
 /** run the test: exercice the code and check results*/
 virtual void runTest() = 0;
 /** the test human-readable name */
 virtual std::string name() const = 0;
};


/**
 * This class is just a placeholder for all assert functions --as static methods.
 * It is meant for being used just by the assert macros
 */
class Assert
{
 static const char * errmsgTag_testFailedIn() { return "Test failed in "; }
 static const char * errmsgTag_inLine() { return ", line: "; };
 static const char * errmsgTag_failedExpression() { return "Failed expression: "; }
 static const char * errmsgTag_expected() { return "Expected: "; }
 static const char * errmsgTag_butWas() { return "But was: "; }

public:
#ifdef _MSC_VER
 static const char * blue() { return ""; }
 static const char * green() { return ""; }
 static const char * red() { return ""; }
 static const char * normal() { return ""; }
 static const char * bold() { return ""; }
 static const char * yellow() { return ""; }
#else
 static const char * blue() { return "\033[36;1m"; }
 static const char * green() { return "\033[32;1m"; }
 static const char * red() { return "\033[31;1m"; }
 static const char * normal() { return "\033[0m"; }
 static const char * bold() { return "\033[" "1m"; }
 static const char * yellow() { return "\033[93;1m"; }
#endif
 template<typename AType>
 static void assertEquals( const AType& expected, const AType& result,
  const char* file="", int linia=0 )
 {
  if(expected != result)
  {
   TestsListener::theInstance().errorsLog()
    << file << ", linia: " << linia << "\n"
    << errmsgTag_expected() << " " << expected << " "
    << errmsgTag_butWas() << " " << result << "\n";
   TestsListener::theInstance().testHasFailed();
  }
 }

 static void assertTrue(char* strExpression, bool expression,
   const char* file="", int linia=0);

 static void assertTrueMissatge(char* strExpression, bool expression,
   const char* missatge, const char* file="", int linia=0);

 static void assertEquals( const char * expected, const char * result,
  const char* file="", int linia=0 );
 
 static void assertEquals( const bool& expected, const bool& result,
  const char* file="", int linia=0 );
 
 static void assertEquals( const double& expected, const double& result,
  const char* file="", int linia=0 );

 static void assertEquals( const float& expected, const float& result,
  const char* file="", int linia=0 );
 
 static void assertEquals( const long double& expected, const long double& result,
  const char* file="", int linia=0 );
 
 static void assertEqualsEpsilon( const double& expected, const double& result, const double& epsilon,
  const char* file="", int linia=0 );

 static int notEqualIndex( const std::string & one, const std::string & other );

 /**
  * we overload the assert with string doing colored diffs
  *
  * MS Visual6 doesn't allow string by reference :-(
  */
 static void assertEquals( const std::string expected, const std::string result,
  const char* file="", int linia=0 );
 
 static void fail(const char* motiu, const char* file="", int linia=0);


};

/**
 * A TestFixture is a class that contain TestCases --which corresponds to
 * ConcreteTestFixture methods-- common objects uder tests, and setUp and
 * tearDown methods which are automatically executed before and after each
 * test case.
 *
 * Is the base class of ConcreteFixtures implemented by the framework user
 *
 * It does the 'Composite' role in the 'Composite' GoF pattern.
 * Its composite children are TestCases, which wrapps the test methods.
 *
 * It is a template class parametrized by ConcreteTestFixture so that it can
 * instantiate TestCase objects templatized with this same parameter: it needs the
 * concrete class type for calling its non-static methods.
 */
template <typename ConcreteTestFixture>
class TestFixture : public Test
{
protected:

 typedef ConcreteTestFixture ConcreteFixture;
 typedef void(ConcreteTestFixture::*TestCaseMethod)();

 /**
  * Wrapper for the test methods of concrete TestFixtures.
  *
  * Makes the 'Leave' role in the 'Composite' GoF pattern because can't be
  * be a composition of other tests.
  *
  * It's also a case of 'Command' pattern because it encapsules in an object
  * certain functionality whose execution depends on some deferred entity.
  */
 class TestCase : public Test
 {
 public:
  TestCase(ConcreteFixture* parent, TestCaseMethod method, const std::string & name) :
    _parent(parent),
    _testCaseMethod(method),
    _name(name)
  {
  }
  /** calls TestFixture method.  setUp and tearDown methods are called by
   * its parent TestFixture (in its runTest method).
   * it is robust to unexpected exceptions (throw) */
  void runTest()
  {
   TestsListener::theInstance().testHasRun();
   TestsListener::theInstance().currentTestName(_name);
   try
   {
    (_parent->*_testCaseMethod)();
   }
   catch( std::exception& error )
   {
    TestsListener::theInstance().testHasThrown();
    TestsListener::theInstance().errorsLog()
     << "std::exception catched by MiniCppUnit: \n"
     << "what() : "
     << Assert::yellow() << error.what()
     << Assert::normal() << "\n";
   }
   catch ( TestFailedException& failure) //just for skiping current test case
   {
   }
   catch(...)
   {
    TestsListener::theInstance().testHasThrown();
    TestsListener::theInstance().errorsLog()
     << "non standard exception catched by MiniCppUnit.\n";
   }
  }

  /** the TestFixture method hame */
  std::string name() const
  {
   return _name;
  }

 private:
  ConcreteFixture* _parent;
  TestCaseMethod _testCaseMethod;
  std::string _name;
 };
    //------------- end of class TestCase ----------------------------

private:
 
 typedef std::list<Test*> TestCases;
 TestCases _testCases;
 std::string _name;

 void testsList() const
 {
  std::cout << "\n+ " << name() << "\n";
  for( TestCases::const_iterator it=_testCases.begin();
   it!=_testCases.end(); it++ )
   std::cout << "  - "<< (*it)->name() << "\n";
 }
 

public:
 virtual void setUp() {}
 virtual void tearDown() {}

 std::string name() const
 {
  return _name;
 };

 TestFixture(const std::string& name="A text fixture") : _name(name)
 {
 }

 void afegeixCasDeTest(ConcreteFixture* parent, TestCaseMethod method, const char* name)
 {
  TestCase* casDeTest = new TestCase(parent, method, _name + "::" + name);
  _testCases.push_back( casDeTest );
 }
 /** calls each test after setUp and tearDown TestFixture methods */
 void runTest()
 {
  testsList();
  TestCases::iterator it;
  for( it=_testCases.begin(); it!=_testCases.end(); it++)
  {
   setUp();
   (*it)->runTest();
   tearDown();
  }
 }
 /** TestCase that wrapps TestFixture methods are dynamically created and owned by
  * the TestFixture. So here we clean it up*/
 ~TestFixture()
 { 
  TestCases::iterator it;
  for( it =_testCases.begin(); it!=_testCases.end(); it++)
   delete (*it);
 }
};


/**
 * This class is aimed to hold a creator method for each concrete TestFixture
 */
class TestFixtureFactory
{
private:
 /** Well behaved singleton:
  *  Don't allow instantiation apart from theInstance(), so private ctr.*/
 TestFixtureFactory()
 {
 }
 typedef Test* (*FixtureCreator)();
 std::list<FixtureCreator> _creators;
public:
 /** Accessor to the (static) singleton instance */
 static TestFixtureFactory& theInstance()
 {
  static TestFixtureFactory theFactory;
  return theFactory;
 }
 bool runTests()
 {
  std::list<FixtureCreator>::iterator it;
  for(it=_creators.begin(); it!=_creators.end(); it++)
  { 
   FixtureCreator creator = *it;
   Test* test = creator();
   test->runTest();
   delete test;
  }
  std::string errors =  TestsListener::theInstance().logString();
  if (errors!="") std::cout << "\n\nError Details:\n" << errors;
  std::cout << TestsListener::theInstance().summary();

  return TestsListener::theInstance().allTestsPassed(); 
 }
 void addFixtureCreator(FixtureCreator creator)
 {
  _creators.push_back( creator );
 }
 
};

/**
 * Macro a usar despr閟 de cada classe de test
 */
#define REGISTER_FIXTURE( ConcreteTestFixture ) \
\
Test* Creador##ConcreteTestFixture() { return new ConcreteTestFixture; } \
\
class Registrador##ConcreteTestFixture \
{ \
public: \
 Registrador##ConcreteTestFixture() \
 { \
  TestFixtureFactory::theInstance().addFixtureCreator( \
    Creador##ConcreteTestFixture); \
 } \
}; \
static Registrador##ConcreteTestFixture estatic##ConcreteTestFixture;


/**
 * Assert macros to use in test methods. An assert is a test condition
 * we want to check.
 */
#define ASSERT_EQUALS( expected, result) \
 Assert::assertEquals( expected, result, __FILE__, __LINE__ );

#define ASSERT_EQUALS_EPSILON( expected, result, epsilon) \
 Assert::assertEqualsEpsilon( expected, result, epsilon, __FILE__, __LINE__ );

#define ASSERT( exp ) \
 Assert::assertTrue(#exp, exp, __FILE__, __LINE__);

#define ASSERT_MESSAGE( exp, message ) \
 Assert::assertTrueMissatge(#exp, exp, message, __FILE__, __LINE__);

#define FAIL( why ) \
 Assert::fail(#why, __FILE__, __LINE__);

/**
 * Macros that allows to write the  constructor of the concrete TestFixture.
 * What the constructor does is agregate a wrapper for each test case (method)
 * As easy to write as this:
 *
 * @code
 * class MyTests : public TestFixture<MyTests>
 * {
 *  public:
 *   TEST_FIXTURE( MyTests )
 * {
 *  TEST_CASE( test );
 *  // etc
 * }
 * void test()
 * {
 *  ASSERT_EQUALS( 4, 1+1+2 );
 * }
 * @endcode
 */

#define TEST_FIXTURE( ConcreteFixture ) \
 ConcreteFixture() : TestFixture<ConcreteFixture>( #ConcreteFixture )

#define TEST_CASE( methodName ) \
 afegeixCasDeTest( this, &ConcreteFixture::methodName, #methodName );

 


       
#endif  // MiniCppUnit_hxx
/*
 * Copyright (c) 2003-2004  Pau Arum?& David Garc韆
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include "MiniCppUnit.hxx"

#include <cmath>

#ifdef _MSC_VER
#include <float.h>
namespace std
{
  template <typename T>
  inline bool isnan(T x) {
  return _isnan(x) != 0;
 }
 template <typename T>
 inline bool isinf(T x) {
  return _finite(x) == 0;
 }
}
#endif

TestsListener& TestsListener::theInstance()
{
 static TestsListener instancia;
 return instancia;
}

std::stringstream& TestsListener::errorsLog()
{
 if (_currentTestName)
  _log << "\n" << errmsgTag_nameOfTest() << (*_currentTestName) << "\n";
 return _log;
}

std::string TestsListener::logString()
{
 std::string aRetornar = _log.str();
 _log.str("");
 return aRetornar;
}
void TestsListener::currentTestName( std::string& name)
{
 _currentTestName = &name;
}
void TestsListener::testHasRun()
{
 std::cout << ".";
 theInstance()._executed++;
}
void TestsListener::testHasFailed()
{
 std::cout << "F";
 theInstance()._failed++;
 throw TestFailedException();
}
void TestsListener::testHasThrown()
{
 std::cout << "E";
 theInstance()._exceptions++;
}
std::string TestsListener::summary()
{
 std::ostringstream os;
 os << "\nSummary:\n"
  << Assert::bold() << "\tExecuted Tests:         "
  << _executed << Assert::normal() << std::endl
  << Assert::green() << "\tPassed Tests:           "
  << (_executed-_failed-_exceptions)
  << Assert::normal() << std::endl;
 if (_failed > 0)
 {
  os  << Assert::red() << "\tFailed Tests:           "
   << _failed << Assert::normal() << std::endl;
 }
 if (_exceptions > 0)
 {
  os  << Assert::yellow() << "\tUnexpected exceptions:  "
   << _exceptions << Assert::normal() << std::endl;
 }
 os << std::endl;
 return os.str();
}
bool TestsListener::allTestsPassed()
{
 return !theInstance()._exceptions && !theInstance()._failed;
}

 

void Assert::assertTrue(char* strExpression, bool expression,
  const char* file, int linia)
{
 if (!expression)
 {
  TestsListener::theInstance().errorsLog() << "\n"
   << errmsgTag_testFailedIn() << file
   << errmsgTag_inLine() << linia << "\n"
   << errmsgTag_failedExpression()
   << bold() << strExpression << normal() << "\n";
  TestsListener::theInstance().testHasFailed();
 }
}

void Assert::assertTrueMissatge(char* strExpression, bool expression,
  const char* missatge, const char* file, int linia)
{
 if (!expression)
 {
  TestsListener::theInstance().errorsLog() << "\n"
   << errmsgTag_testFailedIn() << file
   << errmsgTag_inLine() << linia << "\n"
   << errmsgTag_failedExpression()
   << bold() << strExpression << "\n"
   << missatge<< normal() << "\n";
  TestsListener::theInstance().testHasFailed();
 }
}

 

void Assert::assertEquals( const char * expected, const char * result,
 const char* file, int linia )
{
 assertEquals(std::string(expected), std::string(result),
  file, linia);

}
void Assert::assertEquals( const bool& expected, const bool& result,
 const char* file, int linia )
{
 assertEquals(
  (expected?"true":"false"),
  (result?"true":"false"),
  file, linia);
}

// floating point numbers comparisons taken
// from c/c++ users journal. dec 04 pag 10
bool isNaN(double x)
{
 bool b1 = (x < 0.0);
 bool b2 = (x >= 0.0);
 return !(b1 || b2);
}

double scaledEpsilon(const double& expected, const double& fuzzyEpsilon )
{
 const double aa = fabs(expected)+1;
 return (std::isinf(aa))? fuzzyEpsilon: fuzzyEpsilon * aa;
}
bool fuzzyEquals(double expected, double result, double fuzzyEpsilon)
{
 return (expected==result) || ( fabs(expected-result) <= scaledEpsilon(expected, fuzzyEpsilon) );
}
void Assert::assertEquals( const double& expected, const double& result,
  const char* file, int linia )

 const double fuzzyEpsilon = 0.000001;
 assertEqualsEpsilon( expected, result, fuzzyEpsilon, file, linia );
}

void Assert::assertEquals( const float& expected, const float& result,
  const char* file, int linia )
{
 assertEquals((double)expected, (double)result, file, linia);
}
void Assert::assertEquals( const long double& expected, const long double& result,
  const char* file, int linia )
{
 assertEquals((double)expected, (double)result, file, linia);
}
void Assert::assertEqualsEpsilon( const double& expected, const double& result, const double& epsilon,
  const char* file, int linia )
{
 if (isNaN(expected) && isNaN(result) ) return;
 if (!isNaN(expected) && !isNaN(result) && fuzzyEquals(expected, result, epsilon) ) return;

 TestsListener::theInstance().errorsLog()
   << errmsgTag_testFailedIn() << file
   << errmsgTag_inLine() << linia << "\n"
   << errmsgTag_expected()
   << bold() << expected << normal() << " "
   << errmsgTag_butWas()
   << bold() << result << normal() << "\n";
 TestsListener::theInstance().testHasFailed();
}

int Assert::notEqualIndex( const std::string & one, const std::string & other )
{
 int end = std::min(one.length(), other.length());
 for ( int index = 0; index < end; index++ )
  if (one[index] != other[index] )
   return index;
 return end;
}


/**
 * we overload the assert with string doing colored diffs
 *
 * MS Visual6 doesn't allow string by reference :-(
 */
void Assert::assertEquals( const std::string expected, const std::string result,
 const char* file, int linia )
{
 if(expected == result)
  return;
 
 int indexDiferent = notEqualIndex(expected, result);
 TestsListener::theInstance().errorsLog()
  << file << ", linia: " << linia << "\n"
  << errmsgTag_expected() << "\n" << blue()
  << expected.substr(0,indexDiferent)
  << green() << expected.substr(indexDiferent)
  << normal() << "\n"
  << errmsgTag_butWas() << blue() << "\n"
  << result.substr(0,indexDiferent)
  << red() << result.substr(indexDiferent)
  << normal() << std::endl;

 TestsListener::theInstance().testHasFailed();
}
void Assert::fail(const char* motiu, const char* file, int linia)
{
 TestsListener::theInstance().errorsLog() <<
  file << errmsgTag_inLine() << linia << "\n" <<
  "Reason: " << motiu << "\n";

 TestsListener::theInstance().testHasFailed();
}

 

组织简介 | 联系我们 |   Copyright 2002 ®  UML软件工程组织 京ICP备10020922号

京公海网安备110108001071号