Skip to content

Latest commit

 

History

History
244 lines (175 loc) · 22.9 KB

11. Testing Overview.md

File metadata and controls

244 lines (175 loc) · 22.9 KB

11. 테스트 개요

  • 자동 테스트(automated test)는 버그가 몰래 숨어들어 고객을 놀라게 하는 사태를 막아준다.
  • 개발 주기에서 버그를 발견하는 시기가 늦어질수록 고치는 비용이 커진다.
  • 새로운 기능을 추가하거나, 코드가 더 건실해지도록 리팩터링하거나, 대규모 재설계를 진행하는 상황에서 자동 테스트는 실수를 빠르게 잡아주므로 안심하고 소프트웨어를 변경할 수 있게 해준다.
  • 테스트 체계가 잘 갖춰져 있다면 변화를 두려워할 이유가 없다.
  • 테스트를 작성하는 행위가 시스템의 설계도 개선해준다. 데이터베이스에 너무 강하게 묶여 있는지, 이 API가 필수 유스케이스를 지원하는지, 시스템이 극단적인 상황들에 잘 대처하는지 등을 확인할 수 있다.
  • 자동 테스트를 작성하면 이런 문제들을 개발 주기의 초반에 잡아내게 된다. 그 결과 모듈화가 더 잘되어 미래의 변화에 훨씬 유연한 소프트웨어가 만들어진다.

11.1 테스트를 작성하는 이유

  • '자동 테스트'의 정체는?
    • 테스트하려는 단 하나의 행위(주로 메서드나 API)
    • 특정한 입력(API에 전달하려는 값)
    • 관측 가능한 출력 혹은 동작
    • 통제된 조건(하나의 격리된 프로세스 등)
  • 시스템에 특정 값을 입력하고, 출력 결과를 확인하여 시스템이 기대한 대로 동작한 것인지를 판단한다.
  • 코드베이스의 덩치에 비례하여 테스트 스위트도 커진다. 그 과정에서 테스트 결과가 일관되지 못하거나 느려지는 문제가 나타나기도 한다. 이러한 문제를 해결하지 못하면 테스트 스위트의 존폐가 위태로워진다.
  • 테스트가 생산성을 떨어뜨리고 고칠 게 계속 나오거나 결과를 믿을 수 없다면 엔지니어들은 더 이상 테스트를 신뢰하지 않고 우회 방법을 찾으려 할 것이다.
  • 테스트는 좋은 제품을 빠르게 만들 수 있게 해줄 뿐 아니라 우리 삶에서 중요한 제품과 서비스의 안전을 보장하는 데도 점점 핵심적인 역할을 하고 있다. 소프트웨어 결함은 단순히 짜증을 일으키는 수준을 넘어서 막대한 금전적 손실을 낳고 심하면 목숨을 앗아가기까지 하기 때문이다.

11.1.1 구글 웹 서버 이야기

  • 제품 결함 해결을 프로그래머의 능력에만 의존해서는 안 된다.
  • 개별 엔지니어가 버그를 심는 빈도는 아주 낮더라도 프로젝트가 커져 팀원이 많아지면 결함 목록은 계속 길어질 것이다.
  • 디버깅 방식에서는 버그가 발생할 때마다 엔지니어가 디버거를 실행해 문제를 분석해내야 한다. 자동 테스트와의 엔지니어링 비용 차이는 하늘과 땅만큼 벌어진다.

11.1.2 오늘날의 개발 속도에 맞는 테스트

  • 대부분의 소프트웨어는 기능과 지원 플랫폼이 너무 폭증해서 사람이 모든 행위를 수동으로 검증할 수 있는 한계를 아득히 넘어섰다. 그래서 테스트에서의 해법은 '자동화'뿐이다.

11.1.3 작성하고, 수행하고, 조치하라

  • 가장 순수한 형태의 자동 테스트는 '테스트 작성', '테스트 수행', '실패한 테스트에 대한 조치'로 이루어진다.
// Calculator 클래스가 결과가 음수인 계산을 제대로 처리하는지 확인한다.

