들어가기에 앞서, 이 글은 테스트 자동화 환경을 구축하게 된 사고의 흐름과 소감을 다루나, 환경을 구축하기 위한 세세한 절차들을 모두 명시하지는 않았음을 밝힌다.

 

 1년간 게임 인재원에서 진행한 게임 개발 프로젝트들은 모두 약 2주~4주의 개발기간을 갖는 소규모 프로젝트들이었다. 이런 프로젝트들은 개발 도중 기능에 문제가 생길 때마다 센스와 직관으로 디버깅을 하면서 진행을 해도 큰 탈이 없었다. 하지만 게임인재원 5학기 일정을 시작하면서 1년동안 진행될 장기 프로젝트에서도 이런 식으로 개발을 할 수 있을지 의문이 들었다. 장기 프로젝트일수록 필요한 기능들을 산더미 파불고기처럼 많이 구현하게 될 텐데, 기능 중 하나가 고장이 났을 때 산더미같은 기반 코드 중 정확히 어디에서 문제가 터진 것인지 진단하는 것이 예삿일이 아닐 것 같았다.

소규모 프로젝트(좌)에서는 하나의 기능에 문제가 생겼을 때 해당 기능이 의존적인 기능들을 전수조사하는 것에 대한 부담이 적었으나, 대규모 프로젝트(우)의 경우 문제가 생긴 부분을 찾아내기 매우 힘들것이라는 예상이 들었다.

 이 문제에 대한 진지한 고민은 학기 초에 빌드머신을 세팅할 때부터 시작했다. 빌드머신용 컴퓨터에 젠킨스 서버를 설치하고 이를 깃허브 리포지터리와 연동해 마스터 브랜치가 업데이트될때마다 자동으로 빌드를 시도하게 만들었지만 이것만으로는 아쉽다는 생각이 들었다. 젠킨스는 프로젝트 빌드가 성공했을 때 해당 빌드를 성공적인 빌드로 마킹하는데, 프로젝트가 그저 컴파일가능하다는 것 하나만으로 프로젝트의 안정성이 보장될 수는 없다고 보았기 때문이다.

빌드머신에 젠킨스를 구축해 깃허브 저장소가 업데이트될때마다 자동빌드를 시킨 모습. 하지만 코드가 빌드가능하다는 것만으로 전체 프로젝트의 안정성을 보장할 수는 없겠다는 생각이 들었다.

 빌드머신의 절차를 어떻게 더 고도화시킬 수 없을까 고민을 하다가 대학을 다닐때 소프트웨어 테스팅에 대한 강의를 들은 기억이 났다. 개발중인 소프트웨어의 복잡성이 증가할수록 뭘 더 개발하는 것이 어려워질테니, 구현된 기능들을 반복적으로 검증할 수 있는 체계를 마련해야 지속가능한 개발을 꾀할 수 있다는 교수님의 말씀이 떠올랐다. 비주얼 스튜디오에서 테스트와 관련된 견본 솔루션을 찾아보니 과연 유닛 테스트들을 작성할 수 있는 솔루션이 있었고, 이를 우리 프로젝트에 적용하기로 했다.

비주얼 스튜디오에서 제공하는 테스트케이스 솔루션(좌)과 팀 프로젝트를 진행하면서 생성한 테스트케이스 목록(우)

 테스트 주도 개발 방식을 적용하면서 하나의 기능을 구현할 때마다 하나의 테스트 코드를 작성하기로 했다. 예를 들어 게임엔진에 물리엔진을 이식한 경우, 나는 중력과 물리 충돌이 잘 작동하는지 확인하기 위해 평면 위에 상자를 떨어뜨린 뒤 몇 초 후 상자의 y축 좌표를 확인하는 코드를 짰다.

가설을 세우고 그 가설에 맞는 유닛테스트를 실행한 모습, 시뮬레이션을 잠깐 실행한 후 Assert문이 실행되며, 내부 조건이 참이라면 프로그램은 정상코드로 종료한다.

 나는 이 테스트케이스들이 누적될수록 프로젝트의 안정성 또한 보장될 것이라 판단했고, 이걸 잘 이용하면 젠킨스의 빌드 검증절차의 부족한 점을 보완할 수 있을 것 같았다. 나는 빌드머신에서 빌드 직후 수록된 모든 테스트들을 돌려 테스트가 하나라도 실패하면 빌드를 실패로 띄우게 만들었다. 이를 통해 마스터 브랜치에 커밋이 들어올 때마다 해당 커밋이 기존에 잘 작동하던 동작들을 망가뜨리지 않는지 확인할 수 있었고, 마스터 브랜치의 안정적인 빌드 히스토리를 추적할 수 있었다.

