-
C++에 google test를 테스트 프로젝트에 적용해보기개발/C, C++ 2022. 8. 11. 12:03
테스트 구조는 알았고, 사내에 테스트를 적용을 마쳤다.
이해한 내용을 정리해서 테스트 프로젝트를 하나 작성하고,
이 아이를 "단위 테스트가 없던 프로젝트" 에서 "단위 테스트가 포함된 프로젝트" 로 바꾸는 과정을 살펴보자
CentOS 7
google-test
단위 테스트가 없는 상태의 프로젝트와 단위 테스트를 포함한 프로젝트를 각각 첨부한다.
코드는 직접 내려받아서 make 하고 make test 를 통해 테스트 바이너리까지 빌드할 수 있도록 만들어두었다
github에서 확인하기
https://github.com/BearMett/UnitTestTester
파일로 내려받기 (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 (구문)
구문을 진행하는 동안 예외를 던지지 않으면 통과
'개발 > C, C++' 카테고리의 다른 글
error: expected initializer before ‘namespace’ 해결일기 (0) 2022.08.12 google test 로 C++ 단위 테스트 진행기 (0) 2022.05.08