작성자: Denis Brandi
작성일: Jun 30, 2020
플랫폼: Medium
위 글을 1차 번역한 후 회사에서 테스트 코드를 작성하며 했던 생각들을
함께 엮어 봤습니다.
이 세상의 모든 회사는 이 사실을 안다: 버그는 (손실)비용이다.
Nasa’s Mars Climate Orbiter bug: $125 million
Ariane 5 Flight 501 bug: $500 million
EDS Child Support System bug: + $ 1 billion
Japanese bitcoin exchange, Mt. Got, bug: + $ 500 million
이것만 봐도 배포 전 테스트의 필요성을 확실히 알 수 있다.
스타트업을 포함한 중소기업들은 매뉴얼 테스트에 굉장히 많이 의존한 채
MVP 를 실험한다. 한정된 인적 자원과 투자금을 고려해 본다면 코드의 퀄리티가 최우선이 아닌 사실이 이해가 안가는 것은 아니다.
하지만, 이미 (손실)비용이 발생하고 있다.
총 두 곳의 스타트업을 다녀보며 유닛 테스트에 대해 많은 생각을 하게 된다.
"누가 좋은 거 몰라서 안하나요?
시간 없잖아요. 품질이 중요한 거 우리도 알아요, 근데 소프트웨어는 릴리즈 이후에 계속 개선할 수 있잖아요?. 일단 배포하고 그 테스트 작업은 나중에 할까요?"
소프트웨어 장인
이란 책에 보면 이런 말이 있다.
절대 스크럼 마스터한테 "테스트" 말을 꺼내지도 말라고 말이다.
어떤 것이든 칸반 보드에 올라가는 순간 다음의 의미를 지니게 된다고 한다.
"그 일은 타협이 가능한 것이야"
첫 MVP 배포 이후의 후속 배포들은 검증되지 않아 투박한 코드 베이스를 기반으로 진행된다.
테스트를 진행하는 사람들은 매번 테스트 flow가 늘어나는 것을 알게 된다.
또한, 사소한 코드의 변화는 메이저 버그가 된기 십상이다.
버그를 발견하고 수정하는 데 쓰이는 노력은 초기 개발의 배가 되어 돌아온다. 스프린트가 진행될 수록 이러한 상황은 악화된다.
이제 팀은 나비효과를 피할 수 없게 됐다.
매뉴얼 테스트에는 많은 시간이 필요하고, 새로운 기능을 위한 코드 작성 또한 마찬가지다. 이런 지루하면서 중요한 작업을 자동화할 때가 왔다.
하지만, 아직 독립적인 테스트를 작성할 수준은 아니다.
왜냐하면 비즈니스 코드는 다른 객체, 모듈들과 의존적인 상태일 것이고,
의존성 주입 역시 고려되지 않았을 것이다.
또한 비동기 처리해야 할 작업들이 동기적으로 작동되고 있을 것이다.
따라서, UI 테스트부터 시작해야 한다.
UI 테스트를 기반으로 코드베이스의 일부를 여러 번 리팩토링 하다보면,
대개는 통합 테스트
또는 단위 테스트
작성이 아주 조금은 가능한 상태로 개선된다.
(결론부터 말하자면, 아이스크림 콘 형태의 테스트 코드 구조는 비효율적이다.)
자동화된 테스트가 존재하고, 분명히 이전보다 상황은 나아졌지만 이러한 형태는 옳지 않다. 매튜얼 테스트와 UI 테스트는 고비용 작업이란 걸 분명히 알아야 한다.
(위에서 매뉴얼 테스트가 끔찍하단 걸 확인했으니, UI 테스트 레이어부터 하나씩 살펴보자)
역시 작성과 수정에 많은 시간이 소요된다.
테스트를 위해 전체 시스템이 가동돼야 한다.
어떤 부분에서 에러가 났는지 모른다.
그냥 어떤 페이지에서 원하는 대로 작동되지 않았음을 알려줄 뿐이다.
또한, 여러 시나리오들을 커버하기에 조합의 수는 너무나 많다.
UI테스트보다 빠르고 모든 시스템의 작동이 필요하진 않다.
하지만 여전히 구체적인 에러 위치를 알려주진 않는다.
비즈니스 로직을 모두 커버하는 테스트 작성 역시 조합의 수가 많아 고비용의 작업이다.
매우 빠르고 말 그대로 가볍다.
코드의 어느 위치에서 문제가 발생했는지 정확히 알려준다.
하지만 안타깝게도 아이스크림 콘 모양에서 봤듯이, 단위 테스트의 비중이 너무 작다.
2003년에 마이크 콘과 리사 크리스핀은 테스트 자동화 피라미드 개념을 만들었다. 테스트 종류를 그룹화했고 각 그룹에 대해 얼마나 많은 테스트를 실시해야 하는지를 설명했다.
명확하게 3개의 테스트 레이어를 보여준다.
여기에 집중해야 한다. 작성과 검증 그리고 실행에 있어 저비용이며 빠르다.
특히, 에러의 위치를 정확하게 알려준다.
프로그래머가 생각한 대로 작동하는지의 논리를 테스트하기 위해 사용된다.
유닛 테스트가 커버하는 영역이 많다는 것은 비즈니스 코드가 충분히 검증되고 있음을 의미하고, 그러므로 코드가 유연해진다.
단위 테스트만큼 생산적이지 않지만, 인프라 계층(DB 계층)과 함께 잘 작동하고 외부 종속성이 잘 통합되어 있는지 확인하는 용도로만 사용된다.
End-to-End 테스트라고도 하며, 가장 비생산적인 테스트다.
: 단위 테스트와 서비스 테스트에 비해 실행 시간이 10배 이상 오래 걸리고
UI를 조금만 변경해도 깨질 수 있기 때문에 사소한 코드 변경에도 고비용이 발생한다.
따라서, 스모크 테스트에만 사용돼야 한다.
스모크 테스트는 QA팀에서 기본 기능이 잘 작동하는지 확인하기 위해 소프트웨어 빌드 후에 수행되는 테스트다. 기본 기능도 작동 하지 않는데, QA 팀에서 테스트를 진행하는 것은 매우 비효율적이다. 도메인의 기본과 핵심 기능이 무엇인지 명확히 하고, 해당 기능을 스코므 테스트로 정의하는 작업이 선행돼야 한다.
피라미드 구조의 서비스 테스트가 자칫 불명확해 보일 수 있다.
마이크 콘 본인도 중간 단계의 테스트가 종종 무시되는 것을 발견했는데, 이것은 개발자들이 UI 테스트나 매뉴얼 테스트 작업에 좀 더 많은 시간을 할애하게 되기도 한다.
하지만, 개발자들의 불안이 이해가 가기도 한다.
"정말 매뉴얼 테스트가 없어도 된다고? 불안하지 않아도 되는거야?"
앨리스터 스콧은 초기 콘의 피라미드를 개선했다.
중급 레이어의 테스트와 맨 꼭대기 수동 탐색 테스트를 구체화했다.
구조를 설명하면 이렇다.
모호했던 서비스 테스트와 새로운 개념인 맨 꼭대기의 수동 탐색 테스트를 살펴 보자.
실용주의 피라미드에서 중간의 서비스 테스트는 컴포넌트 테스트, 통합 테스트 그리고 API 테스트(또는 Contract 테스트)로 분류한다.
UI 테스트의 경량화 및 저비용화한 버전이다.
테스터가 직접 행하는 수고로움을 하는 대신 서비스 레이어에서 인프라 레이어까지의 검증을 한다. 인프라 레이어에는 데이터베이스, 네트워크 요소들이 있다.
컴포넌트 테스트와 유사하지만, 서브 시스템/모듈과의 상호작용 그리고 인터페이스 검증에 더 초점을 맞춘다.
이 과정에서 서드 파티와 통신을 하는 테스트도 포함되기에 테스트 자체가 온전히 격리됐다고 말하기 힘들다. 따라서 실제 환경에서 실행해야 하므로 속도가 빠르진 않다.
외부 서비스와의 연관된 기능들은 항상 예측이 불가능하다.
그래서 Pact
같은 모킹 관련 라이브러리들을 사용하게 된다.
이러한 독특한 모킹 구조는 Consumer(클라이언트)와 Provider(서버)를 위한 하나의 contract 파일을 생성한게 되는데, 중간 매개체인 브로커를 통해 연결된다.
따라서, 테스트 과정에서 연결의 실패는 Consumer 또는 Provider 중 한 곳에서 contract가 깨졌다는 것이다.
실용주의 피라미드 테스트 구조에서 주목할 점은 매뉴얼 테스트가 여전히 필요하다는 것이다.
개발팀이나 QA팀이 테스트 자동화에 얼마의 노력을 들였다는 것은 중요하지 않다. 냉정하게 말하자. 테스트 자동화를 이뤄냈다고 테스트가 완벽하다고 할 수 있는가. 그러므로 자동화를 기반으로 하는 피라미드 구조 가장 최상단에는 수동 테스트가 있게 된다.
재밌는 점은 이렇다. 테스트에 참여하는 관계자들은 최대한 애플리케이션에서 에러를 일으켜 보려는 마음으로 임해야 한다는 것이다. 그 과정에서 어떤 flow를 거쳤는지 상세히 기록해야 함은 물론이다. 개발적인 이슈 이외에도 디자인의 이상한점, UI/UX 측면까지도 기록해야 한다.
만약 버그를 찾았다면 자동화 테스트로 전환할 수 있는지 고민해야 한다. 개발자는 해당 버그를 통해 다음 스프린트에서 유의할 체크 리스트가 생기게 된 것이다.
소프트웨어 산업에서 테스트와 관련된 전문용어가 많이 생겨나고 있다.
그런 현상들로 인해 테스트가 점점 혼란을 가중시킨다.
그러니 테스팅 용어에 너무 집착하지 말자.
단지, 당신의 팀에 적절한 개념을 지칭하는 이름이 있으면 된다.
테스트 피라미드의 맨 아래의 유닛 테스트는 코드
자체에 초점을 맞춘다고 배웠다. 그리고 피라미드의 상위 계층으로 갈수록 테스트를 정의하고 실행하고 평가하는데 더 많은 시간, 비용, 개발 지식, 비즈니스 로직 그리고 비즈니스 변화 히스토리 배경지식을 먼저 알아야 한다.
만약 이제 막 신규 입사한 개발자라면 어떻게 해야할까?
그들이 처음 맞닥뜨리는 코드는 전부 레거시다.
스프린트를 진행해야 하거나 리팩터링을 해야 하는 업무를 배정받았다면, 선행해야할 작업이 있다 - 반드시 레거시를 위한 테스트 코드를 먼저 작성해야 한다. 그 이후에 리팩터링을 진행하는 것이 옳다.
Thanks for reading!