func test_2에서5를빼면_마이너스3이다() {
    let sut = Calculator()
    let expectedResult = -3
    
    let actualResult = calculator.subtract(2, 5)
    
    XCTAssertEqual(actualResult, expectedResult)
}
  • 오늘날 시스템의 규모와 배포 속도를 따라잡으려면 모든 엔지니어가 테스트도 함께 개발해야만 한다.
  • 테스트를 작성하는 것과 좋은 테스트를 작성하는 것은 별개이다.
  • 테스트를 코드로 작성하면 다양한 환경에서 수행할 수 있도록 테스트들을 모듈화하기도 좋다.
  • 실패하는 테스트가 해결되지 못하고 빠르게 쌓여간다면 테스트에 투자한 노력이 허사가 되니 그렇게 되지 않도록 하는 것이 중요하다.
  • 건실한 자동 테스트 문화에서는 모두가 테스트를 작성하고 공유하도록 장려하고, 테스트들을 정기적으로 실행한다. 그리고 테스트가 실패하면 바로 조치하도록 권장해야 테스트 프로세스를 신뢰하고 계속 이어갈 수 있다.

11.1.4 테스트 코드가 주는 혜택

  • 테스트에 투자하는 게 개발자 생산성을 향상시키는 이유는?

디버깅 감소

  • 테스트를 거친 후 제출되는 코드는 통상적으로 결함이 적다. 그러므로 그 코드의 존속 기간 전체로 봤을 때 결함이 줄어든다.
  • 코드 조각은 수명이 다하는 날까지 여러 팀 또는 자동 코드 유지보수 시스템이 수정할 수도 있다. 그래서 테스트를 한 번 작성해두면 프로젝트가 살아 있는 내내 값비싼 결함을 예방해주고 짜증나는 디버깅에서 해방시켜주는 식으로 지속해서 혜택을 준다.

자신 있게 변경

  • 테스트들이 프롲게트의 주요 기능들을 끊임없이 검증해주는 덕에 좋은 테스트들로 무장한 팀은 자신감을 가지고 변경들을 리뷰하고 수용할 수 있다.
  • 행위가 달라지지 않는 리팩터링 시에는 테스트 자체는 변경될 필요가 없으므로 적극 권장할 수 있다.

더 나은 문서자료

  • 한 번에 하나의 행위만 집중해 검증하는 명확한 테스트는 마치 실행 가능한 문서와 같다. 코드가 특정 상황에서 어떻게 동작하는지 궁금하다면 그 상황을 검증하는 테스트를 보면 되기 때문이다.
  • 요구사항이 변경되어 새로운 코드가 기존 테스트를 통과하지 못한다면 그 문서자료(테스트)가 이제 낡았음을 알 수 있다.

더 단순한 리뷰

  • 정확성, 극단 상황, 오류 상황 등 다양한 측면에서 코드를 검사해주는 테스트가 준비되어 있다면 리뷰어가 변경된 코드가 제대로 작동하는지를 검증하는 시간을 크게 줄여준다. 각각의 상황을 머릿속에서 일일이 그려보는 대신 해당 테스트를 수행해 통과하는지만 보면 되기 때문이다.

사려 깊은 설계

  • 새로 작성한 코드의 테스트를 작성하는 일은 해당 코드의 API가 잘 설계되었는지를 시험하는 행위이다.
  • 테스트하기 어려운 코드는 너무 많은 역할을 짊어지거나 의존성을 관리하기 어렵게 짜여졌기 때문일 가능성이 크다.
  • 잘 설계된 코드라면 모듈화가 잘 되어 있어야 한다. 다른 코드와 강하게 결합되지 않고 특정 역할에 집중해야 한다.
  • 설계 문제를 조기에 바로잡는다면 훗날 수정할 때 고생을 덜 한다.

고품질의 릴리즈를 빠르게

  • 건실한 자동 테스트 스위트를 갖춘 팀은 새로운 버전을 릴리즈하며 불안에 떨지 않는다.

11.2 테스트 스위트 설계하기

  • 엔지니어들은 커다란 시스템 규모의 테스트를 작성하는 것을 선호하지만, 이런 테스트는 작은 테스트와 비교하여 느리고 신뢰도가 낮고 디버깅하기도 어렵다.
  • 고통을 줄이고자 하는 욕구가 엔지니어들을 점점 더 작은 테스트를 작성하도록 이끌었다. 그러면서 더 작은 테스트가 더 빠르고, 안정적이고, 평균적으로 고통이 적다는걸 깨우쳤다.
  • 작다는 것의 의미는?
    • 크기(size): 테스트 케이스 하나를 실행하는 데 필요한 자원
    • 범위(scope): 검증하려는 특정한 코드 경로(code path)

