테스트 코드도 유지 보수와 가독성을 신경 써야 한다. 많은 기능들을 하나의 메서드에 담고 있는 것을 금기시 하는 것과 같은 이유다. 테스트 코드를 작성할 때 어떤 기법들이 있는지 혹은 지켜야할 생활 체조(?)같은 것들이 있는지 지키면 좋은 것들, 혹은 알고 있으면 좋은 것들을 간략하게 알아본다.
🎯 given-when-then 패턴
테스트 코드도 유지 보수를 하고 가독성을 신경써야 한다. 일관성 있는 패턴을 유지하여 가독성을 향상 시킨다.
@Test void test() throws Exception { // given // when // then }
🎯 테스트 코드와 유지 보수
- 변수나 필드를 사용해서 기댓값 표현하지 않기
- 두 개 이상을 검증하지 않기
- 정확하게 일치하는 값으로 모의 객체 설정하지 않기
- 과도하게 구현 검증하지 않기
- 셋업을 이용해서 중복된 상황을 설정하지 않기
- 통합 테스트에서 데이터 공유 주의하기
- 통합 테스트의 상황 설정을 위한 보조 클래스 사용하기
- 실행 환경이 다르다고 실패하지 않기
- 실행 시점이 다르다고 실패하지 않기
- 랜덤하게 실패하지 않기
- 필요하지 않은 값은 설정하지 않기
- 단위 테스트를 위한 객체 생성 보조 클래스
- 조건부로 검증하지 않기
- 통합 테스트는 필요하지 않은 범위까지 연동하지 않기
🎯 변경에 유연하고 테스트하기 좋은 코드?
테스트할 수 없다면 public Class
로
- ex.
coreLogic1()
과coreLogic2()
의 메서드가 중요하다면 테스트를 위해 클래스 추출을 고려하자.
class AClazz { public void logic() { coreLogic1(); coreLogic2(); } private void coreLogic1() { ... } private void coreLogic2() { ... } }
테스트할 수 없다면 하드 코딩됐을 가능성이 높다
테스트가 불가능하거나 까다로운 코드는 제외한다.
try-catch
while
: 무한 루프를 위한 테스트 코드는 작성하기가 어렵다.
randomeNumber()
: 랜덤 숫자를 생성하는 테스트 코드는 작성하기가 어렵다.
🎯 Right-BICEP
- Right: 결과가 올바른가?
- B: 경계 조건은 맞는가?
- I: 역 관계를 검사할 수 있는가?
- C: 다른 수단을 활용하여 교차 검사할 수 있는가?
- E: 오류 조건을 강제로 일어나게 할 수 있는가?
- P: 성능 조건은 기준에 부합하는가?
[Right]-BICEP: 결과가 올바른가?
- '코드가 정상적으로 작동한다면, 그것을 알 수 있을까?'
- 다른 관점으로, 작은 부분의 코드에 대해 행복 경로 테스트를 할 수 없다면 그 내용을 완전히 이해하지 못한 것이다.
- 모든 질문에 대답할 수 없다면 그 최선의 판단으로 테스트를 작성하라.
Right-[B]ICEP: 경계 조건은 맞는가?
- 모호하고 일관성 없는 입력 값. (ex. 특수 문자("!*W:X&Gi/w$->>$g/h#WQ@))가 포함된 파일이름
- 잘못된 양식의 데이터. (ex. fred@foobar.)
- 수치적 오버플로우를 일으키는 계산
- 비거나 빠진 값. (ex. 0, 0.0, "", null)
- 이성적인 기댓값을 훨씬 벗어나는 값. (ex. 150세의 나이)
- 중복을 허용해서는 안 되는 목록에 중복 값이 있는 경우
경계 조건에서는 CORRECT를 기억하라
- [C]conformance(준수): 값이 기대한 양식을 준수하고 있는가?
- [O]rdering(순서): 값의 집합이 적절하게 정렬되거나 정렬되지 않았나?
- [R]ange(범위): 이성적인 최솟값과 최댓값 안에 있는가?
- [R]eference(참조): 코드 자체에서 통제할 수 없는 어떤 외부 참조를 포함하고 있는가?
- [E]xistence(존재): 값이 존재하는가?(non-null, non-zero, 집합에 존재하는가? 등)
- [C]ardinality(기수): 정확히 충분한 값들이 있는가?
- [T]ime(절대적 혹은 상대적 시간): 모든 것이 순서대로 일어나는가? 정확한 시간에? 정시에?
Right-B[I]CEP: 역 관계를 검사할 수 있는가?
수학에서 덧셈의 검증으로 뺄셈을 사용하고, 나눗셈의 검증을 곱셈을 사용하는 것처럼
교차 검사를 통해 전체를 찾는다.
ex. 긍정 사례 답변들 + 역 사례 답변들 = 전체
Right-BI[C]EP: 다른 수단을 활용하여 교차 검사할 수 있는가?
ex. 제곱근을 제공해주는 자바 라이브러리 사용해 내가 만든 제곱근 메서드와 같은지 비교해본다.
ex. 대출된 도서와 대출되지 않은 도서의 합은 도서의 총 수량과 같아야 한다.
클래스의 서로 다른 조각 데이터를 사용하여 모든 데이터가 합산되는지 확인해보는 것.
Right-BIC[E]P: 오류 조건을 강제로 일어나게 할 수 있는가?
- 메모리가 가득 찰 때
- 디스크 공간이 가득 찰 때
- 클라이언트 시간과 서버 시간이 다를 때 (벽시계 시간에 관한 문제들)
- 네트워크 가용성 및 오류들
- 시스템 로드
- 제한된 색상 팔레트
- 매우 높거나 낮은 비디오 해상도
로직 전체에 대한 커버리지를 달성하는 것이 아닌 예상하지 못 한 조건도 핸들링 해야 한다.
Right-BICE[P]: 성능 조건은 기준에 부합하는가?
추축만으로 성능 문제에 대응하기 보단 단위 테스트의 결과로 대응하라. 모든 성능 최적화 시도는 실제 데이터로 해야 하며 추측을 기반으로 해서는 안 된다. 성능이 핵심 고려 사항이라면 단위 테스트보다는 좀 더 고수준에서 문제에 집중하고 싶을 것이고, JMeter 같은 도구를 사용하길 원할 수도 있다.
JUnitPerf: 단위 수준 성능 측정 서드 파티 도구
🎯 FIRST: 좋은 테스트 조건
- Fast: 빠른
- Isolated: 고립된
- Repeatable: 반복 가능한
- Self-validating: 스스로 검증 가능한
- Timely: 적시의
Fast
- 테스트를 빠르게 유지하라.
- 단위 테스트가 느리다면 무언가 잘못된 방향으로 나아가고 있는 것이다.
- 설계를 깨끗하게 하면 빠르게 유지가 가능하다.
- 더 자주
merge
하여 변경 사항이 나머지 시스템에 어떤 영향을 주는지 파악하라.
Isolated
- 좋은 단위 테스트는 다른 단위 테스트에 의존하지 않는다.
- 단위 테스트를 독립적으로 유지하라.
- 테스트 메서드가 하나 이상의 이유로 깨진다면 테스트를 분할하는 것을 고려하라.
Repeatable
- 실행할 때마다 결과가 같아야 한다.
- 통제할 수 없는 요소를 격리하고 독립성을 유지하라.
- 각 테스트는 항상 동일한 결과를 만들어 내야 합니다.
Self-validating
- 테스트는 기대하는 것이 무엇인지 단언해야한다.
- 테스트는 스스로 검증할 뿐만 아니라 준비할 수도 있어야 한다.
Timely
- 적합한 시간에 테스트 코드를 작성해야 한다.
- 팀 규칙을 세우는 것도 좋은 방법이다.