단위 테스트부터 잘 작성하고 볼 일

삽질의 시작

코드 베이스가 커지면 변경이 미치는 범위를 가늠하기 힘들다. 팀 프로젝트를 하다 보면 작은 변경으로 발생한 사이드 이펙트를 자주 접한다. 많은 걸 신경 써야 하는 상황에서 수동으로 사이드 이펙트를 모두 찾아낸다는 건 알파고라면 가능할지도. 아쉽지만 우리는 알파고가 아닌 인간이고, 코드 구현 시점에 발견하지 못한 버그는 QA까지 가서야 덜미를 잡혀 돌아온다. 버그 발견 시점이 코드 구현 시점에서 멀어질수록 원인을 찾기 어렵다.

브라질의 축구 영웅 호나우두가 드리블하는 사진

 그래서 테스트 자동화에 열을 올렸다. 단위 테스트를 작성하자고 열심히 외쳤다. 하지만 몸에 배지 않아서인지 바쁜 일정에 치이면 나조차도 테스트부터 뒤로 미루기 일쑤였다. 어떻게 하면 자연스럽게 테스트를 작성할까? 프로세스가 있으면 어떨까?

그러다 스토리 주도 개발 방법론에 꽂혔다. 사용자 스토리를 기반으로 인수 테스트를 자동화한다. 이 방법론을 도입하면 테스트 작성을 개발 프로세스에 자연스럽게 녹일 수 있으리라. 책 속의 이야기는 무척이나 달콤했고, 나는 순진했다.

순간 당황한 분이 계실 테다. 단위 테스트도 뒷전이라는데 웬 스토리 주도 개발? 그렇다. 제대로 헛다리를 짚은 순간이다.

혹시나 단위 테스트와 기능 테스트의 차이가 알쏭달쏭한 누군가를 위해 책의 한꼭지를 인용한다. 마틴 파울러는 자신의 저서인 "리팩터링"에서 단위 테스트와 기능 테스트의 차이를 이렇게 설명했다.

단위 테스트의 목적은 프로그래밍 생산성 향상이다. 프로그래밍 생산성이 높아지면 부수적을 품질 보증 부서의 업무 효율도 향상된다. 단위 테스트는 매우 국소적이어서, 각 테스트 클래스는 하나의 패키지 안에서만 효력이 있다. 다른 패키지의 인터페이스를 테스트하지만, 그 외에 나머지 코드는 잘 돌아간다고 가정한다.

기능 테스트는 단위 테스트와 전혀 다르다. 기능 테스트의 목적은 소프트웨어 전반이 제대로 돌아가는지 확인하는 것이다. 기능 테스트는 고객에게 품질 보증만 할 뿐 프로그래머의 생산성과는 무관하다. 따라서 기능 테스트 코드는 별도의 버그 발견 전문 팀이 개발해야 한다. 버그 발견 전문 팀은 기능 테스트 작성에 강력한 도구와 기술을 동원한다.

기능 테스트는 대체로 시스템 전반을 최대한 블랙박스처럼 취급한다. 또한 기능 테스트는 GUI 기반 시스템에선 GUI를 통해 이뤄지고, 파일이나 데이터베이스 업데이트 프로그램에선 데이터가 특정 입력에 대해 어떻게 변하는지를 관찰하기만 한다.

이 내용에 따르면 단위 테스트와 기능 테스트는 테스트의 범위와 목적하는 바가 다르다. 내가 웹 프런트엔드의 E2E(End-To-End) 도구인 nightwatch.js를 이용하여  팀에 도입하려 했던 게 바로 기능 테스트 자동화였다.

열심히 예제 테스트를 작성해서 팀에 공유했지만 당시 동료들의 반응은 싸늘했다. 얘는 지금 바빠죽겠는데 뭘 더 하라는 거니. 버그를 빨리 찾아서 생산성을 향상시키자던 애가 갑자기 기능 테스트를 들고 왔다. 이게 헛다리가 아니면 뭐가 헛다리람.

어쨌든 지금 아는 것들을 그때는 몰랐으니, 그저 내 마음을 몰라주는 동료들이 서운했다(당시에). "스마트에디터3 오픈에 즈음하여"라는 글을 통해 당시의 심정을 살짝 엿볼 수 있다.

...(생략)

네이버로 이직한 후 스마트에디터 3 프로젝트를 시작하면서 의욕 충만하게 테스트를 작성하려 했다. 생각처럼 잘 되지 않았다. 이번에는 고민의 축을 "테스트를 작성해야 한다"에서 "왜 테스트를 작성하지 않을까?"로 옮겼다.