11.2.1 테스트 크기

  • 구글에서는 모든 테스트를 크기 기준으로 구분한다.
  • 테스트의 크기를 가늠하는 기준은 코드 줄 수가 아니다. 대신 어떻게 동작하고, 무엇을 하고, 얼마나 많은 자원을 소비하는지로 평가한다.
  • 쉽게 설명하면, 작은 테스트는 프로세스 하나에서 동작하고, 중간 크기 테스트는 기기 하나에서, 큰 테스트는 자원을 원하는 만큼 사용해 동작한다.
  • 구글이 테스트 스위트에 바라는 품질은 속도와 결정성이다.
  • 범위에 상관없이 작은 테스트는 더 많은 인프라나 자원을 사용하는 테스트보다 거의 항상 더 빠르고 결정적이다.

작은 테스트

  • 제약이 가장 엄격하다.
  • 테스트가 단 하나의 프로세스에서 실행되어야 한다. 프로그래밍 언어에 따라서는 이 제약을 하나의 스레드까지 좁히는 경우가 많다.
  • 서버를 두고 독립된 테스트 프로세스에 연결해 수행하는 방식도 허용되지 않는다. 데이터베이스와 같은 제3의 프로그램을 수행하ㅐ서도 안 된다.
  • sleep, I/O 연산 같은 블로킹 호출을 사용해서는 안 된다. 네트워크와 디스크에도 접근할 수 없다는 뜻이다.
  • 블로킹 호출을 수반하는 대상을 검사하는 테스트 코드는 테스트 대역을 사용해야 한다. 테스트 대역은 강한 의존성을 가벼운 이프로세스 의존성으로 대체해주는 수단이다.
  • 제약들의 목적은 테스트를 느려지게 하거나 비결정적으로 만드는 주요 원인들로부터 작은 테스트를 떼어 놓는 것이다.
  • 테스트 케이스가 많은 테스트 스위트에서는 불규칙한 테스트(flaky test)가 단 몇 개만 있어도 원인을 파악하느라 생산성이 급격하게 떨어질 것이다.

중간 크기 테스트

  • 작은 테스트에 부과된 제약을 그대로 따르기는 어렵지만 유용한 테스트.
  • 여러 프로세스와 스레드를 활용할 수 있고, 로컬 호스트로의 네트워크 호출 같은 블로킹 호출도 이용할 수 있다. 단, 외부 시스템과의 통신은 여전히 허용하지 않는다(단 한 대의 기기에서만 수행되어야 한다).
  • 데이터베이스 인스턴스를 실행할 수 있게 되어 테스트 대상 코드를 더 현실적인 설정 하에서 검증할 수 있다.
  • 유연성이 커지면 반대급부로 테스트는 느려지고 비결정적이 될 가능성이 높아진다. 여러 프로세스에 걸쳐 있거나 블로킹 호출을 하기 시작하면 운영체제나 서드파티 프로세스에 의존하게 된다. 외부 요인이 개입되므로 성능과 결정성을 온전히 우리 스스로가 보장할 수 없다.

큰 테스트

  • 중간 크기 테스트를 구속하던 로컬 호스트 제약에서 해방되어 테스트와 대상 시스템이 여러 대의 기기를 활용할 수 있게 된다.
  • 더 유연해지는 만큼 위험도 늘어난다. 여러 기기에 걸쳐 있느 시스템을 네트워크로 연결해 다루게 되면서 단일 기기에서 구동할 때보다 느려지거나 비결정성이 커질 가능성이 훨씬 높아진다.
  • 구글은 큰 테스트를 몇 가지 용도에 한정해서 활용한다.
    1. 종단간 테스트(end-to-end test), 코드 조각이 아닌 설정을 검증하는 게 주된 목적이다.
    2. 테스트 대역을 사용하는 게 불가능한 레거시 컴포넌트 테스트
  • 큰 테스트는 작은 테스트나 중간 테스트와 분리하여 빌드나 릴리즈 때만 수행되도록 하여 개발 워크플로에 영향을 주지 않도록 한다.

