[Effective Unit Testing] Chap2. 좋은 테스트란? |
-
좋은 테스트의 고려 사항은 아래와 같다.
테스트 코드의 가독성과 유지보수성
프로젝트 안에서, 그리고 소스 파일 안에서 코드는 적절히 구조화되어 있는가?
테스트가 무엇을 검사하는가?
테스트는 안정적이고 반복 가능한가?
테스트가 테스트 더블을 잘 활용하는가?
...
2.1. 읽기 쉬운 코드가 유지보수도 쉽다.
-
자동화된 테스트는 결함을 효과적으로 막아주지만, 테스트 역시 코드인지라 가독성 문제에서 벗어날 수는 없다.
읽기 어려운 코드는 검증하기도 어렵고, 결과적으로 테스트를 조금만 작성하는 사태로까지 이어진다.
또 그렇게 작성된 테스트는 우리가 생각하는 좋은 테스트와는 거리가 멀다.
제품의 구조와 API 가 테스트를 고려하지 않고 만들어졌다면 아무리 날고 기는 테스트 작성자가 와도 결국 끔찍한 구조의 이해하기 어려운 테스트밖에 만들 수 없기 때문이다.
2.2. 구조화가 잘 되어 있다면 이해하기 쉽다.
-
읽기 쉽고, 찾기 쉽고, 이해하기 쉽도록 한 가지 기능에 충실한 테스트가 필요하다.
그러면 다음과 같은 이점이 생긴다.
현재 작업과 관련된 테스트 클래스를 찾을 수 있다.
그 클래스에서 적절한 테스트 메서드를 고를 수 있다.
그 메서드에서 사용하는 객체의 생명주기를 이해할 수 있다.
2.3. 엉뚱한 걸 검사하는 건 좋지 않다.
-
테스트의 이름을 너무 믿어버리는 실수를 저지르곤 한다.
보통은 테스트의 이름을 보면 그 테스트가 검사하는 내용을 알 수 있는데, 실제로는 이름과 전혀 관련 없는 것을 검사하는 경우가 종종 있다.
이는 구조를 잘 잡는 일과도 관련이 있다.
테스트의 이름이 검사 내용과 다르다는 건 마치 낭떠러지로 안내하는 도로 표지판을 믿고 운전하는 것과 같다.
우리는 자신의 테스트를 신뢰할 수 있어야 한다.
-
올바른 것을 검사하는 것 못지않게 올바른 것을 똑바로 검사하는 것도 중요하다.
특히 유지보수 관점에서는 어떻게 구현했느냐가 아니라 의도한 대로 구현했느냐를 검사하는 게 중요하다.
2.4. 독립적인 테스트는 혼자서도 잘 실행된다.
-
테스트 코드의 경우에는 테스트가 얼마나 독립적인가를 잘 살펴야 한다.
이때 아키텍처의 경계부분이 특히 중요하다.
경계에서 일어나는 일을 관찰하면서 수많은 코드 냄새를 맡을 수 있는데, 다음 요소와 관련이 있다면 각별한 주의가 필요하다.
시간(Time)
임의성(Randomness)
동시성(Concurrency)
인프라(Infrastructure)
기존 데이터(Pre-existing Data)
영속성(Persistency)
네트워킹(Networking)
-
격리와 독립성이 중요한 이유는 그것이 없다면 테스트를 실행하고 관리하기가 훨씬 어렵기 때문이다.
단위 테스트를 실행하기 위해 개발자가 해야 할 귀찮은 작업이 훨씬 많아진다.
파일시스템의 특정 위치에 빈 디렉터리를 만들어둬야 하거나, MySQL 의 버전과 포트 번호를 확인하거나, 테스트가 사용할 로그인 정보를 데이터베이스에 미리 추가하거나, 환경 변수를 잔뜩 설정하는 일 등이 여기 해당한다.
이 모두는 개발자가 하지 않아도 됐을 작업이다.
이런 작은 하나하나가 모두 작업량을 늘리고 기묘한 테스트 실패를 일으키는 원인이 된다.
-
흔치는 않지만, 테스트 스위트 전체를 실행할 때는 아무 이상 없다가 하나만 따로 실행하면 실패하는 경우가 있다.
물론 그 반대로 마찬가지다.
상호의존성이 있다는 강력한 징조다.
이들은 다른 테스트가 먼저 실행되어 시스템을 원하는 상태로 변경해 놓았다고 가정한다.
그러나 그 가정이 우리 발등을 내려찍는 순간 지옥 같은 디버깅 세계로의 문이 열리게 된다.
-
다시 말하자면 시간, 임의성, 동시성, 인프라, 영속성, 기존 데이터, 네트워킹 관련 코드를 검사할 때는 특별히 더 신경 써야 한다.
이들과의 종속성은 가능하다면 피하는 게 최선이고, 아니면 작고 격리된 단위로 구분해서 이 복잡한 상황으로부터 다른 테스트를 보호해야 한다.
골치 아픈 문제는 소수의 테스트에서만 신경 쓰게 하고, 나머지는 긴장을 풀 수 있도록 배려하는 방법이다.
-
실전에서는 다음과 같은 방법을 시도해 볼 수 있다.
테스트 더블로 서드파티 라이브러리와의 종속성을 제거한다. 손수 만든 어댑터로 적절히 감싸주는 것이다 성가신 부분이 어댑터 안으로 감춰지므로 나머지 앱 로직과 분리해서 검사할 수 있다.
테스트에 필요한 자원을 테스트 코드와 같은 위치에 둔다. 자바 프로젝트라면 같은 패키지에 두면 된다.
테스트가 사용할 자원을 직접 만들도록 한다.
테스트가 필요한 문맥을 직접 설정하게 한다. 절대 다른 테스트에 의존하지 말자.
영속성이 필요한 통합 테스트라면 인메모리(in-memory) 데이터베이스를 활용한다. 테스트를 위한 깨끗한 데이터를 훨씬 간단하게 준비할 수 있다. 덤으로 초기화 시간도 상당히 단축된다.
스레드를 사용하는 코드는 동기식과 비동기식을 구분 지어서 골치 아픈 동시성 문제는 소규모의 전문 테스트 그룹에 맡긴다. 평범한 동기식 코드로 작성된 나머지 로직 대부분은 별다른 어려움 없이 검사할 수 있게 된다.
2.5. 믿음직한 테스트라야 기댈 수 있다.
-
절대 실패하지 않거나 항상 실패하는 테스트는 거의 있으나 마나다.
-
테스트를 믿고 의지하려면 반복할 수 있게 만들어야 한다.
열 번 실행하면 열 번 모두 반드시 같은 결과가 나와야 한다.
그렇지 않으면 빌드할 때마다 테스트 결과를 직접 중재해줘야 한다.
-
앱이 비동기적 요소나 현재 시각에 종속된 코드를 포함한다면 그 부분을 인터페이스로 감싸 격리해야 한다.
그렇게 하면 테스트 더블로 대체할 수 있어 반복 가능한 테스트를 만들 수 있다.
신뢰할 수 있는 테스트를 만드는 핵심 비법이다.
2.6. 모든 일이 그렇듯 테스트에도 도구가 쓰인다.
-
테스트 더블이란 프로그래머들이 흔히 스텁, 가짜 객체, Mock 객체 등으로 알고 있는 개념들을 통칭하는 용어다.
근본적으로는 테스트를 위해 실제 구현체와 교체할 수 있는 객체다.
-
테스트 더블은 테스트 추종 프로그래머의 최고의 친구다.
테스트를 위해 수많은 것을 개선해주고 사용하기도 쉽기 때문이다.
원래의 로직을 간소화된 코드로 대체하여 테스트 속도를 개선한다.
만들어내기 어려운 특수한 상황을 시뮬레이션한다.
대상 객체의 내부 상태나 동작 등 테스트가 접근할 수 없던 정보를 얻어낸다.
-
가장 필수적인 도구는 뭐니뭐니해도 JUnit 과 같은 테스트 프레임워크를 꼽을 수 있다.
-
자동화된 테스트 작성에 꼭 필요한 세 번째 도구는 바로 빌드 도구다.
자동화된 테스트를 통합하지 않은 빌드는 결코 용납될 수 없다.
2.7. 요약
-
테스트의 필수 미덕 중 하나는 가독성이다.
아무리 애를 써도 이해할 수 없는 테스트 코드는 그 자체가 곧 큰 골칫거리가 되어 유지보수하기 어렵게 한다.
이렇게 되면 유지 비용이 너무 커져서 차라리 지워버리는 길을 선택하곤 한다.
-
잘 구조화된 테스트라면 원하는 코드를 빠르게 찾을 수 있고 논리 흐름도 쉽게 이해할 수 있다.
가독성과 직결되는 특성이기도 하다.
-
엉뚱한 걸 검사하는 테스트는 개발자를 잘못된 길로 안내하거나 본질을 흐려버려서 테스트가 의도했던 진짜 논리를 감추고 가독성을 떨어뜨리는 결과를 가져온다.
-
가끔 의심스러운 동작을 하는 테스트도 문제이다. 불신을 키우는 공통 원인을 찾아내고 반복할 수 있게 만드는 것이 중요하다.
-
자동화된 테스트 작성을 보조해주는 필수 도구 세가지는..
테스트 프레임워크, 프레임워크로 작성된 테스트를 실행해줄 자동 빌드, 검사할 수 있는 범위를 넓혀주는 테스트 더블이다.
'프로그래밍 놀이터 > 안드로이드, Java' 카테고리의 다른 글
[Effective Unit Testing] Chap4. 가독성 (0) | 2019.03.13 |
---|---|
[Effective unit Testing] Chap3. 테스트 더블 (0) | 2019.02.28 |
[Effective Unit Testing] Chap1. 좋은 테스트의 약속 (0) | 2019.02.26 |
[Gradle] compile(api) vs. implementation (1) | 2019.02.06 |
[android] Android unit and instrumentation tests tutorial (0) | 2019.02.05 |
댓글