백엔드 클린코드, 테스트코드 회고 3주차
해당 회고는 'Practical Testing: 실용적인 테스트 가이드' 강의에 대한 회고입니다.
시작
이번주부터는 본격적으로 테스트 코드에 대한 강의를 듣게 되었습니다. 테스트 코드에 대해서 적용은 하고 싶지만 접근 방법에 대해서 몰랐던 저에게 한줄기 희망이 된 강의입니다.
배운 것들
3주차에서는 테스트가 왜 필요한지, 사용한다면 어떤 이점이 있는지에 대한 학습을 위주로 했습니다.
테스트 필요성
테스트 코드가 없는 경우
production code가 커짐에 따라 커버할 수 없는 영역 발생
변화가 생기는 순간마다 발생가능한 케이스를 모두 고려해야 한다
그에 따른 피드백은 느려지고 이는 유지보수의 어려움으로 이어진다, 소프트웨어의 안정성을 보장할 수 없다
따라서 테스트코드를 작성하는 것이 좋다
테스트 코드를 작성할 경우
개발 코드에 대한 빠른 피드백을 얻을 수 있으며 자동화를 통해 시스템이 코드를 검증할 수 있도록 할 수 있다
이를 통해 서비스의 안정성을 보장할 수 있다
이런 이유에서 테스트 코드를 잘 작성하는 것은 매우 중요하다
무엇을 검증하는지 모호하고 혹은 너무 복잡한 테스트 코드라면 무용지물이 될 수 있다. 혹은 잘못된 검증이 이루어질 수 있다
좋은 테스트 코드는 다음과 같다
테스트 코드가 자동화되어 빠른 시간에 버그를 발견할 수 있어 사람이 수동 테스트하는 비용을 줄여준다
내가 남긴 코드와 테스트에 대해 다른 팀원에게도 공유되어 팀 전체의 공유 자산으로 활용된다
짤 때는 귀찮은데 사실 이것이 가장 빠르고 확실하고 좋은 길이다
문서로써 테스트
프로덕션 기능을 설명
다양한 테스트 코드를 통해 프로덕션 코드를 이해하는 시각과 관점 보완
과거에 경험했던 고민의 결과물을 팀 차원으로 승격시켜서 모두의 자산으로 공유 가능!
@Test
void add() {
CafeKiosk cafeKiosk = new CafeKiosk();
cafeKiosk.add(new Americano());
System.out.println("담긴 음료 수 : " + cafeKiosk.getBeverages().size());
System.out.println("음료 상세 : " + cafeKiosk.getBeverages().get(0).getName() + ": " + cafeKiosk.getBeverages().get(0).getPrice());
}
이런 테스트 코드의 경우 문제가 있다
어쨋든 마지막에 사람이 직접 확인해야한다
다른 사람이 봤을 때 어떤 것이 맞는 테스트인지 틀린 테스트인지 알기 힘들다
심지어 이 코드는 틀릴 일이 없다… 단순한 콘솔 찍기이므로..
이런 수동 테스트는 효율적이지도 못하고 테스트의 역할조차 제대로 하지 못한다
자동화된 테스트로 코드 검증을 자동으로 해주어 개발자의 코드검증 개입을 최소화 해보자
단위테스트
단위테스트란
클래스나 메서드와 같이 작은 단위를 독립적으로 테스트
테스트 속도가 빠르고 안정적
테스트를 위한 프레임워크와 라이브러리 이용
테스트 세분화하기
암묵적인 요구사항이 있는지 먼저 확인
최대한 많은 상황을 커버하기 위해 테스트 세분화는 중요
세분화
해피 케이스 → 생각한대로 잘 작동하는지 (ex. 아메리카노를 2잔 주문했다면 주문내역에 2잔이 들어갔는지)
예외 케이스 → 작동하지 않는 경우 (ex. 아메리카노 105잔 주문은 불가능)
경계 테스트 → 범위, 구간, 날짜에 대해서 정상동작 범위를 명확히 하면 해피 케이스와 예외 케이스의 테스트 생성을 올바르게 할 수 있다.
(ex. 3이상까지 정상 동작하는 코드라면 해피케이스로 3, 예외 케이스로 1을 두어 테스트)
경계값에서 테스트하는 것이 중요하다
경계값에서 설정한 예외와 설정한 메시지가 잘 던져지는지 확인하자
테스트하기 어려운 영역을 구분하고 분리하자
테스트 가능한 코드에 테스트가 불가능한 부분이 들어온다면 전체 코드가 테스트 불가능하게 된다. 따라서 테스트하고자 하는 영역을 잘 생각해야 한다.
테스트 영역을 어디까지 분리하는 것이 좋을까?
기본적으로는 모호하고 어려운 부분을 외부로 분리할 수록 테스트를 할 수 있는 범위는 늘어난다. 하지만 당연히 적당한 멈추는 선이 있을 것이다.
정확하게 어려운 영역이 무엇인지 체크해보고 분리범위에 대해서 알아보자.
테스트 어려운 영역
테스트마다 다른 값에 의존하는 코드
날짜, 시간, 랜덤 값, 전역 변수나 함수, 사용자 입력
외부 세계에 영향을 주는 코드
표준 출력, 메시지 발송, DB기록 등
테스트 쉬운 영역 (순수 함수)
같은 입력에 항상 같은 결과
외부 세상과 단절된 형태
테스트하기 쉬운 코드
TDD
Test Driven Development
테스트 코드를 먼저 작성하고 이후에 프로덕션 코드를 작성해서 테스트 코드를 중심으로 서비스가 구현되는 방법론
cf. 애자일 소프트웨어 개발 선언에서 켄트 백이 소프트웨어 개발 방법으로 XP(extream programming)을 제안했고 이것을 실천할 수 있는 방법 중 하나가 TDD
TDD과정
실패할 수밖에 없는 테스트 작성 (RED)
테스트를 통과하기 위한 최소한의 코드 작성 (돌아가기만 하면된다. GREEN)
테스트 통과를 유지하면서 작성한 코드를 개선해 나감 (REFACTOR)
TDD의 핵심
내가 작성하는 프로덕션 코드에 대해서 빠르고 자주 피드백을 받을 수 있다
결국 테스트를 잘하기 위한 구조를 고민하게 된다. 시간을 분리했던 코드와 같이 시간이 테스트하기 어려운 부분이라는 것을 먼저 캐치하고 미리 외부로 분리하는 구조를 선택!
선 테스트 작성 후 기능 구현
테스트 가능한 코드로 구현하면서 복잡도가 낮고 유지보수가 쉬운 구현이 가능해진다
발견하기 어려운 엣지 케이스를 놓치지 않게 해준다
구현에 대한 빠른 피드백 가능
과감한 리팩토링 가능
TDD는 구현부가 테스트와 상호작용하며 발전한다
TDD는 클라이언트 관점에서 피드백을 준다
DisplayName과 BDD
@DisplayName
junit5에서 추가된 어노테이션
해당 테스트가 어떤 역할을 하는지 명시해줄 수 있다
명사의 나열보다 문장으로 쓰자
“~테스트” 형식은 지양하자
테스트에 대한 결과까지 써주는 것이 좋다
BDD
TDD에서 파생
함수 단위 테스트가 아닌 시나리오 기반 테스트케이스에 집중해서 테스트한다
개발자가 아니어도 이해할 수 있을 정도의 추상화 수준 권장
작성 방법
Given 어떤 환경에서
When 어떤 행동을 진행했을 때
Then 어떤 상태가 일어난다
Persistence Layer 테스트
Persistence Layer?
Data Access 역할을 한다
비즈니스 관련 작업을 하는 로직이 포함되어서는 안된다
data에 대한 CRUD에만 집중한 레이어
Repository Layer에 대한 테스트
JpaRepository를 상속하는 경우 쿼리 메서드명을 잘 지을 경우 틀릴 일이 없는데 왜 테스트를 진행할까?
→ 간단한 쿼리라면 가시적으로 충분히 확인할 수 있지만 where절의 조건 때문에 메서드명이 길어지거나 파라미터를 잘못 작성할 수도 있다. 날 것 그대로 쿼리를 작성하는 JPQL이나 QueryDSL을 사용하는 경우도 있다
따라서 현재 내가 작성한 쿼리가 잘 동작하는지 테스트하는 것도 있지만 이후 다른 방법으로 사용될 쿼리를 미리 검증하기 위해서도 필요하다.
Repository 테스트의 경우 서버를 띄워서 하지만 DB에 엑세스하는 부분만 보기 때문에 기능 별로 테스트하는 단위 테스트의 느낌이 있다
cf. 테스트가 통과할거라고 예상하고 테스트했지만 에러 발생
java.lang.AssertionError:
Expected size: 2 but was: 4 in:
[sample.cafekiosk.spring.domain.product.Product@7bb996e0,
sample.cafekiosk.spring.domain.product.Product@589cc8eb,
sample.cafekiosk.spring.domain.product.Product@44233014,
sample.cafekiosk.spring.domain.product.Product@6888a33]
아마 설정파일이 디폴트가 local로 되어 있는데 local 설정은 디비 초기화 후 data.sql이 자동실행되어 이미 저장된 데이터들 때문에 에러가 발생하는 것 같다
해당 테스트를 실행할 설정 프로파일을 data.sql이 적용되지 않는 다른 프로파일로 지정해주자!
테스트 클래스에 @ActiveProfiles(”적용할 설정 프로파일이름”)만 해주면 된다
마무리 회고
이것저것 할 것이 많은 한 주였습니다. 잘 마무리하고 마지막주인 다음주까지 열심히 달리고 싶습니다.
워밍업 클럽뿐만 아니라 배운 것들을 정리하면서 기록하는 것이 중요하다는 것을 크게 느낍니다. 앞으로도 차곡차곡 쌓아갈 수 있었으면 좋겠습니다. 앞으로도 화이팅!
댓글을 작성해보세요.