사례 연구: 불규칙한 테스트는 비싸다

  • 테스트 각각이 아주 조금이라도 비결정적이라면 이따금 하나씩 실패하는 일이 드물지 않을 것이다. 실패 확률이 0.1%밖에 안 되더라도 테스트를 하루에 1만 번 수행한다면 매일 평균 10번씩은 실패 원인을 조사해서 조치해야 한다. 팀에 당장 필요한 더 생산적인 일을 할 시간은 그만큼 줄어든다.
  • 테스트 결과가 불규칙한 상황이 계속되면 테스트를 믿지 못하는 상황이 올 수 있다. 이 단계에 들어서면 엔지니어들은 테스트가 실패하더라도 더는 신경 쓰지 않는다.
  • 경험상 불규칙한 실패가 1%에 이르면 테스트의 가치가 바래기 시작한다.
  • 대부분의 불규칙한 실패는 테스트 자체에 비결정적으로 동작하는 로직이 있어서 발생한다. 소프트웨어에는 클록 시간, 스레드 스케줄링, 네트워크 지연시간 등 비결정적인 요소가 많다.

테스트 크기와 무관한 공통 특성

  • 모든 테스트는 밀폐(hermetic)되어야 한다. 즉, 셋업, 실행, 테어다운 하는 데 필요한 모든 정보를 담고 있어야 한다.
  • 테스트 수행 순서 같은 외부 환경에 관해서는 가능한한 아무것도 가정하지 않아야 한다.
  • 테스트는 확인하려는 행위를 수행하는 데 필요한 정보'만' 포함해야 한다.
  • 테스트에서는 조건문이나 순환문 같은 제어문을 쓰지 않는 게 좋다. 복잡한 테스트일수록 버그가 숨어들 가능성이 커지며, 실패 원인을 찾기도 어렵다.

11.2.2 테스트 범위

  • 구글은 테스트 크기를 많이 강조하지만 테스트 범위도 중요하게 생각한다.
  • 테스트 범위란 주어진 테스트가 얼마나 많은 코드를 검증하느냐를 말한다.
  • 보통 단위 테스트라고 하는 좁은 범위 테스트(narrow-scoped test)는 독립된 클래스느 메서드같이 코드베이스 중 작은 일부 로직을 검증하도록 설계된다.
  • 보통 통합 테스트라고 하는 중간 범위 테스트(medium-scoped test)는 적은 수의 컴포넌트들 사이의 상호작용을 검증하도록 설계된다.
  • 종단간 테스트, 시스템 테스트 등으로 불리는 넓은 범위 테스트(large-scoped test)는 시스템의 서로 다른 부분들 사이의 상호작용, 혹은 클래스나 메서드 하나만 실행할 때는 괜찮다가 여럿을 조합해 실행하면 나타나는 예기치 못한 동작을 검증하도록 설계된다.
  • 일부 다른 테스트 전략에서는 가짜 책체나 모의 객체 같은 테스트 대역을 많이 활용하여 테스트 대상 시스템 바깥 코드가 실행되는 일을 피하기도 하지만, 구글은 딱히 실행할 수 없는 상황이 아니라면 실제 의존성을 끊지 않는 편을 선호한다.
  • 구글은 되도록 작고 좁은 테스트를 추구한다. 실제로 구글은 비즈니스 로직 대부분을 검증하는 좁은 범위 단위 테스트가 80%, 둘 이상의 구성요소가 상호작용을 검증하는 중간 범위의 통합 테스트가 15%, 전체 시스템을 검증하는 종단간 테스트가 5% 정도가 되도록 한다.
  • 주의해야 할 안티패턴
    1. 아이스크림 콘(ice cream cone): 종단간 테스트를 많이 작성하고 통합 테스트나 단위 테스트는 훨씬 적게 작성한다. 이런 테스트 스위트는 일반적으로 느리고 신뢰할 수 없으며 고치기도 어렵다.
    2. 모래시계(hourglass): 종단간 테스트와 단위 테스트는 많지만 통합 테스트가 적다. 아이스크림 콘만큼 나쁘지는 않지만, 중간 범위 테스트였다면 더 빠르고 쉽게 해결했을 문제들을 종단간 테스트로 막아내고 있다. 구성요소들이 강하게 커플링되어 각각의 인스턴스를 독립적으로 만들어낼 수 없을 때 나타난다.

11.2.3 비욘세 규칙

