게임처럼 요구사항이 수시로 변하는 프로그램에서 유닛테스트를 적용하기란 쉽지가 않다.
개인적으로는 데이터 의존성 테스트나 누락여부를 체크하는 부분에 사용하는게 적당하다고 생각한다.
최대한 일반화 할 수 있는 것들을 케이스로 만들어 적용하면 데이터 스크립팅과 관련된 이슈들은 QA에 올린 다음 문제를 발견하고 원인을 파악하는 수고들을 덜 수 있다.
유닛 테스트 익스플로러가 지원되는 컴파일러를 사용 중이라면 문제가 없지만 그렇지 않은 경우 가볍게 사용할 수 있는 테스트 코드를 추가한다.
#pragma once
#if defined(_PRJTYPE_GAMESERVER)
#include <vector> // for vector
#include <string> // for string
#include <stdexcept> // for exception
#include <sstream> // for stringstream
#include <cassert> // for assert macro
#include <ctime> // for time-functions
#endif
namespace cppunit
{
/**
@brief 테스트 수행시 걸린 시간을 측정하기 위해 사용되는 간단한 Util 클래스.
start(), stop()을 반복적으로 호출할 수 있으며 전체 시간이 계속 누적된다. 다시 초기화를 하고자
한다면 clear() 함수를 사용하여야 한다. 총 수행 시간은 records()함수로 얻어지며 초 단위이다.
*/
class CTimer
{
public :
CTimer() : m_total(0.0), m_run(false)
{
}
void start()
{
m_stime = ::clock() ;
m_run = true ;
}
void stop()
{
if ( false == m_run )
return ;
double diff ;
m_etime = ::clock() ;
m_run = false ;
diff = static_cast<double>(m_etime-m_stime) ;
m_total += diff/CLOCKS_PER_SEC ;
assert ( m_total >= 0.0 ) ;
}
void clear()
{
m_total = 0.0 ;
m_run = false ;
}
double records() const
{
return m_total ;
}
private :
double m_total ;
clock_t m_stime ;
clock_t m_etime ;
volatile bool m_run ;
} ;
/**
@brief POD(Plain Old Data) 타입의 코드의 위치 정보를 담고 있는 클래스
*/
class CContext
{
public :
CContext() : m_msgs(), m_file(), m_func(), m_info(), m_line(0)
{
}
CContext(const std::string& msgs, const std::string& file,
const std::string& func, const std::string& info, long line)
: m_msgs(msgs), m_file(file), m_func(func), m_info(info), m_line(line)
{
}
public :
std::string m_msgs ;
std::string m_file ;
std::string m_func ;
std::string m_info ;
long m_line ;
} ;
/**
@brief 테스트에서 사용되는 assert 계열 함수 실패시 발생하는 예외 객체
*/
class CFailureException : public std::exception
{
public :
CFailureException() : m_context()
{
}
CFailureException(const CContext& context) : m_context(context)
{
}
~CFailureException() throw()
{
}
/// 에러 메세지 반환
const char* what() const throw()
{
return m_context.m_msgs.c_str() ;
}
/// 현재 설정된 Context 정보를 반환한다.
const CContext& context() const throw()
{
return m_context ;
}
private :
CContext m_context ; // 위치 정보
} ;
/**
@brief 테스트 함수 오브젝트의 인터페이스
*/
class CMethod
{
public :
virtual ~CMethod() { }
virtual bool execute(int& output_iterator, const int iCancelIterator) const = 0 ; // 등록된 메소드를 실행한다.
virtual CMethod* clone() const = 0 ; // 현재 객체를 복사한다.(deep copy)
virtual const char* name() const = 0 ; // 현재 등록된 메소드의 이름을 반환한다.
} ;
/**
@brief 테스트 함수 호출을 대행한다. command 패턴
*/
template < class T, class TestMethod >
class CMethodHolder : public CMethod
{
public :
CMethodHolder(const std::string& name, T& object, const TestMethod& testMethod)
: m_name(name), m_object(object), m_method(testMethod)
{
}
CMethodHolder(T& object, const TestMethod& testMethod)
: m_name(""), m_object(object), m_method(testMethod)
{
}
CMethodHolder(const CMethodHolder& holder)
: m_name(holder.m_name), m_object(holder.m_object), m_method(holder.m_method)
{
}
/// 실제 함수를 호출
/// Command 패턴의 execute() 역할을 한다.
bool execute(int& output_iterator, const int iCancelIterator) const override
{
return (m_object.*m_method)(output_iterator, iCancelIterator) ;
}
/// 함수 이름을 반환한다.
const char* name() const throw() override
{
return m_name.c_str() ;
}
CMethod* clone() const override
{
return new CMethodHolder(*this) ;
}
private :
std::string m_name ; // 테스트 메서드 이름 ( 메시지 출력을 위해 사용 )
T& m_object ; // 테스트 메서드를 소유한 실제 객체
TestMethod m_method ; // 테스트 메서드를 Wrapping 한 메서드 객체
} ;
/**
@brief 테스트의 결과를 처리하고 수집
테스트 객체 트리거
*/
class CTestResult
{
private :
void addSuccess(const CContext& ctxt) { m_success.push_back(ctxt) ; }
void addFailure(const CContext& ctxt) { m_failure.push_back(ctxt) ; }
void addError (const CContext& ctxt) { m_error.push_back(ctxt) ; }
public :
// Helper Method
int totalRunCount() const { return (successCount() + errorCount() + failureCount()) ; }
int successCount() const { return static_cast<int>(m_success.size()); }
int failureCount() const { return static_cast<int>(m_failure.size()); }
int errorCount() const { return static_cast<int>(m_error.size()) ; }
/// 현재 테스트가 모두 성공적이었는지를 체크한다.
bool wasSuccessful() const
{
if ( 0 == errorCount() && 0 == failureCount() )
return true ;
return false ;
}
/// 테스트 결과 초기화
void clear()
{
m_timer.clear() ;
m_success.clear() ;
m_failure.clear() ;
m_error.clear() ;
}
/// 테스트 구동 함수. 하나의 메서드에 대해서 구동을 하게 된다.
bool run(const CMethod& method)
{
// 테스트 함수 내부 for구문 처리용
bool bResult = false;
int iIterator = 0;
int iCancelIterator = -1;
m_timer.start() ;
do {
try
{
// 넘겨받은 테스트 함수를 호출한다.
bResult = method.execute(iIterator, iCancelIterator) ;
CContext c("OK","",method.name(),"",0) ;
addSuccess(c) ;
}
catch(CFailureException& e)
{
addFailure(e.context()) ;
}
catch(std::exception& e)
{
CContext c(e.what(),"",method.name(),"",0) ;
addError(c) ;
}
catch(...)
{
CContext c("unexpected exception is occured","",method.name(),"",0) ;
addError(c) ;
}
iCancelIterator = iIterator;
} while (false == bResult);
m_timer.stop() ;
return true ;
}
/// 파일 포인터에 테스트 결과를 출력해주는 함수
friend inline void WriteResult(const CTestResult& test, FILE* fp);
/// 테스트 결과를 trace하는 함수
friend inline void TraceResult(const CTestResult& test);
private :
CTimer m_timer ; // 시간 측정용 객체
std::vector<CContext> m_success ; // 성공한 테스트 정보 객체
std::vector<CContext> m_failure ; // 실패한 테스트 정보 객체
std::vector<CContext> m_error ; // 에러 테스트 정보 객체
} ;
/**
@brief 테스트 객체의 인터페이스
*/
class CTest
{
public :
virtual ~CTest() { }
virtual void run(CTestResult& result) = 0 ;
virtual int countTestCase() const { return 1 ; }
/// TestCase 에서 구현이 되어야 auto_ptr 생성 객체에서
/// 가상 함수를 호출할 수 있다.
virtual CTest* clone() const = 0 ;
} ;
/**
@brief 하나의 테스트를 의미
*/
#define _append(function) methodAppend(#function, *this, function)
class CTestCase : public CTest
{
protected :
CTestCase() { }
CTestCase(const CTestCase& t)
{
MethodList::const_iterator cursor ;
for ( cursor=t.m_methods.begin() ; cursor!=t.m_methods.end() ; ++cursor )
{
const CMethod* mem_fn = *cursor ;
if ( mem_fn )
m_methods.push_back(mem_fn->clone()) ;
}
}
virtual ~CTestCase()
{
MethodList::iterator cursor ;
for( cursor=m_methods.begin() ; cursor!=m_methods.end() ; ++cursor )
{
CMethod* mem_fn = *cursor ;
delete mem_fn ;
}
m_methods.clear() ;
}
template < class T, class TestMethod >
void methodAppend(const std::string& name, T& object, const TestMethod& method)
{
CMethod* mem_fn = new CMethodHolder<T,TestMethod>(name, object, method) ;
m_methods.push_back(mem_fn) ;
}
public :
/// 클래스 초기화 함수
virtual void setupBeforeClass() { } ;
/// 함수 셋업
virtual void setup() {} ;
/// 함수 테스트 후
virtual void tearDown() { } ;
/// 케이스 구동 종료
virtual void tearDownAfterClass() { } ;
// 구동 메소드
void run(CTestResult& control) override
{
setupBeforeClass() ;
MethodList::iterator cursor ;
for ( cursor=m_methods.begin() ; cursor!=m_methods.end() ; ++cursor )
{
CMethod* mem_fn = *cursor ;
setup() ;
control.run(*mem_fn) ;
tearDown() ;
}
tearDownAfterClass() ;
}
public :
typedef std::vector<CMethod*> MethodList ;
private :
MethodList m_methods ;
} ;
/**
@brief 여러 개의 테스트 또는 테스트 집합들을 묶어내어 하나의 테스트 집합을 만드는 클래스
Composite 패턴을 이용하여 Tree 자료구조 형태로 테스트들을 묶어낸다.
*/
class CTestSuite : public CTest
{
public :
CTestSuite() { }
CTestSuite(const CTestSuite& t)
{
TestList::const_iterator cursor ;
for ( cursor = t.m_tests.begin() ; cursor != t.m_tests.end() ; ++cursor )
{
const CTest* test = *cursor ;
m_tests.push_back(test->clone()) ;
}
}
virtual ~CTestSuite()
{
TestList::iterator cursor ;
for ( cursor = m_tests.begin() ; cursor != m_tests.end() ; ++cursor )
{
CTest* test = *cursor ;
delete test ;
}
m_tests.clear() ;
}
void addTest(std::auto_ptr<CTest> test)
{
m_tests.push_back(test->clone()) ;
// 파라미터로 넘겨온 test 객체는 자동으로 삭제된다.
}
int countTestCase() const override
{
int count = 0 ;
TestList::const_iterator cursor ;
for ( cursor = m_tests.begin() ; cursor != m_tests.end() ; ++cursor )
{
const CTest* test = *cursor ;
count += test->countTestCase() ;
}
return count ;
}
void run(CTestResult& control) override
{
TestList::iterator cursor ;
for ( cursor = m_tests.begin() ; cursor != m_tests.end() ; ++cursor )
{
CTest* test = *cursor ;
test->run(control) ;
}
}
CTest* clone() const override
{
return new CTestSuite(*this) ;
}
public :
typedef std::vector<CTest*> TestList ;
private :
TestList m_tests ;
} ;
/**
@brief 테스트 그룹을 구동하는 클래스
*/
class CTestRunner
{
public :
static void run(std::auto_ptr<CTest> test, FILE* fp)
{
CTestResult result;
test->run(result);
WriteResult(result, fp);
}
static void run(std::auto_ptr<CTest> test)
{
CTestResult result;
test->run(result);
TraceResult(result);
}
} ;
/*
아래 클래스들은 테스트 메서드 내부에서 사용하는 ASSERT 함수를 Wrapping 한 클래스들로
사용자 편의성을 위해 실제 사용은 매크로를 이용한다. 삽입시 ASSERT 객체로 전환되어 입력된다.
*/
class Asserts
{
public :
Asserts(const CContext& context) : m_ctxt(context)
{
}
protected :
CContext m_ctxt ;
} ;
/**
@brief (내부용) Exception 발생 매크로 : 관련 정보들을 Context에 저장 후 throw 한다.
*/
#define THROW_FAILURE_EXCEPTION(msg, info) \
do { \
m_ctxt.m_msgs += " " ; \
m_ctxt.m_msgs += (msg) ; \
m_ctxt.m_info += (info) ; \
throw CFailureException(m_ctxt) ; \
}while(0)
/**
@brief (내부용) Asserts 클래스에 포함된 operator 메서드는 대부분 특정한 패턴을 보이거나 반복되는 구조이기
때문에 Macro로 작성하여 사용한다.
*/
#define OP_P2(_TYPE, _P1, _P2, _COND) \
void operator()(_TYPE _P1, _TYPE _P2) \
{ \
operator()("No Messages", _P1, _P2) ; \
} \
void operator()(const std::string& message, _TYPE _P1, _TYPE _P2) \
{ \
if ( true == (_COND) ) return ; \
std::stringstream ss ; \
ss << "[" << #_TYPE << "] :: " ; \
ss << "[ LHS : " << _P1 << " ] " ; \
ss << "[ RHS : " << _P2 << " ]" ; \
THROW_FAILURE_EXCEPTION((message), ss.str()) ; \
}
#define TMPL_OP_P2(_P1, _P2, _COND) \
template <typename _TYPE> \
void operator()(_TYPE _P1, _TYPE _P2) \
{ \
if ( true == (_COND) ) return ; \
THROW_FAILURE_EXCEPTION("No Message", "[Object Type]"); \
} \
template <typename _TYPE> \
void operator()(const std::string& message, _TYPE _P1, _TYPE _P2) \
{ \
if ( true == (_COND) ) return ; \
THROW_FAILURE_EXCEPTION(message, "[Object Type]"); \
}
// __FUNCTION__ 매크로
#if defined(_WIN32) || defined(_WIN64)
#define __FUNC_NAME__ __FUNCTION__
#else
#define __FUNC_NAME__ __func__
#endif
/**
@brief assertEquals Obejct
*/
#define assertEquals AssertEquals(__FILE__,__FUNC_NAME__,__LINE__)
class AssertEquals : public Asserts
{
public :
AssertEquals(const char* file, const char* func, int line)
: Asserts(CContext("[assertEquals]", file, func, "", line))
{
}
TMPL_OP_P2(p1, p2, (p1==p2))
OP_P2(int , p1, p2, (p1==p2))
OP_P2(short, p1, p2, (p1==p2))
OP_P2(char , p1, p2, (p1==p2))
OP_P2(bool, p1, p2, (p1==p2))
OP_P2(int* , p1, p2, (p1==p2))
OP_P2(void*, p1, p2, (p1==p2))
OP_P2(const int* , p1, p2, (p1==p2))
OP_P2(const short*, p1, p2, (p1==p2))
OP_P2(const std::string&, p1, p2, (p1==p2))
// for double
void operator()(double p1, double p2, double delta=0.0000001)
{
operator()("No Messages.", p1, p2, delta) ;
}
void operator()(const std::string& msg, double p1, double p2, double delta=0.0000001)
{
double value = p1 - p2 ;
if ( (-1*delta) < value && value < delta )
return ;
std::stringstream ss ;
ss << "[double] :: " ;
ss << "[ LHS : " << p1 << " ] " ;
ss << "[ RHS : " << p2 << " ]" ;
THROW_FAILURE_EXCEPTION((msg), ss.str()) ;
}
} ;
/**
@brief not equals object
*/
#define assertNotEquals AssertNotEquals(__FILE__,__FUNC_NAME__,__LINE__)
class AssertNotEquals : public Asserts
{
public :
AssertNotEquals(const char* file, const char* func, int line)
: Asserts(CContext("[assertNotEquals]", file, func, "", line))
{
}
TMPL_OP_P2(p1, p2, (p1!=p2))
OP_P2(int , p1, p2, (p1!=p2))
OP_P2(short, p1, p2, (p1!=p2))
OP_P2(char , p1, p2, (p1!=p2))
OP_P2(bool, p1, p2, (p1!=p2))
OP_P2(int* , p1, p2, (p1!=p2))
OP_P2(void*, p1, p2, (p1!=p2))
OP_P2(const int* , p1, p2, (p1!=p2))
OP_P2(const short*, p1, p2, (p1!=p2))
OP_P2(const std::string&, p1, p2, (p1!=p2))
} ;
/**
@brief Fail Object
*/
#define fail AssertFail(__FILE__,__FUNC_NAME__,__LINE__)
class AssertFail : public Asserts
{
public :
AssertFail(const char* file, const char* func, int line)
: Asserts(CContext("[fail]", file, func, "", line))
{
}
void operator()()
{
operator()("No Messages.") ;
}
void operator()(const std::string& msg)
{
THROW_FAILURE_EXCEPTION(msg, "always shows error-message with [fail] macros.") ;
}
} ;
/**
@brief AssertTrue Object
*/
#define assertTrue AssertTrue(__FILE__,__FUNC_NAME__,__LINE__)
class AssertTrue : public Asserts
{
public :
AssertTrue(const char* file, const char* func, int line)
: Asserts(CContext("[assertTrue]",file,func,"", line))
{
}
void operator()(bool task)
{
operator()("No Messages.", task) ;
}
void operator()(const std::string msg, bool task)
{
if ( true == task )
return ;
THROW_FAILURE_EXCEPTION(msg, "[Boolean expression] : passes when [true] statement") ;
}
} ;
/**
@brief AssertFalse Object
*/
#define assertFalse AssertFalse(__FILE__,__FUNC_NAME__,__LINE__)
class AssertFalse : public Asserts
{
public :
AssertFalse(const char* file, const char* func, int line)
: Asserts(CContext("[assertFalse]",file,func,"", line))
{
}
void operator()(bool task)
{
operator()("No Messages.", task) ;
}
void operator()(const std::string msg, bool task)
{
if ( false == task )
return ;
THROW_FAILURE_EXCEPTION(msg, "[Boolean expression] : passes when [false] statement") ;
}
} ;
/**
@brief 결과를 텍스트 파일에 출력한다.
*/
inline void WriteResult(const CTestResult& test, FILE* fp)
{
if (NULL == fp) return;
if (fseek(fp, 0, SEEK_END) != 0) {
fclose(fp);
return;
}
fprintf(fp, "\r\n");
fprintf(fp, "================================================================================\r\n");
if (true == test.wasSuccessful()) {
fprintf(fp, "Result : Succeed\r\n");
}
else {
fprintf(fp, "Result : Failed\r\n");
}
fprintf(fp, "--------------------------------------------------------------------------------\r\n");
fprintf(fp, "- Total Test Count : %d\r\n", test.totalRunCount());
fprintf(fp, " - Success Count : %d\r\n", test.successCount());
fprintf(fp, " - Failure Count : %d\r\n", test.failureCount());
fprintf(fp, " - Error Count : %d\r\n", test.errorCount());
fprintf(fp, " - Success Rates : ");
if ( true == test.wasSuccessful() )
fprintf(fp, "100 %%\r\n");
else if ( 0 == test.totalRunCount() )
fprintf(fp, "N/A\r\n");
else
{
double rate ;
rate = static_cast<double>(test.successCount()) ;
rate /= test.totalRunCount() ;
rate *= 100 ;
rate += 0.5f; // 소수 첫째자리 반올림
fprintf(fp, "%d%%\r\n", static_cast<int>(rate));
}
fprintf(fp, " - Total Times : %g seconds \r\n", test.m_timer.records());
fprintf(fp, "--------------------------------------------------------------------------------\r\n");
if ( false == test.wasSuccessful() )
{
if ( false == test.m_failure.empty() )
{
fprintf(fp, "\r\n\r\n");
fprintf(fp, "================================================================================\r\n");
fprintf(fp, "Failure Info (%d/%d)\r\n", test.failureCount(), test.totalRunCount());
fprintf(fp, "--------------------------------------------------------------------------------\r\n");
std::vector<CContext>::const_iterator iter ;
int idx = 0 ;
for ( iter=test.m_failure.begin() ; iter!=test.m_failure.end() ; ++iter)
{
const CContext& info = *iter ;
fprintf(fp, " [%3d] code : %s() in %s (%d)\r\n", ++idx, info.m_func.c_str(), info.m_file.c_str(), info.m_line);
fprintf(fp, " msgs : %s\r\n", info.m_msgs.c_str());
fprintf(fp, " info : %s\r\n\r\n", info.m_info.c_str());
}
fprintf(fp, "--------------------------------------------------------------------------------\r\n");
}
if ( false == test.m_error.empty() )
{
fprintf(fp, "\r\n\r\n");
fprintf(fp, "================================================================================\r\n");
fprintf(fp, "Error Info (%d/%d)\r\n", test.errorCount(), test.totalRunCount());
fprintf(fp, "--------------------------------------------------------------------------------\r\n");
std::vector<CContext>::const_iterator iter ;
int idx = 0 ;
for ( iter=test.m_error.begin() ; iter!=test.m_error.end() ; ++iter)
{
const CContext& info = *iter ;
fprintf(fp, " [%3d] code : %s() in %s (%d)\r\n", ++idx, info.m_func.c_str(), info.m_file.c_str(), info.m_line);
fprintf(fp, " msgs : %s\r\n", info.m_msgs.c_str());
fprintf(fp, " info : %s\r\n\r\n", info.m_info.c_str());
}
fprintf(fp, "--------------------------------------------------------------------------------\r\n");
}
}
else
{
fprintf(fp, "\r\n\r\n");
fprintf(fp, "================================================================================\r\n");
fprintf(fp, "Success Info (%d/%d)\r\n", test.successCount(), test.totalRunCount());
fprintf(fp, "--------------------------------------------------------------------------------\r\n");
std::vector<CContext>::const_iterator iter ;
int idx = 0 ;
for ( iter=test.m_success.begin() ; iter!=test.m_success.end() ; ++iter)
{
const CContext& info = *iter ;
fprintf(fp, " [%3d] function : %s\r\n", ++idx, info.m_func.c_str());
}
fprintf(fp, "--------------------------------------------------------------------------------\r\n");
}
}
inline void TraceResult(const CTestResult& test)
{
TRACE("\n");
TRACE("================================================================================\n");
if ( true == test.wasSuccessful() ) {
TRACE("Result : Succeed\n");
}
else {
TRACE("Result : Failed\n");
}
TRACE("--------------------------------------------------------------------------------\n");
TRACE("- Total Test Count : %d\n", test.totalRunCount());
TRACE(" - Success Count : %d\n", test.successCount());
TRACE(" - Failure Count : %d\n", test.failureCount());
TRACE(" - Error Count : %d\n", test.errorCount());
TRACE(" - Success Rates : ");
if ( true == test.wasSuccessful() ) {
TRACE("100 %%\n") ;
} else if ( 0 == test.totalRunCount() ) {
TRACE("N/A\n");
} else {
double rate ;
rate = static_cast<double>(test.successCount()) ;
rate /= test.totalRunCount() ;
rate *= 100 ;
rate += 0.5f; // 소수 첫째자리 반올림
TRACE("%d%%\n", static_cast<int>(rate));
}
TRACE(" - Total Times : %g seconds \n", test.m_timer.records());
TRACE("--------------------------------------------------------------------------------\n");
if ( false == test.wasSuccessful() )
{
if ( false == test.m_failure.empty() )
{
TRACE("\n\n");
TRACE("================================================================================\n");
TRACE("Failure Info (%d/%d)\n", test.failureCount(), test.totalRunCount());
TRACE("--------------------------------------------------------------------------------\n");
std::vector<CContext>::const_iterator iter ;
int idx = 0 ;
for ( iter=test.m_failure.begin() ; iter!=test.m_failure.end() ; ++iter)
{
const CContext& info = *iter ;
TRACE(" [%3d] code : %s() in %s (%d)\n", ++idx, info.m_func.c_str(), info.m_file.c_str(), info.m_line);
TRACE(" msgs : %s\n", info.m_msgs.c_str());
TRACE(" info : %s\n\n", info.m_info.c_str());
}
TRACE("--------------------------------------------------------------------------------\n");
}
if ( false == test.m_error.empty() )
{
TRACE("\n\n");
TRACE("================================================================================\n");
TRACE("Error Info (%d/%d)\n", test.errorCount(), test.totalRunCount());
TRACE("--------------------------------------------------------------------------------\n");
std::vector<CContext>::const_iterator iter ;
int idx = 0 ;
for ( iter=test.m_error.begin() ; iter!=test.m_error.end() ; ++iter)
{
const CContext& info = *iter ;
TRACE(" [%3d] code : %s() in %s (%d)\n", ++idx, info.m_func.c_str(), info.m_file.c_str(), info.m_line);
TRACE(" msgs : %s\n", info.m_msgs.c_str());
TRACE(" info : %s\n\n", info.m_info.c_str());
}
TRACE("--------------------------------------------------------------------------------\n");
}
}
else
{
TRACE("\n\n");
TRACE("================================================================================\n");
TRACE("Success Info (%d/%d)\n", test.successCount(), test.totalRunCount());
TRACE("--------------------------------------------------------------------------------\n");
std::vector<CContext>::const_iterator iter ;
int idx = 0 ;
for ( iter=test.m_success.begin() ; iter!=test.m_success.end() ; ++iter)
{
const CContext& info = *iter ;
TRACE(" [%3d] function : %s\n", ++idx, info.m_func.c_str());
}
TRACE("--------------------------------------------------------------------------------\n");
}
}
} // namespace cppunit
유닛 테스트는 보통 테스트 assert를 사용해서 실패하면 결과를 기록하는데 테스트 케이스 구문에서 for 루프를 도는 경우 첫번째 assert 실패 시 해당 케이스는 테스트를 종료하게 된다.
그런 문제를 해결하기 위해 실패한 곳부터 다시 처리하도록 만든 부분이 do {} while 문으로 처리한 구문이다.
테스트 케이스는 CTestCase를 상속 받으면 된다.
아래는 사운드 테스트 케이스 예제..
케이스는 테스트 그룹에 포함된다.
RECENT COMMENT