[인프런 워밍업 클럽 백엔드 스터디 2기] 3주차 발자국
워밍업 클럽 백엔드 스터디 2기 3주차 발자국 입니다.
본격적인 TDD 가이드에 대하여 배우기 시작했습니다.
테스트는 왜 필요할까?
테스트 = 귀찮다?
프로그램이 커지면 수동테스트의 정확도가 어떨까?
사람이 할거야?
변화가 생기는 매순간마다 모든 팀원이 동일한 고민을 해야한다면 소프트웨어의 신뢰도가 낮아질 가능성이 높다.
올바른 테스트 코드
자동화 테스트로 비교적 빠른 시간 안에 버그를 발견할 수 있고, 수동 테스트에 드는 비용을 크게 절약할 수 있다.
소프트웨어의 빠른 변화를 지원한다.
팀원들의 집단 지성을 팀 차원의 이익으로 승격시킨다.
가까이 보면 느리지만, 멀리 보면 가장 빠르다.
단위테스트란
작은 코드(클래그 or 메서드) 단위를 독립적으로 검증하는 테스트
단위테스트는 검증속도가 빠르고 안정적이다.
우리는 JUnit5 와 AssertJ 를 사용하여 테스트 코드를 작성해보았다.
테스트 케이스 세분화하기
질문하기 : 암묵적이거나 아지 드러나지 않은 요구사항이 있는가?
해피케이스와 예외케이스에 대하여 테스트 케이스를 세분화 해야한다.
-> 경계값 테스트라는 개념을 배웠다.
어떠한 정수가 있는데 정수가 3이상일 때, A라는 조건이 성립한다면?
⇒ 테스트를 4나 5로 테스트 하는게 야니고 3에 대하여 테스트를 작성하는게 맞다.
⇒ 예외 케이스는 2나 1이나 -1과 같은 경계값보다 아래의 경우의 수로 예외케이스를 짜는게 맞다.
테스트하기 어려운 영역 분리하기
외부세계에 관련된 도메인은 테스트하기 어려우니 분리하여 가능한 것에 집중한다.
input 과 output이 외부세계에 영향을 주거나, 외부세계에 영향을 받는 경우에는 테스트하기 어렵다.
ex) 표준출력, 메시지발송, 데이터베이스에 기록하기 등..
강의에서는 "현재시간" 은 테스트를 돌리는 시간이 바뀜에 따라 매번 결과값이 달라질 수 있으므로 정확한 테스트가 어려워 프로덕트 코드에는 현재시간을 매개변수로 넣고, 테스트할 때는 테스트 하고자 하는 시간을 매개변수로 세팅하여 분리한다.
즉, 테스트 어려운 조건이 생기면 테스트 어려운 영역을 외부로 분리하여 조치한다.
TDD
프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 방법론
RED - GREEN - REFACTOR
위 과정으로 TDD가 이루어진다. 실패케이스를 먼저 짜고, 통과를 위한 통과 케이스를 짜고, 리팩토링하고 이를 반복한다.
선기능 구현, 후 테스트의 문제점
테스트 자체의 누락 가능성(아 테스트 짤 시가니 없네)
특정 테스트 케이스만 검증할 가능성(해피케이스만 생각)
잘못된 구현을 다소 늦게 발견할 가능성
테스트는 [문서]다.
언어가 사고를 제한한다.
프로덕션 기능을 설명하는 테스트 코드 문서
다양한 테스트 케이스를 통해 프로덕션 코드를 이해하는 시각과 관점을 보안
해피케이스와 예외케이스를 생각하여 더 깊게 이해할 수 있다.
어느 한 사람이 과거에 경험했던 고민의 결과물을 팀차원으로 승격시켜서, 모두의 자산으로 공유할 수 있다.
테스트케이스를 통해 고민의 결과를 공유하여 소스코드를 관리할 수 있다.
DisplayName을 섬세하게
테스트 하고자 하는 내용을 명확하게 기술하자
문장형태로 작성하자!
테스트 행위에 대한 결과까지 기술한다.
도메인 용어를 사용하여 한층 추상화된 내용을 담기
테스트의 현상 중점 기술: "특정 시간 이전에 주문을 생성하면 실패한다."
여기서는 "실패한다"는 구체적인 현상을 강조한다. 테스트가 어떤 상황에서 실패하는지를 중심으로 표현한 것이다. 하지만 이 표현은 테스트가 도메인의 규칙이나 의도를 명확하게 설명해주지 못할 수 있다. 실제 비즈니스 로직의 핵심 규칙보다는 실패 여부에 집중하게 되는 문제가 있게된다.
도메인 관점에서의 기술: "영업 시작 시간 이전에는 주문을 생성할 수 없다."
이 표현은 현상보다는 도메인의 규칙을 명확하게 기술. "영업 시작 시간 이전에는 주문을 생성할 수 없다"는 표현은, 해당 비즈니스 로직이 적용되는 이유나 규칙을 이해하는 데 도움이 되며, 비즈니스 도메인에서의 규칙을 중심으로 설명하는 방식이다.
레이어드 아키텍쳐와 테스트
Presentation Layer - Business Layer - Persistence Layer
관심사의 분리를 위해 레이어를 분리한다.
기본적인 JPA 엔티티를 설계하고 테스트하고자하는 레이어를 분리하였다.
Persistence Layer 테스트
기본적인 CRUD를 구성한 후 데이터베이스와의 연동테스트를 하였다.
EnableJpaAuditing을 통하여 테이블의 상태를 트랙킹하고 H2 테이버베이스 콘솔과 비지니스 구현 로직이 정상 작동하는지 확인하였다.
Business Layer 테스트
비지니스 로직을 구현하는 역할
Persistence Layer와의 상호작용(data를 읽고 쓰는 행위)를 통해 비지니스 로직을 전개시킨다.
트랜잭션을 보장해야한다.
도메인 로직에 대한 테스트 코드를 작성하였다. 이때 트랜잭션에 대한 관리가 중요했다.
상품을 주문할 때, 제고차감에 대한 로직에서 트랜잭션 관리의 중요성을 느꼈는데 기본적인 테스트코드에서의 @Transactional 개념은 이러하다.
트랜잭션 관리:
@Transactional
을 사용하면, 해당 테스트 메서드가 실행될 때 자동으로 트랜잭션이 시작된다. 그리고 테스트가 완료되면, 해당 트랜잭션은 롤백처리.데이터 정리: 트랜잭션이 롤백되므로, 테스트 중 데이터베이스에 삽입되거나 수정된 데이터는 테스트가 끝나면 모두 사라집니다. 따라서, 별도의 정리 작업이 필요 없게된다..
테스트 격리성: 트랜잭션 범위 내에서 테스트가 실행되고, 롤백을 통해 테스트 이전 상태로 되돌아가므로, 테스트 간의 데이터 격리성이 자연스럽게 유지된다.
이렇게 보면 무조건 트랜잭션 어노테이션을 사용하는 것이, AfterEach를 통한 수동 삭제보다 이점이 크다는 생각이 들지만, 테스트코드에만 트랜잭션이 잡혀있고 실제 프로덕션 코드에는 트랜잭션이 안 잡혀있을 수 있는 위험성이 있으므로 팀원 내부적인 공지나 개발자의 인지가 필요하다.
이런 점을 놓치면 심각한 상황이 발생할 수 있다. 테스트코드는 통과했지만 실제 운영에는 버그가 생기는 것이다. 예를 들어, 주문을 생성하고 주문에 포함된 상품이나 재고를 업데이트하는 작업이 순차적으로 실행될 때, 중간에 예외가 발생하면 앞서 실행된 작업은 롤백되지 않아 장애가 있을 것이다.
3주차 회고
Keep (만족했고, 앞으로도 지속하고 싶은 부분)
테스트 코드의 기본적인 Given When Then 방식이 익숙해짐을 느꼈다. 더불어 JPA와 Spring Data JPA에 대한 개념을 다시 한번 다지는 기회도 갖을 수 있었다. 또한, 단위테스트와 통합테스트에 대한 개념이 실무에서는 어떻게 적용되어야 하는지 인지할 수 있었다.
.
Problem (아쉬웠던 점)
기본적으로 JUnit 과 AssertJ의 메서드에 아직 친숙한 느낌은 아닌 거 같다. 그러다보니 강의를 들으며 도메인을 따라가면서 코드까지 신경쓰는 것이 어려운 부분이 있었다. 천천히 다시 한번 강의를 들으며 다시 학습하는 과정이 필요해보인다.
Try (다음에 시도해볼 점)
세부적으로 여러가지를 알려주셔서 좋았다. 다음에는 개인프로젝트로 동시성문제에 대해서 좀 더 고찰해보는 시간을 가지고 싶다. 지나가면서 말씀해주신 ptimistic lock / pessimistic lock 에 대해서도 공부해보고 싶다.
댓글을 작성해보세요.