빌드 후 batch 파일을 실행해 등록된 테스트들을 전부 실행하고, 테스트가 단 하나라도 실패하면 빌드 결과를 실패로 기록하도록 절차를 만들었다.

 테스트 케이스 코드 중 빌드 검증에 동원되지 않는 테스트코드들도 프로젝트에 넣을 수 있게 했다. 검증에 동원되지 않는 테스트라니, 팥없는 붕어빵이 아닌가 하는 생각이 들겠지만 사람의 눈으로 봐야 검증이 가능한 그래픽스 기능 테스트, 혹은 특정 기능을 시연하는 쇼케이스 코드와 같이 그저 아카이빙하고 싶은 실행코드들도 있을 수 있기 때문이다. 이런 테스트코드들은 프로젝트의 조각, 편린이라는 의미로 Snippet 코드라고 부르기로 했다. 빌드머신에서 실행되는 테스트용 batch 파일은 모든 테스트들을 실행하지만, 접두어로 Snippet이 붙은 테스트들은 무시하도록 만들었다.

  Debug Release
EditorUnitTests 디버그 버전 게임 + 툴
단위테스트 빌드
릴리즈 버전 게임 + 툴
단위테스트 빌드
UnitTests 디버그 버전 게임
단위 테스트 빌드
릴리즈 버전 게임
단위 테스트 빌드
GraohucsExe 그래픽스 디버깅 빌드 그래픽스 확인용 빌드
EditorExe 툴 디버깅 빌드 툴 배포 빌드
Exe 게임 디버깅 빌드 게임 출시 빌드

 

 우리 프로젝트의 빌드 설정은 위와 같이 총 10개로 나뉘었다. 빌드머신은 마스터 브랜치가 업데이트 될 때마다 이 중 GraphicsExe 빌드를 제외한 나머지 8개 빌드가 컴파일에 성공하고, EditorUnitTests, UnitTests에 해당하는 4가지 버전에서 실행되는 단위 테스트들을 모두 통과해야 해당 빌드를 안정적인 것으로 간주했다.

 

테스트케이스라고 해서 자동화 테스트의 대상이 되는 코드만 있는 것은 아니다. 사람의 눈으로 봐야 검증이 되는 테스트, 기능의 사용법을 알려주기 위한 데모용 코드, 기능의 정상 동작을 시연하는 쇼케이스 테스트들은 따로 아카이빙 되었다.

