ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • C++에 google test를 테스트 프로젝트에 적용해보기
    개발/C, C++ 2022. 8. 11. 12:03

    테스트 구조는 알았고, 사내에 테스트를 적용을 마쳤다.

     

    이해한 내용을 정리해서 테스트 프로젝트를 하나 작성하고,

    이 아이를 "단위 테스트가 없던 프로젝트" 에서 "단위 테스트가 포함된 프로젝트" 로 바꾸는 과정을 살펴보자

     

    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 ~)

    project_sum_no_test.tar
    0.01MB
    project_sum_with_test.tar
    2.13MB

    아래는 테스트 코드가 없는 프로젝트 구조이다.

     

     

    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 (구문)

    구문을 진행하는 동안 예외를 던지지 않으면 통과

     

Designed by Tistory.