https://www.jetbrains.com/help/clion/unit-testing-tutorial.html#basics
※ 본 포스팅은 위의 글을 번역한 것입니다.
1. 유닛 테스트의 장점
1. 코드를 모듈화 해준다.
- 단위 테스트를 수행하기 위해서는 코드가 모듈화 되어야 하기 때문에, 단위 테스트를 수행하기 위해서라도 코드를 모듈화하게(혹은 테스트하기 쉽게)(Code Testability) 설계를 하게 된다
2. (테스트) 회귀를 피해준다
- 단위 테스트는 작업의 진전이 있을 때 마다 반복적으로 수행을 한다. 따라서, 계속 적으로 작업이 옳은 방향으로 나아갈 수 있게 도와준다.
3. 코드를 문서화 해준다.
- 테스트 코드를 읽는 것만으로도 원본 코드가 어떻게 동작하는지에 대한 많은 정보를 준다. 따라서, 단위 테스트를 암묵적인 도큐먼트(Implicit Document)로 활용할 수 있다.
싱글 단위 테스트는 특정 기능을 검사하는 방법이며 Pass/Fail 기준이 존재한다. 아래와 같은 형태를 가진다.
Test (TestGroupName, TestName) {
1 - setup block
2 - running the under-test functionality
3 - checking the results (assertions block)
}
단위 테스트의 좋은 예시는 아래와 같다.
- 클래스의 생성자, 연산자를 포함하여 공개적으로 노출된 모든 함수에 대한 테스트 수행
- 코드가 수행되는 모든 경로를 다루며, 사소하면서도 엣지 케이스를 모두 체크한다. 부정확한 데이터 입력에 대해서도 체크한다.
- 각각의 테스트는 독립적이며, 다른 테스트가 수행되는데 영향을 미쳐서는 안된다.
- 테스트의 실행 순서에 따라 결과가 다르게 나와서는 안된다.
논리적으로 연결되거나 같은 데이터를 사용하는 테스트는 하나로 묶는 것이 바람직하다. 다른 테스트 케이스를 이용해서 같은 기능을 수행하는 경우도 묶어준다. Fixture 클래스는 다양한 테스트에 사용될 공유 자원을 관리하는데 도움을 준다. 따라서, 코드 중복을 피하게 해준다(테스트 만을 담당하는 하나의 클래스를 두어 코드 중복을 피한다는 개념으로 본인은 이해하였다. 밑에 예시가 나온다.)
2. 프레임 워크 선택
물론 프레임 워크에 의존하지 않고 main()문을 새로 구성하여 단위 테스트를 수행할 수 있다. 하지만, 프린트 문을 만들어야 하는 등 부차적인 작업이 많다. 단위 테스트 프레임워크는 자동화 수행 측면 뿐만 아니라 여러 면에서 이득이 있다.
- ASSERT(실패하면 프로그램 종료), EXPECT/CHECK(실패해도 프로그램 종료하지 않음) 기능을 제공한다.
- 프레임워크를 사용하면 공통 기능 혹은 공유 데이터 별로 그룹화된 테스트의 하위 집합을 쉽게 만들고 실행할 수 있다.
- 맞춤형 메시지를 출력할 수 있다.
- 출력 결과를 XML 형태로 내보내어 연속적인 통합 테스트 환경을 이어나갈 수 있다. ex) Jenkins
2-1 Google Test
Assertions 문
TEST (SquareTest /*test suite name*/, PosZeroNeg /*test name*/) {
EXPECT_EQ (9.0, (3.0*2.0)); // fail, test continues
ASSERT_EQ (0.0, (0.0)); // success
ASSERT_EQ (9, (3)*(-3.0)); // fail, test interrupts
ASSERT_EQ (-9, (-3)*(-3.0));// not executed due to the previous assert
}
Fixtures 클래스
class myTestFixture: public ::testing::test {
public:
myTestFixture( ) {
// initialization;
// can also be done in SetUp()
}
void SetUp( ) {
// initialization or some code to run before each test
}
void TearDown( ) {
// code to run after each test;
// can be used instead of a destructor,
// but exceptions can be handled in this function only
}
~myTestFixture( ) {
//resources cleanup, no exceptions allowed
}
// shared user data
};
설치 (두가지 방법)
1) 코드를 다운한다. 프로젝트에 포함한다. git 프로젝트를 진행한다면 git submodule에 같이 포함할 수 있다.
2) 두번째 방법으로는 CMAKE를 이용하는 방법이 있다(link)
2-2) Catch2
- 헤더 온리 테스트 프로그램이다.
- 아래 예시와 같이 테스트가 가능하다.
- Catch2의 REQUIRE문이 Google Test의 ASSERTION문 역할을 하고, CHECK가 GoogleTest의 EXPECT 역할을 하는 것으로 보인다.
- #define CATCH_CONFIG_MAIN 가 main()문을 제공한다고 나와 있다. 따라서, 하나의 테스트 프로그램에서만 포함 시켜야 할 것이다.
#define CATCH_CONFIG_MAIN // provides main(); this line is required in only one .cpp file
#include "catch.hpp"
int theAnswer() { return 6*9; } // function to be tested
TEST_CASE( "Life, the universe and everything", "[42][theAnswer]" ) {
REQUIRE(theAnswer() == 42);
}
TEST_CASE("{자유 형식의 테스트 이름 기입. 다만, Unique 해야 한다}", [name][tags]) {
}
예시) 출처
AbsoluteDateTest.cpp 파일
#define CATCH_CONFIG_MAIN <-- 하나의 cpp 테스트 파일에서만 포함해 준다.
#include "catch.hpp" <-- 반드시 포함해 준다.
#include "Gregorian.h" <-- 내가 작성한 코드에서 단위 테스트를 수행할 모듈을 포함한 헤더 파일
TEST_CASE("ExampleDate", "[AbsoluteDateTests]"){ // 12/2/2020 -> 737761
GregorianDate gregDate;
gregDate.SetMonth(12);
gregDate.SetDay(2);
gregDate.SetYear(2020);
CHECK(gregDate.getAbsoluteDate() == 737761);
}
TEST_CASE("IncorrectDate", "[AbsoluteDateTests]"){ // 12/0/2020 -> 0
GregorianDate gregDate;
gregDate.SetMonth(12);
gregDate.SetDay(0);
gregDate.SetYear(2020);
REQUIRE(gregDate.getAbsoluteDate() == 0);
}
ConverterTests.cpp 파일
// #define CATCH_CONFIG_MAIN <-- 이미 AbsoluteDateTest.cpp에서 포함했으므로 또 포함할 필요 없다.
#include "catch.hpp" <-- 반드시 포함해 준다.
#include "Gregorian.h" <-- 내가 작성한 코드에서 단위 테스트를 수행할 모듈을 포함한 헤더 파일1
#include "Julian.h" <-- 내가 작성한 코드에서 단위 테스트를 수행할 모듈을 포함한 헤더 파일2
TEST_CASE( "Check various dates", "[DateConverterTests]" ) {
GregorianDate gregDate;
JulianDate julDate;
SECTION( "Gregorian 1/1/1 -> Julian 1/3/1" ) {
gregDate.SetMonth(1);
gregDate.SetDay(1);
gregDate.SetYear(1);
int absGregDate = gregDate.getAbsoluteDate();
julDate.CalcJulianDate(absGregDate);
CHECK(julDate.getMonth() == 1);
CHECK(julDate.getDay() == 3);
CHECK(julDate.getYear() == 1);
}
SECTION( "Gregorian 3/1/100 -> Julian 3/2/100" ) {
gregDate.SetMonth(3);
gregDate.SetDay(1);
gregDate.SetYear(100);
int absGregDate = gregDate.getAbsoluteDate();
julDate.CalcJulianDate(absGregDate);
CHECK(julDate.getMonth() == 3);
CHECK(julDate.getDay() == 2);
CHECK(julDate.getYear() == 100);
}
SECTION( "Gregorian 3/1/300 -> Julian 2/29/300" ) {
gregDate.SetMonth(3);
gregDate.SetDay(1);
gregDate.SetYear(300);
int absGregDate = gregDate.getAbsoluteDate();
julDate.CalcJulianDate(absGregDate);
CHECK(julDate.getMonth() == 2);
CHECK(julDate.getDay() == 29);
CHECK(julDate.getYear() == 300);
}
SECTION( "Gregorian 3/1/900 -> Julian 2/25/900" ) {
gregDate.SetMonth(3);
gregDate.SetDay(1);
gregDate.SetYear(900);
int absGregDate = gregDate.getAbsoluteDate();
julDate.CalcJulianDate(absGregDate);
CHECK(julDate.getMonth() == 2);
CHECK(julDate.getDay() == 25);
CHECK(julDate.getYear() == 900);
}
}
결론
- Clion은 Google Test, Boost.Test, Catch2, Doctest를 지원한다. (본인은 위의 글에서 사용자가 많은 GoogleTest, Catch2 예시를 들었다). 간단한 예시를 사용해 보면서 본인에게 편리한 단위 테스트 프로그램을 사용하면 좋을 것이다.
- 어떤 프레임워크도 최신 프레임워크 코드로 자동 갱신해주는 방법은 없는 것 같다. 따라서, 최신 프레임워크 버전을 이용하고 싶다면 본인이 직접 갱신해줘야 할 것이다.
- 4 가지 프로그램을 Step by Step 으로 따라해보는 예시가 있다. 꼭 따라해 보고 본인에게 맞는 단위 테스트 프레임워크를 고르도록 한다.
- Clion에서는 커밋 하기 전에 자동으로 테스트를 돌리게끔 설정할 수도 있어, 누적해서 안정된 코드를 작성해 나갈 수 있다.
'프로그래밍 언어 > C, C++' 카테고리의 다른 글
fstream 처음 위치로 옮겨서 덮어쓰기 (0) | 2022.02.27 |
---|---|
C++ 파일 입출력 정리 (1) | 2022.02.14 |
void 포인터 용법 정리 (0) | 2021.08.26 |
Two pointer as argument (0) | 2021.08.02 |
서식 지정자 및 출력 포맷 (0) | 2021.02.04 |