테스트 작성에 대한 팀 공통 기준이 없었고, 테스트가 개발 프로세스에 자연스레 녹아있지 않았다. 테스트 작성이 코딩과 하나로 묶인 작업이 아닌, 추가로 해야 하는 귀찮은 노동 취급을 받고 있었다. 이 문제를 해결하려면 프로세스 개선이 필요하다고 생각했고 사용자 스토리 작성부터 시작해서 기능 테스트 작성까지 이어지는 프로세스를 나름 팀에 적응할 수 있겠다 싶은 수준으로 추렸다.

...(생략)

적응하려면 상당한 연습과 시간이 필요한데 우리는 아직 너무 바쁘다. 결정적으로 "좋다"고 "강요"하기에는 내 경험이 너무 미천하다. 내가 느끼는 그대로를 상대에게 전달하여 설득하는 일은 언제나 힘들다.

...(생략)

헛다리를 짚었지만 당시에는 단위 테스트와 기능 테스트를 개발자가 모두 책임지는 게 가능하다고 믿었고, 할 수 있다는 걸 어떤 식으로든 증명해 보일 수만 있다면 모두 자연스럽게 나를 따라올 거라 확신했다. 지금의 거부감은 그저 무지에서 비롯된 것일 뿐. 아직은 낯설어서, 혹은 잘 몰라서 그런 거라 생각했다. 그럼 내가 알려줄테다.



삽질 끝에 얻은 결론

열심히 기능 테스트를 작성했고, 무지한 것은 나라는 사실을 깨닫기까지 걸린 시간은 불과 한 달. 시간이 지나면서 기능 테스트 작성이 버거워졌다. 언젠가부터는 슬며시 한 쪽 구석에 방치하더니, 그렇게 우리는 남이 되었다.

시간이 흘러 며칠 전에 테스트 코드를 정리하다가 버려진 기능 테스트 코드 조각들을 발견했다. 여러 가지 생각이 교차하다 아래에 기술할 몇 가지 이유로 미련 없이 기능 테스트를 모두 삭제했다. 이제 기능 테스트 자동화에 대한 나의 미련에 안녕을 고하련다.


테스트 수행 속도가 너무 느리다.

기능 테스트는 성격상 테스트 실행 속도가 느릴 수밖에 없다. 피드백이 늦다는 소리다. 테스트 실행이 느리다는 건 생산성 향상에 단위 테스트만큼 도움을 주지 못한다는 뜻. 자주 실행할 수 없기에 단위 테스트를 대체하기 어렵다. 그렇다면 테스트를 둘 다 작성하거나, 단위 테스트를 포기해야 한다는 소린데. 둘 중에 하나를 택하라면 생산성(과 단위 레벨의 품질)을 택하겠다.


웹 프런트엔드 E2E 테스팅 도구의 완성도가 떨어진다.

Nightwtch.jsProtractor든, 모두 셀레늄 기반이다. 아는 사람은 알겠지만 셀레늄의 IE 드라이버는 버그가 많다. 브라우저 파편화 이슈도 꽤나 성가시다.모던 브라우저라도 예외가 아니어서 크로스 브라우징 테스트를 하려면 일일이 확인해가며 테스트 코드를 작성해야 한다. 한 번은 테스트 케이스 하나 짜는데 3시간이나 걸린 적이 있었는데 기능 하나를 개발하는 기분이었다. 배보다 배꼽이 더 크다.

우리 조직이 만들고 있는 웹 에디터처럼 인터랙션이 복잡하게 엉켜있는 제품을 대상으로 수행하는 기능 테스트는, 테스트 자체를 신뢰할 수 없는 경우도 꽤 있었다. DOM 셀렉팅 중에 알 수 없는 오류가 발생해서 통과해야 하는 테스트가 실패하는 경우가 그렇다. 제멋대로 오락가락. 리포트를 신뢰할 수 없는 테스트라니. 떡 없는 떡볶이도 아니고 이게 뭐람.