네가 좋아 했다면 테스트를 준비해뒀어야지

  • 깨뜨려보고 싶은 모든 것을 테스트하라(성능, 행위 정확성, 접근성, 보안, 시스템이 실패에 대처하는 방법, ...)

의도적으로 실패 상황을 만드는 테스트

  • 실패할 때까지 기다리는 대신 흔한 유형의 실패 상황을 시뮬레이션하는 자동 테스트를 작성하자.
  • 단위 테스트에서라면 예외나 에러를, 통합 테스트나 종단간 테스트라면 원격 프로시저 호출(RPC) 오류를 주입하거나 지연시간을 늘려볼 수 있다.
  • 카오스 엔지니어링 같은 기법을 활용하면 실제 프로덕션 네트워크에 영향을 주는 훨씬 더 큰 중단 사태도 시뮬레이션 할 수 있다.
  • 신뢰할 수 있는 시스템이라면 부정적인 조건을 예측하고 대응 방식을 통제할 수 있어야 한다.

11.2.4 코드 커버리지

  • 어느 테스트가 기능 코드의 어느 라인을 실행했는지를 측정하는 수단.
  • 테스트 품질을 파악하는 표준 지표로 간주되기도 하지만, 적은 수의 테스트만으로 상당량의 라인을 실행하면서도 의미 있는 동작은 거의 돌려보지 않을 수 있기 때문에 적절치 않다.
  • 코드 커버리지는 호출된 라인 수만 셀 뿐, 실행 결과로 어떤 일이 벌어졌는지는 고려하지 않는다.
  • 큰 테스트는 커버리지 인플레이션을 일으키므로 커버리지는 작은 테스트에서만 측정하길 권한다.
  • 여느 지표와 마찬가지로 커버리지 자체가 목표가 되기 쉽다는 현실이다. 처음에는 매우 합리적으로 들리지만, 목표 커버리지를 달성하면 자발적으로 그 이상을 해야 할 동기가 부족해 그 이상 올리기 어렵다.
  • 테스트 스위트의 품질을 높이는 방법은 검사해야 할 행위에 집중하는 것이다.
  • 코드 커버리지는 테스트되지 않은 코드가 어디인지는 알려줄 수 있지만, 시스템이 얼마나 제대로 테스트되었느냐를 판가름하는 지표로는 적합하지 않다.

11.3 구글 규모의 테스트

11.3.1 대규모 테스트 스위트의 함정

  • 코드베이스가 커가다 보면 기존 코드를 변경하는 일을 피할 수 없다. 그런데 자동 테스트가 엉망으로 작성되어 있다면 이럴 때 코드를 변경하기 어렵다.
  • 특히 깨지기 쉬운 테스트(brittle test), 즉 예상 결과를 너무 세세하게 표현하거나 광범위하고 복잡한 상용구사 덕지덕지한 테스트가 우리를 가로막는다.
  • 깨지기 쉬운 테스트를 만드는 주범으로 모의 객체 오용을 들 수 있다. 모의 객체의 한계를 이해해두면 잘못 사용하는 일이 많이 줄어들 것이다.
  • 테스트 스위트가 비결정적이고 느려지면 생산성을 갉아먹는다. 테스트 스위트가 득보다 실이 많다면 엔지니어들은 결국 테스트를 실행하지 않고서라도 구현 작업을 끝마칠 수 있는 방법을 찾으려 할 것이다.
  • 거대한 테스트 스위트를 잘 관리하는 비결은 바로 테스트를 존중하는 문화이다. 훌륭한 기능을 출시했을 때와 똑같이 테스트를 견고하게 만든 엔지니어에게 보상해준다.
  • 문화를 가꾸는 일과 더불어 린터를 개발하거나 문서자료를 보강하는 등, 안 좋은 테스트를 만드는 실수를 줄여주는 인프라에도 투자해야 한다.
  • 지원해야 할 프레임워크와 도구의 수를 줄여서 투자대비 효율을 높인다. 테스트 관리 비용을 낮추는 데 투자하지 않는다면 결국에 엔지니어들이 테스트가 전혀 가치가 없다고 결론내게 될 것이다.

11.4 구글의 테스트 역사

  • 자동 테스트를 전사적으로 뿌리내리게 한 원동력
    1. 오리엔테이션 수업
    2. 테스트 인증 프로그램
    3. 화장실에서도 테스트

