C++에 google test를 테스트 프로젝트에 적용해보기
테스트 구조는 알았고, 사내에 테스트를 적용을 마쳤다.
이해한 내용을 정리해서 테스트 프로젝트를 하나 작성하고,
이 아이를 "단위 테스트가 없던 프로젝트" 에서 "단위 테스트가 포함된 프로젝트" 로 바꾸는 과정을 살펴보자
CentOS 7
google-test
단위 테스트가 없는 상태의 프로젝트와 단위 테스트를 포함한 프로젝트를 각각 첨부한다.
코드는 직접 내려받아서 make 하고 make test 를 통해 테스트 바이너리까지 빌드할 수 있도록 만들어두었다
github에서 확인하기
https://github.com/BearMett/UnitTestTester
GitHub - BearMett/UnitTestTester: 프로젝트의 테스트 코드 적용기
프로젝트의 테스트 코드 적용기. Contribute to BearMett/UnitTestTester development by creating an account on GitHub.
github.com
파일로 내려받기 (tar xf ~)
아래는 테스트 코드가 없는 프로젝트 구조이다.
sum.h
왼쪽 값과 오른쪽 값을 설정하고 수행하면 결과값을 계산하는 클래스이다.
왼쪽 값과 오른쪽 값은 각각 bool 로 설정 여부를 상태로 가진다.
run 기능은 왼쪽 혹은 오른쪽 값이 설정된 적 없이 run이 수행되면 연산하지 않고 false를 반환
양쪽 값이 설정 된 상태로 run을 수행하면 멤버 변수 value_result 에 연산 결과를 저장
전체 코드의 가장 큰 차이는 테스트용 코드, google test 라이브러리 겠지만 그 변화를 바로 체감할 수 있는것은 Makefile 이다, 우선 기본 프로젝트의 Makefile이다.
TARGET_EXEC := sum_runner
BUILD_DIR := ./bin
OBJ_DIR := ./obj
SRC_DIRS := ./src
INC_DIRS := ./include
#빌드 관련 디렉토리 설정
INC_FLAGS := $(addprefix -I,$(INC_DIRS))
#$INC_DIRS에의 라인마다 접두 -I를 붙여서 include 대상 디렉토리로 설정 (-I./include)
LIB_DIRS :=
LDFALGS :=
#library를 사용하는 경우 포함, 현재 필요 없음
CPPFLAGS := $(INC_FLAGS) -MMD -MP
#dependancy (.d) 파일을 생성하기 위한 플래그, 동시에 이전에 만든 $INC_FLAGS를 조립한다
CXX = g++
CXXFLAGS = -g
#(디버그 플래그)
SRC := src/main.cpp \
src/sum.cpp
#목적 파일을 생성해야 하는 빌드 대상 소스 목록
OBJS = $(addprefix $(OBJ_DIR)/,$(SRC:.cpp=.o))
#목적 파일 생성시, 빌드 대상 소스.cpp -> obj/빌드 대상 소스.o
DEPS = $(OBJS:.o=.d)
#목적 파일 생성 후, 목적 파일 이름을 obj/빌드 대상 소스.o -> obj/빌드 대상 소스.d
all: $(BUILD_DIR)/$(TARGET_EXEC)
#빌드의 최종 목적인 실행 파일
$(OBJS): $(OBJ_DIR)/%.o: %.cpp
$(CXX) -c $(CXXFLAGS) $(INC_FLAGS) $< -MD -o $@ $(LDFALGS)
#목적 파일을 생성하려면 cpp 파일들이 필요
$(BUILD_DIR)/$(TARGET_EXEC): $(OBJS)
$(CXX) $(OBJS) $(INC_FLAGS) -o $@ $(LDFALGS)
#최종 실행 파일을 생성하려면 o 파일들이 필요
.PHONY: clean
#clean 은 목적이 없으므로 최신을 확인할 필요 없음
clean:
rm -f $(OBJS) $(BUILD_DIR)/$(TARGET_EXEC) $(DEPS)
#생성한 목적 파일(o), 의존 파일(d), 바이너리를 모두 제거
-include $(DEPS)
#생성한 의존 파일을 할 때 참고
이제 테스트 코드가 포함된 프로젝트의 디렉토리 변화를 보자
이 변화를 받아들인 Makefile을 살펴보자, 추가된 부분만 있다.
TEST_DIR := ./test
#프로젝트에 test 디렉토리를 확인할 수 있다.
TEST_INC_DIR := $(INC_DIRS) $(TEST_DIR)/include
TEST_INC_FLAGS := $(addprefix -I,$(TEST_INC_DIR))
TEST_CC := $(TEST_DIR)/sum_test.cc
#테스트 코드 목록
#파일이 늘어나면 구분자를 쓴다
#아래는 예시
NO_USE_CC := $(TEST_DIR)/sum_test.cc \
$(TEST_DIR)/min_test.cc \
$(TEST_DIR)/max_test.cc
#테스트에 포함할 소스는 이렇게 추가하면 된다
TEST_CC_OBJ := $(TEST_CC:.cc=.o)
TEST_CC_DEPS := $(TEST_CC_OBJ:.o=.d)
#여기부터는 원래 테스트 해야 할 목적 파일이 들어간다
TEST_SRC := src/sum.cpp
#이 목록은 원래 테스트 해야 할 소스 파일을 넣는다
TEST_OBJ := $(TEST_SRC:.cpp=.o)
TEST_DEPS = $(TEST_OBJ:.o=.d)
TEST_LIB_DIR := $(TEST_DIR)/lib/
TEST_LIB := -lgtest -lpthread
TEST_TARGET_EXEC := sum_test_run
#테스트에만 필요한 라이브러리인 gtest를 정해주고
#gtest를 빌드할때 당시 pthread 를 포함했으므로 pthread 라이브러리도 포함
test: $(TEST_DIR)/$(TEST_TARGET_EXEC)
#make test 명령에 대한 정의, 테스트 바이너리를 목적으로 한다
#아래는 조립단계
$(TEST_DIR)/$(TEST_TARGET_EXEC): $(TEST_OBJ) $(TEST_CC_OBJ)
$(CXX) $(TEST_CC_OBJ) $(TEST_OBJ) $(TEST_INC_FLAGS) -L$(TEST_LIB_DIR) $(TEST_LIB) $(LDFALGS) -o $@
$(TEST_OBJ): $(TEST_SRC)
$(CXX) -c -g $(CXXFLAGS) $(TEST_INC_FLAGS) $< -MD -o $@ $(LDFALGS)
$(TEST_CC_OBJ): $(TEST_CC)
$(CXX) -c -g $(CXXFLAGS) $(TEST_INC_FLAGS) $< -MD -o $@ $(LDFALGS)
테스트 코드가 포함된 프로젝트를 빌드하고 실행해보는 모습이다.
make test 시점에 마지막 g++ 명령을 주목해보면 google test를 빌드하기 위한 정적 라이브러리 파일과 헤더 파일을 가져오는 모습과 원본 목적 파일을 가져오는 모습을 확인할 수 있다.
테스트에 사용한 코드는 아래와 같다.
생성하자마자 아무 값도 설정하지 않았을 때 실패하기를 기대하는 테스트와
정상적으로 왼쪽에 5, 오른쪽에 3을 놓고 덧셈을 했을 때 맞는 값이 나오길 기대하는 테스트 두종류이다.
include "gtest/gtest.h"
#include "sum.h"
TEST(sum_worker_init_false__Test, sum_worker_test)
{
sum_worker sum_worker_tester;
EXPECT_FALSE(sum_worker_tester.run());
EXPECT_EQ(0, sum_worker_tester.getValue());
}
TEST(sum_worker_sum_test, sum_worker_test)
{
sum_worker sum_worker_tester;
sum_worker_tester.setLeft(5);
sum_worker_tester.setRight(3);
EXPECT_TRUE(sum_worker_tester.run());
EXPECT_EQ(8, sum_worker_tester.getValue());
}
int main(int argc, char **argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
다음은 google test에서 사용할 수 있는 EXPECT 목록이다
EXPECT_ANY_THROW (구문)
구문 내에 어떤 예외라도 던지는(throw)지 확인
구문이라고 표시된 경우 대괄호를 사용해도 표현할 수 있음
EXPECT_ANY_THROW({
int a = 1;
if(a == 1)
throw std::runtime_error("not 1");
})
EXPECT_DEATH
EXPECT_DEATH_IF_SUPPORTED
프로그램 수행 중 SIGSEGV과 같이 시그널로 죽어버리는 상황이 발생하면 통과
EXPECT_DEBUG_DEATH
프로그램 수행 중 DEBUG 빌드 시 동작하는 ASSERT 로 종료되는 상황이 발생하면 통과
EXPECT_EXIT
프로그램 수행 중 정상 종료(exit) 하면 통과
EXPECT_FALSE(상태)
EXPECT_TRUE(상태)
EXPECT 하는 상태가 동일하면 통과
비교 형제들의 사용법은 간단하다.
bash 조건문과 비슷한 문구들
원하는 조건에 맞추어 대상을 쓰면 된다.
E, EQ = Equal
GT = Grater than
LT = Less than
GE = Grater than or EQUAL
LE = Less than or EQUAL
NE = Not Equal
EXPECT_DOUBLE_EQ (8 바이트 실수1 = 8 바이트 실수2)
EXPECT_FLOAT_EQ (4 바이트 실수1 = 4 바이트 실수2)
EXPECT_EQ (정수 1 = 정수 2)
EXPECT_GE (정수 1 >= 정수 2)
EXPECT_GT (정수 1 > 정수 2)
EXPECT_LE (정수 1 <= 정수 2)
EXPECT_LT (정수 1 < 정수 2)
EXPECT_NE (정수 1 != 정수 2)
EXPECT_NEAR(실수1, 실수2, 오차 범위)
두 실수가 허용하는 오차 범위 안쪽으로 있는지 검사
아래와 같이 사용하면 1.2까지만 검사하므로 테스트를 통과한다.
예시: EXPECT_NEAR(1.23, 1.24, 0.0f);
EXPECT_NO_FATAL_FAILURE (구문)
구문을 진행하는 동안 프로그램이 죽지 않으면 통과
EXPECT_NO_THROW (구문)
구문을 진행하는 동안 예외를 던지지 않으면 통과