위 그림은 assertThat은 import static org.junit.Assert.assertThat;에서 가져온 assertThat입니다. 해당 Matchers는 자동완성 기능을 제공해주지 않아 무슨 메서드가 있는지 일단 외우고 있어야 합니다. 다른 Matchers들도 어느 정도 추천 자동 완성 기능을 제공해주고 있지만 매개변수로 넘기는 방식입니다.
반면 AssertJ는 사용법은 매개변수를 넘기는 방식이 아닙니다. assertThat(coupon.isUsed()) 코드 뒤에 .을 붙여 사용하는 방식이라서 해당 함수가 무엇이 있는지 명확하게 알려줍니다. 그리고 AssertJ는 다른 Matchers에 비해 테스트하기 편리한 메서드들을 제공해줍니다. JUnit을 사용하고 있다면 Matchers는 AssertJ를 적극 추천합니다.
여기서 중요한 것은 Given 절에서 Request Body가 받을 DTO를 만들어야 하는 점인데. 실제 값 바인딩은 리플렉션 기반으로 처리되니 별다른 생성자를 만들 필요가 없습니다. 하지만 테스트 코드에서는 json 값을 넘겨줘야 하기 때문에 테스 코드 작성을 위 헤서 아래와 같은 생성자를 만들어야 합니다.
이런 식으로 테스트를 진행하면 동일 패키지에서 밖에 접근하지 못하는 코드이므로 테스트 코드만을 위한 코드이지만 영향을 최소한으로 할 수 있습니다.
주의해야 할 점은 생성자가 추가됐기 때문에 반드시 기본 생성자를 하나 만들어야 합니다. 이때 접근 제어 지시자는 private 접근 지시자를 통해서 최소한으로 만들어 줍니다. 항상 접근 제어 지시자는 되도록 낮은 것을 사용하는 습관을 갖는 것이 좋습니다.
Setter를 추가하는 방법도 있겠지만 Setter는 Request, Response 객체 같은 경우에는 최대한 지양하는 것이 좋습니다. 이런 방식은 코드 양이 많기도 하고 애초에 default 생성자라도 있는 것이기 때문에 현재는 RequestBody 객체가 복잡하면 JSON 기반 테스트를 진행하고 그렇지 않은 경우에는 그냥 public 생서자 or Builder를 이용해서 Given 절을 작성하고 있습니다.
Order 객체를 테스트하기 위해서는 Order 객체를 만들어야 합니다. 그리고 Order 객체는 시간에 따라서 데이터들이 달라집니다. 주문 신청부터 주문 완료까지 Order 객체는 계속해서 변경됩니다.
적절하게 단위 테스트하기 위해서는 데이터를 특정 시점처럼 만들어야 합니다. 위 테스트도 주문 완료를 테스트하기 위해서 주문 생성 -> 주문 배송 중으로 변경해야 합니다. 이럴 경우 Setter를 쓰면 모든 비니지스 로직을 무시하고 데이터를 특정 시점으로 변경시킬 수 있습니다.
위 도메인은 상당히 간단한 편이지만 주문, 환불, 주문 부분 취소, 주문 전체 취소 등 다양한 도메인들을 테스트하기 위해서는 특정 시점으로 변경하기 어렵습니다. 위처럼 방어적 로직이 있어 모든 조건이 만족할 때만 데이터를 변경하도록 하기 때문입니다.
그래서 나름의 결론은 @Setter를 사용하고 test 코드 작성 시에만 Setter를 사용한다 정도입니다. 객체지향 관점에서 아무 로직 없는 Setter는 객체 간의 협력관계에서 객체의 자율성을 심하게 훼손시킵니다. 테스트 코드 이외에는 Setter를 사용하지 않아야 한다는 것이 지금의 결론입니다.
그래도 명확한 가이드가 없는 건 사실입니다. test 디렉터리에서만 Setter 메서드를 사용하지 않는다라는 팀 차원의 공유만 있을 뿐 그것을 강제할 방법은 없습니다.
통합 테스트에서 어려운 점들이 Given 작성하는 것입니다. 페이징 및 필터 관련된 API를 테스트한다고 했을 경우 데이터를 Set up 해주기가 어렵습니다.
만약 주문 생성 API를 테스트하기 위해서는
회원
상품
상품 카테고리
해당 상품의 입점사 정보
쿠폰 (만약 쿠폰을 사용했을 때 필요)
아무리 간단하게 생각해도 최소한 이 정도의 데이터를 Set up 해야 합니다. 실제 오픈 커머스 같은 경우에는 주문 한 번을 하기 위해서 수십 개의 테이블들을 Set up 해야 합니다. 테스트를 위해서 객체를 만들고 JPA로 데이터를 Set up 한다면 차라리 저는 테스트를 포기하겠습니다.
SQL로 given을 구성하게 되면 테스트하고자 하는 부분의 when, then 구절은 어렵지 않게 작성할 수 있습니다. 또 비즈니스 로직과 상관없이 데이터를 특정 시점으로 만들 수 있습니다.
하지만 단점도 있습니다. 칼럼의 변경 및 확장 시 테스트 코드의 지속적인 관리가 어렵다는 점이 있습니다. 또 누군가의 member insert를 추가적으로 하면 .andExpect(jsonPath("numberOfElements").value("4")) 테스트 코드는 실패하게 됩니다. 모든 작업자들이 data.sql을 추가 및 변경하다 보면 문제가 계속 생기게 됩니다.
결론이라고 말하기는 어렵지만 테스트 코드를 작성할 때 Given을 만들기 위해서 많은 어려운 점이 있습니다. 지금의 제 결론은 Given을 최대한 편리하게 작성하는 방법과 의존도를 낮게 코딩해서 최대한 단위 테스트를 하기 쉽게 의도적으로 설계하고 코딩하는 편이 좋다고 생각합니다.