프로세스를 개선한 소감

 새로운 작업방식에 대한 팀원들의 반응은 모두 호평 일색이었다. 다들 '내가 커밋을 하면 갑자기 멀쩡한 프로젝트가 절단나진 않을까?' 하는 불안을 품고 있었는데 '테스트들이 모두 검증을 통과하면 안정적인 빌드로 간주한다.'는 원칙이 생기니까 팀원들이 마스터 브랜치에 커밋을 넣는 부담이 줄었다. 로컬 PC에서 자체적으로 테스트를 진행하고 커밋을 해도 되고, 그런 절차 없이 바로 커밋하더라도 빌드머신에 로그가 남아 어떤 커밋이 어떤 테스트를 실패하게 만들었는지 바로 추적할 수 있기 때문에 잘못된 커밋에 대한 부담이 덜했다.

 테스트용으로 한번 쓰고 버리던 코드들을 Snippet 코드로 따로 뺄 수 있는 것도 반응이 매우 좋았다. 과거의 테스트 환경을 그대로 남기고 언제든 실행할 수 있게 만든다는 것은 생각보다 큰 의미가 있었다. 스니펫 코드들은 기능의 사용법을 설명하는 메뉴얼 역할을 했고, 구현된 기능을 시연하는 보고자 역할을 했다. 이전에 마구잡이로 개발할 때는 임시로 개발해 놓은 테스트용 코드들을 다음 테스트를 위해 지울때마다 뭔가 아깝다는 생각이 들었는데, 이제는 그럴 때마다 스니펫 코드로 저장한다. 이런 코드들은 묵혀두면 언젠가는 반드시 유용하게 쓰일 일이 생기더라.

 내가 처음 빌드머신을 세팅하려고 한 동기는 지속적 통합 / 지속적 배포( Continus Integration / Continuous Delivery )가 개발 프로세스에 미치는 영향을 온몸으로 체감하고 싶었던 이유가 컸다. 빌드머신을 세팅하고 나니 이 중 CD는 몰라도 CI, 지속적 통합이 왜 중요한지는 뼈저리게 느끼게 되었다. 작업물의 지속적 통합에 대한 대책을 마련하지 않은 타 팀의 경우 '지금 내가 브랜치를 합쳐도 되느냐?', '내용을 커밋했을 때 문제가 생길까봐 겁난다.' 등 서로의 작업물들을 통합하는 것에 대해 상당한 부담감을 호소하는 경우가 잦았다. 부담감이 큰 만큼 서로의 작업이 통합되는 빈도가 잦지 않았고, 어쩌다 타인의 작업물을 합친다 한들 구현된 기능에 대한 품질검증이 이루어지지 않기 때문에 불안한 상태에서 작업을 계속해야 했다. 잘 동작하던 기능에 갑자기 문제가 발견될 경우 이 문제가 어느 커밋으로부터 비롯된 건지 특정하는 것도 힘들었다. 이는 팀원간 불신이 쌓이기 쉬운 환경이었고, 이런 환경 위에서 효율적인 협업을 기대하기는 힘들 것이었다. 지속적 통합의 가치는 지속적 통합이 부재한 경우 생기는 협업의 한계에서 명백히 드러났다.

 

아쉬웠던 점

 만약 게임 엔진에서 그래픽스 출력 기능을 끄고 테스트할 수 있었다면 좋았을 것이다. 젠킨스 서버를 돌리는 프로세스에는 그래픽스 출력 장치에 대한 접근 권한이 없어 이 문제를 우회하는 편법을 써야 했고, 또 필요 없는 기능에 컴퓨터 자원이 소모되는 것이 아쉬웠다.

 또 원래 내가 생각했던 프로세스는 마스터 브랜치에 푸시가 들어올 때마다 빌드를 시도하는 게 아니라 풀 리퀘스트가 올라올 때마다 빌드머신에서 풀 리퀘스트를 반영해 테스트를 진행하고 문제가 없으면 마스터 브랜치와 병합하는 것이었다. 이렇게 절차를 만들었다면 마스터 브랜치에는 항상 안정적인 버전만 남길 수 있었을 텐데 아쉽다.

원래 의도했던 프로세스, 만약 이대로 동작했다면 마스터 브랜치에는 항상 안정적인 버전만 남길 수 있었을 것이다.

 다음은 내가 테스트 자동화 환경을 구축하기 위해 사용한 기술들이다.

 

- Jenkins : 빌드머신 구축하는데 필요

- Ngrok : 빌드머신의 IP주소와 포트번호를 도메인 네임 서버에 등록해야 github webhook을 연동할 수 있기 때문에 필요

- psexec  : 빌드머신은 System 계정으로 구동되는데, System 계정은 그래픽 출력 장치에 대한 권한이 없음. User계정에게 원격으로 CMD 명령어를 실행시켜 테스트를 진행시키고 싶을 때 필요

- Batch 파일 작성 : 일괄 테스트를 실행하면 한 프로세스에서 테스트코드들이 순차적으로 실행됨, 한 프로세스당 한 테스트코드를 실행시키면서 일괄테스트를 시키고 싶을 때, 어떤 테스트코드들은 빌드 검증에 관여하지 않게 만들고 싶을 때, 아무튼 자기 입맛에 맞게 임의로 테스트를 진행하고 싶다면 batch 파일 작성법을 배워야 함.

'자체엔진 Yunuty > 개발일지' 카테고리의 다른 글

그래픽스 엔진 설계변경  (0) 2023.06.21
그래픽스 엔진 인터페이스 구상  (0) 2023.06.19
개발일지 - 견적 내기  (0) 2023.06.16