11.4.1 오리엔테이션 수업

  • 오리엔테이션 때 신규 엔지니어들에게 자동 테스트의 가치를 이야기하는 주제를 배치했다.
  • 마치 구글의 관행인 것처럼 소개했다.

11.4.2 테스트 인증

  • 테스트 인증의 목적은 각 팀이 자신의 테스트 프로세스 수준(성숙도)을 알게 하고 한 단계 올라서기 위한 지침을 제공하는 것이었다.
  • 총 5개 레벨로 구성된다.
  • 테스트 인증 레벨1: 지속적 빌드 구축, 코드 커버리지 추적, 모든 테스트를 작은/중간 크기/큰 테스트로 구분, 불규칙한 테스트 식별, 바로 실행할 수 있는 빠른 테스트 스위트 마련
  • 레벨이 높아질수록 '실패하는 테스트가 없어야 릴리즈 가능', 비결정적 테스트 모두 제거' 같이 더 어려운 조건이 추가된다.
  • 레벨5가 되려면 모든 테스트를 자동화하고, 모든 커밋 전에 빠른 테스트 스위트가 수행되도록 하고, 비결정성을 완전히 제거하고, 모든 행위를 테스트해야 한다.
  • 2015년에 자동화된 방식(pH)으로 대체될 때까지 테스트 문화 개선에 이바지함.

11.4.3 화장실에서도 테스트

  • 화장실 변기 앞에 전단지를 붙인다.
  • 에피소드 하나가 한 페이지를 절대 넘지 않도록 제한하고, 제작자가 가장 중요하고 실행 가능한 조언에 집중할 수 있도록 유도했다.
  • 공개 버전

11.4.4 오늘날의 테스트 문화

  • 신규 입사자 오리엔테이션에서는 여전히 테스트를 가르친다.
  • 거의 매주 새로운 TotT가 배포된다.
  • 코드를 변경할 때마다 코드 리뷰를 거쳐야 한다. 그리고 모든 변경에는 테스트 코드가 포함되어 있어야 한다.
  • 테스트 인증 프로그램을 대체하고자 프로젝트 건실성(Project Health, pH) 개념을 내놓았다. pH는 테스트 커버리지와 테스트 지연시간같이 프로젝트의 건실성을 측정할 수 있는 지표 수십 가지를 지속해서 수집하고 종합하여 하나의 값으로 내어준다. 1이 가장 안 좋고 5가 가장 좋다.
  • 테스트를 하라고 강제하지 않았다. 테스트가 훌륭한 아이디어임을 입증하는 데 초점을 맞췄다. 올바른 일이므로 아무도 강요하지 않더라도 계속할 가능성이 크다.

11.5 자동 테스트의 한계

  • 모든 종류의 테스트를 다 자동화할 수는 없다. 전화나 영상 통화 시스템의 성능을 평가할 때는 종종 사람에게 판단을 맡긴다.
  • 창의력이 필요한 분야에서도 인간이 더 뛰어날 수 있다. 사람이 특정 결점을 발견하고 이해한 다음에는 자동 보안 검사 시스템에 테스트를 추가해 지속해서 수행하고 확장한다.
  • 탐색적 테스팅(exploratory testing): 기본적으로 창의력을 요구하는 작업으로, 검사 대상을 마치 고장내야 할 퍼즐로 취급한다.
  • 탐색적 테스팅으로 문제를 발견하는 즉시 자동 테스트를 추가하여 문제가 재발하지 않도록 예방해야 한다.
  • 이렇게 파악한 문제의 행위들을 자동 테스트에 맡겨놓으면 몸값이 비싼 인간 테스터들은 지루한 반복 작업에서 벗어나 인간이 가장 잘할 수 있는 영역에 집중할 수 있다.

11.6 마치며

11.7 핵심 정리

  • 자동 테스트는 소프트웨어를 변경할 수 있게 해주는 토대이다.
  • 테스트를 확장하려면 반드시 자동화해야 한다.
  • 테스트 커버리지를 건실하게 유지하려면 균형 잡힌 테스트 스위트가 필요하다.
  • 네가 좋아했다면 테스트를 준비해뒀어야지
  • 조직의 테스트 문화를 바꾸는 데는 시간이 걸린다.