[워밍업 클럽] BE 클린코드&테스트 3주차 발자국
강의 요약
테스트란?
테스트를 하는 이유, 필요한 이유
빠른 피드백
자동화
안정감
테스트 코드가 없다면?
변화가 생기는 매순간마다 발생할 수 있는 모든 Case를 고려해야 한다.
변화가 생기는 매순간마다 모든 팀원이 동일한 고민을 해야한다.
빠르게 변화하는 소프트웨어의 안정성을 보장할 수 없다.
테스트 코드가 병목이 된다면
프로덕션 코드의 안정성을 제공하기 힘들다.
테스트 코드 자체가 유지보수하기 어려운, 새로운 짐이 된다.
잘못된 검증이 이루어질 가능성이 생긴다.
올바른 테스트 코드란?
자동화 테스트
빠른 시간 안에 버그를 발견
비용을 절약
소프트웨어의 빠른 변화를 지원
팀 차원의 이익
단기적으론 느리지만, 장기적으로는 빠르다.
단위 테스트
작은 코드 단위를 독립적으로 검증하는 테스트
빠름
안정적
AssertJ / Fluent assertions for java
AssertJ
테스트 코드 작성을 원활하게 돕는 테스트 라이브러리
풍부한 API, 메서드 체이닝 지원
테스트 케이스 세분화하기
테스트 코드 작성 전에 암묵적이거나 아직 드러나지 않은 요구사항이 있는가? 생각해보자
금액이 0 이하인가?
수량이 0 이하인가?
해피 케이스와 예외 케이스
경계값 테스트가 중요하다.
테스트하기 어려운 영역을 분리하기
테스트 할 때마다 변경되는 값이 있다면, 테스트를 정상적으로 할 수 없다.
테스트 하고자 하는 영역을 잘 구분하자
외부로 분리할수록 테스트 가능한 코드는 많아진다.
테스트하기 어려운 영역이란?
관측할 때마다 다른 값에 의존하는 코드
현재 날짜/시간, 랜덤 값, 전역 변수/함수, 사용자 입력 등
외부 세계에 영향을 주는 코드
표준 출력, 메시지 발송, 데이터베이스에 기록하기 등
// BAD
public Order createOrder() {
LocalDateTime currentDateTime = LocalDateTime.now();
LocalTime currentTime = currentDateTime.toLocalTime();
if (currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)) {
throw new IllegalArgumentException("주문 시간이 아닙니다.");
}
return new Order(currentDateTime, beverages);
}
// GOOD
public Order createOrder(LocalDateTime currentDateTime) {
LocalTime currentTime = currentDateTime.toLocalTime();
if (currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)) {
throw new IllegalArgumentException("주문 시간이 아닙니다.");
}
return new Order(currentDateTime, beverages);
}
TDD
테스트 주도 개발 Test Driven Development
프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 개발 방법론
RED → GREEN → REFACTOR
실패하는 테스트 작성
테스트가 통과하도록 최소한의 코딩
구현 코드 개선, 테스트 통과 유지하면서 리팩토링
선 기능 구현, 후 테스트 작성 시
테스트 자체의 누락 가능성
특정 테스트 케이스만 검증할 가능성
해피 케이스
잘못된 구현을 다소 늦게 발견할 가능성
선 테스트 작성, 후 기능 구현 시
복잡도가 낮은, 테스트 가능한 코드로 구현할 수 있게 한다.
유연하면서 유지보수가 쉬움
쉽게 발견하기 어려운 엣지 케이스를 놓치지 않게 해준다.
구현에 대한 빠른 피드백을 받을 수 있다.
과감한 리팩토링이 가능해진다.
테스트 코드는 문서다
프로덕션 기능을 설명하는 테스트 코드 문서
다양한 테스트 케이스를 통해 프로덕션 코드를 이해하는 시각과 관점을 보완
어느 한 사람이 과거에 경험했던 고민의 결과물을 팀 차원으로 승격시킴
→ 모두의 자산으로 공유할 수 있다.
문서의 기능을 하기 위해서…
@DisplayName 을 섬세하게
테스트 행위에 대한 결과까지 기술하기
BAD : 음료 1개 추가 테스트
GOOD : 음료를 1개 추가할 수 있다.
BEST : 음료를 1개 추가하면 주문 목록에 담긴다!
도메인 용어를 사용하여 한층 추상화된 내용을 담기
BAD : 특정 시간 이전에 주문을 생성하면 실패한다.
GOOD : 영업 시작 시간 이전에는 주문을 생성할 수 없다.
테스트의 현상을 중점으로 기술하지 말 것
BDD
Behavior Driven Development
TDD에서 파생된 개발 방법
함수 단위의 테스트에 집중하기보다, 시나리오에 기반한 테스트케이스(TC) 자체에 집중
개발자가 아닌 사람이 봐도 이해할 수 있을 정도의 추상화 수준을 권장
Given / When / Then
Given : 시나리오 진행에 필요한 모든 준비 과정 (객체, 값, 상태 등)
When : 시나리오 행동 진행
Then : 시나리오 진행에 대한 결과 명시, 검증
어떤 환경에서 (Given)
어떤 행동을 진행했을 때 (When)
어떤 상태 변화가 일어난다 (Then)
→ DisplayName에 명확하게 작성할 수 있다.
미션 회고
읽기 쉬운 코드에서 작성하였던 지뢰찾기를 기반으로 테스트 코드를 작성하였다.
어떤 코드를 테스트 해야할 지, 어떻게 테스트 코드를 짜야할 지 깊이 고민하게 되었다.
느낀점
테스트코드에 대해 어느 정도 감이 생기고 있는 것 같다. 하지만 여전히 실무에 적용하기엔 너무 어렵다.
비지니스 로직이 변경되더라도 테스트 코드가 어느정도 내구성을 가졌으면 하는데, 이렇게 작성하는게 매우 어렵다.
또한 Given 절에서 객체를 사전준비하는 과정이 너무 번거롭다.
이를 해결할 수 있는 좋은 방법이 있을 것 같은데..
여전히 테스트 코드는 귀찮고 어렵다.
댓글을 작성해보세요.