당시에 내가 사용한 도구인 Nightwatch.js(v0.8.4 기준)도 결함이 많았다. Nightwatch.js가 아주 형편없다는 이야기는 아니다. 기능 테스트를 작성할 때 Nightwatch.js를 애용하는 Eric Elliott(JavaScript Testing: Unit vs Functional vs Integration Tests) 같은 분도 있다(정말 일까...?). 다만 내가 만들고 있는 제품을 테스트하기에는 부족했다. 버그가 있어 코드를 뜯어봐야 했던 경우가 자주 있었다. Nightwatch.js의 기본 테스트 러너는 명세를 1 단계로만 적을 수 있어 명세를 가독성 있게 작성하기 어렵다. mocha를 테스트 러너로 사용하면 describe, it 함수를 이용할 수 있지만, 병렬 테스팅을 제대로 수행하지 못한다. 테스트 작성해야 할 시간에 도구의 코드를 들여다보고 있어야 하다니. 덕분에 내부 구조도 살펴보고, 프로젝트에 기여하기도 했지만 내가 좀 바빠서 미안.


테스트가 주는 피드백의 질이 낮다.

리포트를 신뢰할 수 없는 것 자체로 이미 치명적이지만, 에러가 발생했을 때 돌려주는 피드백도 만족스럽지 않았다. 테스트 도중 오류가 발생했을 때 어디에서 어떤 오류가 발생했는지 정확히 알기 어렵다. 그냥 '테스트 시나리오를 수행하던 중에 어떤 문제가 발생했구나'정도를 감 잡을 수 있을 뿐이다. 심지어 이마저도 빠르게 받아보기 어렵다.


테스트 작성에 들어가는 비용이 너무 많다.

앞에 언급한 내용을 이유로 기능 테스트를 작성하고 유지하려면 많은 비용을 지불해야 한다. 단위 테스트도 작성하기 힘든 여건에서 단위 테스트와 기능 테스트 둘 다 유지하겠다는 건 나, 그리고 우리가 할 수 있는 일이 아니다.


기능 테스트를 제대로 작성하려면 전문성이 필요하다.

기능 테스트를 제대로 수행하려면 잘 짜인 TC가 필요하고, TC를 잘 작성하려면 전문성이 있어야 한다. QA 조직이 그냥 있는 게 아니지 않은가. 사내에 QA 조직이 아예 없거나 지원을 받을 수 없다면 모를까, 그게 아닌 상황에서 개발자가 작성하는 기능 테스트는 그저 QA를 보완하는 수준에 그칠 가능성이 높다(이마저도 사실 의심스럽지만). QA를 보완하는 수준의 테스트라면 단위 테스트로 충분하다는 결론에 도달했다.



그래서 고민은...

기능 테스트와 작별한 이후, 단위 테스트 자동화에만 집중했다. 팀 동료 모두가 힘을 모으고, 우주의 기운까지 더하여 저장소에 Pull Request를 보내면 CI 서버에서 단위 테스트를 자동으로 수행하는 단계까지 개발 프로세스에 정착시켰다. 1년 전 상황을 생각해보면 감개무량하다. 이제는 어떻게 하면 단위 테스트를 더 효율적으로 작성할 수 있을지 고민하고 있다. 아직 갈 길이 멀다.

어디까지나 내가 속한 조직이 처한 상황과 웹 프런트엔드의 개발 환경에 근거하여 판단한 내용일 뿐, 기능 테스트가 전혀 쓸모 없다는 뜻은 아니다. 서 있는 환경이 다르면 해답도 달라야 한다. 이번에 만들고 있는 제품에 새로운 기능을 추가하면서 공통 라이브러리를 리팩터링했다가 버그 폭탄을 맞았다. 이런 일이 발생한 이유는 단위 테스트 커버리지가 부족했던 탓도 있지만, UI 인터랙션 처리는 단위 수준이 아닌 모듈과 모듈 사이에 걸쳐있는 경우가 많고, 마크업 파트에서 스타일을 변경함으로써 발생하는 사이드 이펙트는 성격상 단위 테스트만으로 수정 후 안전을 보장하기 어렵기 때문이다. 이럴 때 부분적으로 기능 테스트를 자동화하면 도움이 될 수도 있겠다는 생각은 아직도 유효하나, 작성에 들어가는 비용을 생각해보면 이게 맞는 건지 잘 모르겠다.

단위 테스트로 검증하기 어려운 부분이 많다는 건 앞으로 풀어야 할 숙제다. 뾰족한 수가 떠오르지 않는다. QA와 TC를 공유하면 어떨까? 언제나 문제는 시간이다. 해외에서 유행한다는 프로세스가 국내에 잘 정착하지 못하는 이유도 그런 거 아니겠는가. 그래도 언젠가는 도달해야 할 이상향이 있기에 고민은 멈추지 말아야겠지.

뭐가 되었든, 단위 테스트라도 잘 작성하고 볼 일이다.


---

이 글은 네이버 블로그에서도 보실 수 있습니다.