Google C++ Testing Framework를 사용해야 하는 이유
이 프레임워크를 사용해야 하는 이유는 여러 가지이다. 이 섹션에서는 그 중 몇 가지 이유에 대해 설명한다.
일부 카테고리의 테스트에는 특정 실행 동안에만 발생하는 악성 메모리 문제점이
있다. Google의 테스트 프레임워크에는 그러한 상황을 효율적으로 처리할 수 있는 뛰어난
지원 기능이 있다. Google 프레임워크를 사용하여 동일한 테스트를 수천 번 반복할 수
있다. 첫 번째 오류가 발생할 때 디버거가 자동으로 호출된다. 게다가 이 모든 작업은 다음과
같이 명령행에서 단 두 개의 스위치만으로 수행된다. --gtest_repeat=1000 --gtest_break_on_failure.
다른 여러 테스트 프레임워크와는 반대로 Google의 테스트 프레임워크에는 예외 처리를 사용하지 않는(일반적으로 성능 상의 이유로) 소프트웨어에 전개할 수 있는 내장 어설션이 있다. 따라서 소멸자에서 어설션을 안전하게 사용할 수 있다.
테스트는 간단하게 실행할 수 있다. 테스트 실행을 위해 별도의 실행자 클래스를 작성하거나
파생할 필요 없이 사전 정의된 RUN_ALL_TESTS 매크로를 호출하여
테스트를 실행할 수 있다. 이는 CppUnit 등의 프레임워크와는 매우 대조적이다.
다음과 같이 스위치를 전달하여 손쉽게 XML(Extensible Markup Language) 보고서를
작성할 수 있다. --gtest_output="xml:<file name>". CppUnit
및 CppTest 등의 프레임워크에서는 XML 출력을 생성하려면 반드시 추가 코드를 작성해야 한다.
간단한 제곱근 함수의 원형을 살펴보자(Listing 1 참조).
Listing 1. 제곱근 함수의 원형
double square-root (const double); |
음수의 경우 이 루틴은 -1을 리턴한다. 양수 및 음수
테스트를 모두 수행할 경우 유용하다. Listing 2에서는 해당
테스트 케이스를 보여 준다.
Listing 2. 제곱근 함수에 대한 유닛 테스트
#include "gtest/gtest.h"
TEST (SquareRootTest, PositiveNos) {
EXPECT_EQ (18.0, square-root (324.0));
EXPECT_EQ (25.4, square-root (645.16));
EXPECT_EQ (50.3321, square-root (2533.310224));
}
TEST (SquareRootTest, ZeroAndNegativeNos) {
ASSERT_EQ (0.0, square-root (0.0));
ASSERT_EQ (-1, square-root (-22.0));
}
|
Listing 2에서는 SquareRootTest라는 테스트 계층 구조를 작성한
다음 PositiveNos와 ZeroAndNegativeNos라는
두 개의 유닛 테스트를 해당 계층 구조에 추가한다. TEST는 이 계층 구조를
정의하기 위해 사용되는 gtest.h(다운로드한 소스에 포함되어 있음)에 정의된 사전 정의된 매크로이다. EXPECT_EQ
및 ASSERT_EQ도 매크로이며, 이 중 첫 번째 케이스 테스트는 오류가 발생하더라도
계속 실행되지만 두 번째 케이스 테스트는 실행이 중단된다. 그리고 0의 제곱근은 0이므로 더 이상 테스트를
수행할 필요가 없다. 바로 이 때문에 ZeroAndNegativeNos 테스트는 ASSERT_EQ만을
사용하지만 PositiveNos 테스트는 EXPECT_EQ를 사용하여
테스트를 중단하지 않은 채로 제곱근 함수가 실패한 케이스의 수를 알려 준다.
지금까지 첫 번째 기본 테스트를 작성했으므로 이제 실행할 차례이다. Listing 3은 테스트를 실행하는 기본 루틴 코드이다.
Listing 3. 제곱근 테스트 실행하기
#include "gtest/gtest.h"
TEST(SquareRootTest, PositiveNos) {
EXPECT_EQ (18.0, square-root (324.0));
EXPECT_EQ (25.4, square-root (645.16));
EXPECT_EQ (50.3321, square-root (2533.310224));
}
TEST (SquareRootTest, ZeroAndNegativeNos) {
ASSERT_EQ (0.0, square-root (0.0));
ASSERT_EQ (-1, square-root (-22.0));
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
|
이름으로 알 수 있듯이 ::testing::InitGoogleTest 메소드는
프레임워크를 초기화하며 RUN_ALL_TESTS 이전에 호출해야 한다. RUN_ALL_TESTS는
여러 번 호출할 경우 프레임워크의 일부 고급 기능과 충돌하여 지원되지 않을 수 있기 때문에
코드에서 한 번만 호출해야 한다. RUN_ALL_TESTS는 TEST
매크로를 사용하여 모든 테스트를 자동으로 감지하고 실행한다. 기본적으로 표준 출력에 결과가
인쇄된다. Listing 4에서는 출력을 보여 준다.
Listing 4. 제곱근 테스트를 실행한 출력
Running main() from user_main.cpp [==========] Running 2 tests from 1 test case. [----------] Global test environment set-up. [----------] 2 tests from SquareRootTest [ RUN ] SquareRootTest.PositiveNos ..\user_sqrt.cpp(6862): error: Value of: sqrt (2533.310224) Actual: 50.332 Expected: 50.3321 [ FAILED ] SquareRootTest.PositiveNos (9 ms) [ RUN ] SquareRootTest.ZeroAndNegativeNos [ OK ] SquareRootTest.ZeroAndNegativeNos (0 ms) [----------] 2 tests from SquareRootTest (0 ms total) [----------] Global test environment tear-down [==========] 2 tests from 1 test case ran. (10 ms total) [ PASSED ] 1 test. [ FAILED ] 1 test, listed below: [ FAILED ] SquareRootTest.PositiveNos 1 FAILED TEST |
Google C++ Testing Framework의 옵션
Listing 3에서 InitGoogleTest 함수는
테스트 인프라에 대한 인수를 사용한다. 이 섹션에서는 테스트 프레임워크에 대한 인수를 사용하여
수행할 수 있는 몇 가지 유용한 기능에 대해 설명한다.
명령행에서 --gtest_output="xml:report.xml"을 전달하여 출력을
XML 형식으로 출력할 수 있다. 물론 report.xml을 원하는 파일 이름으로
바꿀 수도 있다.
대부분의 경우에는 성공하지만 가끔씩 실패하는 테스트가 있다. 이는 일반적으로 메모리 손상과
관련된 문제이다. 테스트를 두 번 정도 실행하면 실패를 감지할 수 있는 확률이 높아진다. 명령행에서
--gtest_repeat=2 --gtest_break_on_failure를 전달하면 동일한 테스트가
두 번 반복된다. 테스트가 실패할 경우 디버거가 자동으로 호출된다.
모든 테스트를 항상 실행할 필요는 없다. 특히, 특정 모듈에만 영향을 주는 코드를
변경할 경우에는 더욱 그러하다. 이를 지원하기 위해 Google은 --gtest_filter=<test string>을
제공한다. 테스트 문자열의 형식은 콜론(:)으로 구분된 일련의 와일드카드 패턴이다. 예를 들어,
--gtest_filter=*는 모든 테스트를 실행하는 반면 --gtest_filter=SquareRoot*는
SquareRootTest 테스트만 실행한다. SquareRootTest의
양수 유닛 테스트만 실행하려면 --gtest_filter=SquareRootTest.*-SquareRootTest.Zero*를
사용한다. SquareRootTest.*는 SquareRootTest에 속한
모든 테스트를 의미하며 -SquareRootTest.Zero*는 Zero로 시작하는 이름을 가진
테스트를 실행하지 않는다는 의미이다.
Listing 5에서는 gtest_output, gtest_repeat
및 gtest_filter를 사용하여 SquareRootTest를 실행하는 예제를 보여 준다.
Listing 5.
gtest_output, gtest_repeat 및 gtest_filter를 사용하여 SquareRootTest 실행하기[arpan@tintin] ./test_executable --gtest_output="xml:report.xml" --gtest_repeat=2 -- gtest_filter=SquareRootTest.*-SquareRootTest.Zero* Repeating all tests (iteration 1) . . . Note: Google Test filter = SquareRootTest.*-SquareRootTest.Z* [==========] Running 1 test from 1 test case. [----------] Global test environment set-up. [----------] 1 test from SquareRootTest [ RUN ] SquareRootTest.PositiveNos ..\user_sqrt.cpp (6854): error: Value of: sqrt (2533.310224) Actual: 50.332 Expected: 50.3321 [ FAILED ] SquareRootTest.PositiveNos (2 ms) [----------] 1 test from SquareRootTest (2 ms total) [----------] Global test environment tear-down [==========] 1 test from 1 test case ran. (20 ms total) [ PASSED ] 0 tests. [ FAILED ] 1 test, listed below: [ FAILED ] SquareRootTest.PositiveNos 1 FAILED TEST Repeating all tests (iteration 2) . . . Note: Google Test filter = SquareRootTest.*-SquareRootTest.Z* [==========] Running 1 test from 1 test case. [----------] Global test environment set-up. [----------] 1 test from SquareRootTest [ RUN ] SquareRootTest.PositiveNos ..\user_sqrt.cpp (6854): error: Value of: sqrt (2533.310224) Actual: 50.332 Expected: 50.3321 [ FAILED ] SquareRootTest.PositiveNos (2 ms) [----------] 1 test from SquareRootTest (2 ms total) [----------] Global test environment tear-down [==========] 1 test from 1 test case ran. (20 ms total) [ PASSED ] 0 tests. [ FAILED ] 1 test, listed below: [ FAILED ] SquareRootTest.PositiveNos 1 FAILED TEST |
암호를 풀고 있다고 가정해 보자. 테스트를 임시로 비활성화할 수 있는가? 물론이다. DISABLE_
접두부를 논리적 테스트 이름이나 개별 유닛 테스트 이름에 추가하면 해당 테스트가 실행되지 않는다. Listing
6에서는 Listing 2의 PositiveNos를 비활성화하려는 경우에 수행해야 하는
작업을 보여 준다.
Listing 6. 임시로 테스트 비활성화하기
#include "gtest/gtest.h"
TEST (DISABLE_SquareRootTest, PositiveNos) {
EXPECT_EQ (18.0, square-root (324.0));
EXPECT_EQ (25.4, square-root (645.16));
EXPECT_EQ (50.3321, square-root (2533.310224));
}
OR
TEST (SquareRootTest, DISABLE_PositiveNos) {
EXPECT_EQ (18.0, square-root (324.0));
EXPECT_EQ (25.4, square-root (645.16));
EXPECT_EQ (50.3321, square-root (2533.310224));
}
|
Google 프레임워크는 비활성화된 테스트가 있을 경우 테스트 실행을 완료한 후 경고를 인쇄한다(Listing 7 참조).
Listing 7. 프레임워크에 비활성화된 테스트가 있음을 알리는 Google 경고
1 FAILED TEST YOU HAVE 1 DISABLED TEST |
비활성화된 테스트를 계속 실행하려면 명령행에서 -gtest_also_run_disabled_tests
옵션을 전달한다. Listing 8에서는 DISABLE_PositiveNos 테스트를
실행한 출력을 보여 준다.
Listing 8. 비활성화된 테스트를 실행할 수 있는 Google
[----------] 1 test from DISABLED_SquareRootTest [ RUN ] DISABLED_SquareRootTest.PositiveNos ..\user_sqrt.cpp(6854): error: Value of: square-root (2533.310224) Actual: 50.332 Expected: 50.3321 [ FAILED ] DISABLED_SquareRootTest.PositiveNos (2 ms) [----------] 1 test from DISABLED_SquareRootTest (2 ms total) [ FAILED ] 1 tests, listed below: [ FAILED ] SquareRootTest. PositiveNos |
Google 테스트 프레임워크에는 모든 사전 정의된 어설션이 포함되어 있다. ASSERT_와
EXPECT_로 시작하는 이름을 가진 두 종류의 어설션이 있다. ASSERT_*
변형은 어설션이 실패할 경우 프로그램 실행을 중단하지만 EXPECT_* 변형은 프로그램을 계속
실행한다. 어설션이 실패할 경우 두 변형 모두 파일 이름, 행 번호 및 사용자 정의할 수 있는 메시지를 인쇄한다. 간단한
어설션으로는 ASSERT_TRUE (condition)와 ASSERT_NE (val1, val2)가
있다. 전자는 항상 true인 조건을 기대하는 반면 후자는 일치하지 않는 두 값을 기대한다. 이러한 어설션은 사용자 정의
유형에서도 작동하지만 사용자가 해당 비교 연산자(==, !=, <= 등)를 오버로드해야 한다.
Google에서는 부동 소수점 비교를 위한 매크로를 제공한다(Listing 9 참조).
Listing 9. 부동 소수점 비교를 위한 매크로
ASSERT_FLOAT_EQ (expected, actual) ASSERT_DOUBLE_EQ (expected, actual) ASSERT_NEAR (expected, actual, absolute_range) EXPECT_FLOAT_EQ (expected, actual) EXPECT_DOUBLE_EQ (expected, actual) EXPECT_NEAR (expected, actual, absolute_range) |
부동 소수점 비교를 위해 별도의 매크로가 필요한 이유는 무엇인가? ASSERT_EQ가
작동하지 않는가? ASSERT_EQ 및 관련 매크로가 작동할 수도 있고 그렇지 않을 수도
있을 것이다. 따라서 특별히 부동 소수점 비교를 위한 매크로를 사용하는 것이 현명하다. 일반적으로 CPU 및 운영
환경에 따라 부동 소수점을 저장하는 방법이 다르기 때문에 예상 값과 실제 값 사이의 단순 비교는 작동하지 않는다. 예를
들어, ASSERT_FLOAT_EQ (2.00001, 2.000011)는 오류 없이 실행된다. 왜냐하면 Google에서는
결과가 소수점 이하 최대 네 자리까지 동일한 경우 오류가 발생하지 않기 때문이다. 더 높은 정밀도가 필요한 경우에는
ASSERT_NEAR (2.00001, 2.000011, 0.0000001)를 사용한다. 그러면 Listing
10과 같은 오류가 표시된다.
Listing 10.
ASSERT_NEAR의 오류 메시지Math.cc(68): error: The difference between 2.00001 and 2.000011 is 1e-006, which exceeds 0.0000001, where 2.00001 evaluates to 2.00001, 2.000011 evaluates to 2.00001, and 0.0000001 evaluates to 1e-007. |
Google C++ Testing Framework에는 데쓰 어설션이라고 하는 흥미로운 카테고리의 어설션(ASSERT_DEATH,
ASSERT_EXIT 등)이 있다. 이 유형의 어설션은 루틴의 입력 값이
올바르지 않을 경우 적절한 오류 메시지가 표시되는지 또는 프로세스가 적절한 종료 코드로
종료되는지 등을 확인하는 데 사용된다. 예를 들어, Listing 3에서는
square-root (-22.0)를 실행한 후 -1.0을
리턴하는 대신 리턴 상태 -1로 프로그램을 종료할 경우 오류 메시지가
수신되는 것이 좋다. Listing 11에서는 ASSERT_EXIT를
사용하여 그러한 시나리오를 확인한다.
Listing 11. Google의 프레임워크를 사용하여 데쓰 테스트 실행하기
#include "gtest/gtest.h"
double square-root (double num) {
if (num < 0.0) {
std::cerr << "Error: Negative Input\n";
exit(-1);
}
// Code for 0 and +ve numbers follow
}
TEST (SquareRootTest, ZeroAndNegativeNos) {
ASSERT_EQ (0.0, square-root (0.0));
ASSERT_EXIT (square-root (-22.0), ::testing::ExitedWithCode(-1), "Error:
Negative Input");
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
|
ASSERT_EXIT는 함수가 적절한 종료 코드(즉, exit
또는 _exit 루틴에 대한 인수)로 종료 중인지 확인한 후 따옴표 내의 문자열과
함수가 표준 오류에 인쇄한 내용을 비교한다. 오류 메시지는 std::cout가 아닌
std::cerr로 이동해야 한다. Listing 12에서는
ASSERT_DEATH 및 ASSERT_EXIT의 원형을 제공한다.
Listing 12. 데쓰 어설션의 원형
ASSERT_DEATH(statement, expected_message) ASSERT_EXIT(statement, predicate, expected_message) |
Google에서는 사전 정의된 ::testing::ExitedWithCode(exit_code) 조건부를
제공한다. 이 조건부의 결과는 프로그램이 조건부에 업급된 것과 동일한 exit_code로
종료된 경우에만 true이다. ASSERT_DEATH는 ASSERT_EXIT보다
단순하며, 표준 오류의 오류 메시지를 사용자가 기대하는 메시지와 비교한다.
일반적으로 유닛 테스트를 실행하기 전에 사용자 정의 초기화 작업을 수행한다. 예를 들어, 테스트의 시간/메모리 풋프린트를 측정하려면 해당 값을 측정할 위치에 테스트 관련 코드를 넣어야 한다. 바로 이 위치에 사용자 정의 테스트 요구를 설정하는 데 도움이 되는 픽스처가 사용된다. Listing 13에서는 픽스처 클래스의 모습을 보여 준다.
Listing 13. 테스트 픽스처 클래스
class myTestFixture1: public ::testing::test {
public:
myTestFixture1( ) {
// initialization code here
}
void SetUp( ) {
// code here will execute just before the test ensues
}
void TearDown( ) {
// code here will be called just after the test completes
// ok to through exceptions from here if need be
}
~myTestFixture1( ) {
// cleanup any pending stuff, but no exceptions allowed
}
// put in any custom data members that you need
};
|
픽스처 클래스는 gtest.h에 선언된 ::testing::test
클래스에서 파생된다. Listing 14는 픽스처 클래스를 사용하는 예제이다. 이 예제에서는
TEST 대신 TEST_F 매크로를 사용한다.
Listing 14. 픽스처 사용 예제
TEST_F (myTestFixture1, UnitTest1) {
.
}
TEST_F (myTestFixture1, UnitTest2) {
.
}
|
픽스처를 사용하려면 먼저 다음 몇 가지 사항을 이해해야 한다.
- 생성자 또는
SetUp메소드에서 자원을 초기화하거나 할당할 수 있다. 그 선택은 사용자에게 달려 있다. TearDown또는 소멸자 루틴에서 자원의 할당을 해제할 수 있다. 하지만 예외를 처리하려는 경우에는 소멸자에서 예외를 발생시킬 경우 정의되지 않은 동작이 발생하므로TearDown코드에서만 예외를 처리해야 한다.- Google 어설션 매크로는 향후 릴리스에서 지원될 플랫폼에서도 예외를 발생시킬 수 있다. 따라서 유지보수
향상을 위해
TearDown코드에서 어설션 매크로를 사용하는 것이 좋다. - 동일한 테스트 픽스처가 여러 테스트 간에 사용되지 않는다. 프레임워크에서는 모든 새 유닛 테스트에
대해 새로운 테스트 픽스처를 작성한다. 따라서 Listing 14를 보면
SetUp(철자 주의) 루틴이 두 번 호출된다. 왜냐하면 두 개의myFixture1오브젝트가 작성되기 때문이다.
이 기사에서는 Google C++ Testing Framework의 겉만 살펴보았을 뿐이다. Google 사이트에서 이 프레임워크에 대한 자세한 문서를 볼 수 있다. 고급 개발자의 경우 Boost 유닛 테스트 프레임워크 및 CppUnit 등의 오픈 회귀 프레임워크에 대한 다른 기사를 읽어보기를 권장한다. 아래의 참고 자료 섹션에서 추가 정보를 찾아볼 수 있다.
교육
-
Google TestPrimer를 읽고 Google C++ Testing Framework를 시작해 보자.
-
고급 Google C++ Testing Framework 주제를 보려면 Google TestAdvancedGuide를 살펴보자.
-
Google TestFAQ에서 Google C++ Testing Framework에 대한 팁과 자주 묻는 질문을 볼 수 있다.
-
"오픈 소스 C/C++ 유닛 테스트 도구, Part 1:
Boost 유닛 테스트 프레임워크 알아보기"(developerWorks, 2009년 12월)를 살펴보자.
-
"오픈 소스 C/C++ 유닛 테스트
도구, Part 2: CppUnit 알아보기"(developerWorks, 2010년 1월)를 살펴보자.
-
부동 소수점 비교에 대한 자세한 정보를 보려면 David Goldberg의 What
Every Computer Scientist Should Know About Floating-Point Arithmetic과 David Goldberg의 Comparing
floating point numbers를 읽어보자.
- 기술 서점에서
다양한 기술 주제와 관련된 서적을 살펴보자.
제품 및 기술 얻기
-
Google C++ Testing Framework 소프트웨어를 다운로드할 수 있다.
- IBM 제품 평가판을
다운로드하거나 IBM SOA Sandbox의
온라인 시험판을 살펴보고 DB2®,
Lotus®, Rational®, Tivoli® 및
WebSphere®.
토론
- developerWorks 포럼 & 블로그를 통해 developerWorks 커뮤니티에 참여하자.
- Twitter의 developerWorks 페이지를 살펴보자.
- My developerWorks 커뮤니티에 참여하자.
-
AIX 및 UNIX® 포럼에 참여하자.
- AIX Forum
- AIX Forum for developers
- Cluster Systems Management
- IBM Support Assistant Forum
- Performance Tools Forum
- PowerVM Forum
- 기타 AIX and UNIX Forums