🌱 들어가기 전
이번 포스팅에서는 빌더 패턴과 엔티티, 그리고 테스트에 대한 여러가지 조언의 내용을 살펴보자.
- 김우근님의 'Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트'를 공부하고 정리한 글입니다. ✏️
지난 포스팅과 이어집니다 !
https://deeper-dev.tistory.com/12
[테스트 코드와 설계] 의존성 주입과 의존성 역전을 활용하여 테스트 가능성 높이기
🌱 들어가기 전이번 포스팅에서는 테스트에 활용되는 의존성 주입과 의존성 역전, 그리고 테스트 가능성을 높이는 과정을 살펴보자. - 김우근님의 'Java/Spring 테스트를 추가하고 싶은 개발자들
deeper-dev.tistory.com
🌱 빌더 패턴
생성자가 지나치게 많아지는 문제를 해결할 수 있는 유연한 해결책
객체 생성이 다양해지는 문제를 해결하기 위해 마련된 유동적인 해결책이다.
생성자가 많아지면 어지러워진다. 그만큼 유지보수해야하는 관리 포인트가 계속 늘어나는 것이다.
💭 단점
- 개발자가 일부 파라미터를 누락하게하는 실수를 만들기도 한다.
💭 테스트에 대한 빌더 패턴의 장점
- 테스트에 필요한 파라미터만 지정할 수 있게하면서 가독성을 늘려준다.
- 만약 생성자로 테스트를 짰다고 가정하자. 클래스에 필드 하나가 추가된다면, new 생성자를 쓰고있는 모든 테스트가 컴파일 에러를 뱉는다.
그리고 결국 내가 작업하고 있는 내용과 관련없는 테스트 파일들이 모두 수정되어야 해서 PR 크기가 영향을 받는다. 빌더 패턴을 사용하면 이런 상황을 예방할 수 있다.
🌱 엔티티
도메인 엔티티, 영속성 객체, DB 엔티티...
💭 엔티티에 대한 잘못된 오해
엔티티는 jpa랑 상관없다.
💭 도메인 엔티티와 DB 엔티티는 다르다.
도메인 엔티티
소프트웨어에서 어떤 도메인이나 문제를 해결하기 위해 만들어진 모델.
비즈니스 로직을 들고 있고, 식별 가능하며, 일반적으로 생명 주기를 갖는다.
DB 엔티티
데이터베이스 분야에서 개체 또는 엔티티라고 하는 것은 데이터베이스에 표현하려고 하는 유형, 무형의 객체로써 서로 구별되는 것을 뜻한다.
💭 강사 개인적인 해석
같은 목적을 해결하기 위해 다른 영역에서 엔티티라는 개념을 사용해 왔는데, 그 해결책이 매우 유사하면서도 달랐다.
- 도메인 엔티티 → class User : 객체지향 진영에서는 엔티티가 class로 표현된다.
- DB 엔티티 → table User : 데이터베이스 진영에서는 엔티티가 테이블로 표현된다.
그래서 둘을 맵핑해주는 역할만 전담하는 다른 엔티티가 필요하게 되었다.
실세계에서는 서비스를 만들려면 도메인 엔티티와 DB 엔티티가 협업해야 한다. = 영속성 객체: JPA + Hibernate
ORM: Object Relational (database) Mapping
@Entity
public class UserEntity {
@Column
private String name;
@Column
private int age;
}
- 도메인 엔티티: 비즈니스 영역을 해결하는 모델
- 영속성 객체: ORM (관계형 데이터베이스에 있는 데이터를 객체로 맵핑해주는 것)
- DB 엔티티: RDB에 저장되는 객체
도메인 모델에 @Entity를 달아서 영속성 객체랑 구분하지 않고 사용하는 방식도 있다. 결국 호불호의 영역이다.
강사는 도메인 엔티티와 영속성 객체가 구분되어야 한다는 입장이다. 즉 도메인 엔티티에 @Entity가 붙는 것을 반대한다.
왜냐하면, 도메인 엔티티에 JPA가 붙는 순간 우리 시스템인 RDB에 종속된다는 이야기이기 때문이다.
생각해보자. 만약, RDB를 사용하지 않고 mongoDB를 사용하면 어떨까?
mongoDB에는 Entity라는 개념대신, Document라고 불리는 대응개념이 있다. DocumentDB는 jpa를 따르지 않는다. 왜냐햐면 jpa에 존재하는 다수의 용어들이(Entity, Column, Table...) RDB에서 파생되었기 때문에 DocumentDB에는 쓰기 적합하지 않기때문이다.
🌱private 메소드는 테스트하지 않아도 된다.
private 메소드를 테스트하고 싶은 느낌이 든다면, 사실 private 메소드가 아니어야 한다는 의미거나, 다른 클래스로 분리/책임을 위임하여 public으로 만들라는 신호다.
즉, 책임이 제대로 할단된게 아닐 수도 있다. 테스트가 보내는 신호이다. 설계를 개선하자.
🌱 final 메소드를 stub하는 상황을 피해야 한다.
final 메소드를 stub해야하는 상황이 생긴다면, 설계가 잘못된 상황이다. (테스트가 보내는 신호)
final로 선언했다는 자체가 더 이상 변경하지 않겠다는 것이다. 그래서 이걸 강제로 stub시키겠다는 자체가 말이 안되는 것이다.
ex)
User → final class UUID
: User가 UUID를 직접 의존하면, 테스트할 때 Mock으로 대체시키고 싶겠지만 UUID가 final이라 mock 자체가 안된다.
왜냐하면 final class란 이 클래스를 대체 할 수 없게 하겠다는 선언이기 때문이다.
이 상황 자체가 잘못됐다는 뜻이다. 의존성 역전으로 완충재를 두어서 해결해야 한다.
User → UuidHolder <<interface>> ← SystemUuidHolder → final class UUID
:인터페이스를 중간에 두고 구현체가 UUID를 의존하게 해서 간접의존하게 하는 방식이다.
User → UuidHolder <<interface>> ← MockUuidHolder
: 지정한 값을 내려주도록 할 수 있다. 테스트 할 때는 지정한 값만 내려주는 Mock을 사용하게하는 방식이다.
관련 내용은 아래 포스팅의 '의존성과 테스트' 항목에 자세히 설명해두었다.
https://deeper-dev.tistory.com/12
🌱 DRY가 아니라 DAMP!
- DRY(Don't Repeat Yourself): 반복하지 않기
- DAMP (Descriptive And Meaningful Phrase): 서술적이고 의미 있는 문구.
중복이 되더라도 가독성이 좋은게 낫다.
🌱 논리 로직을 피하라
테스트에서는 +, for, if ... 같은 로직을 피하라.
@Test
public void shouldNavigateToAlbumsPage () {
String baseUrl = "http://photos.google.com/";
Navigator nav = new Navigator(baseUrl);
nav.goToAlbumPage();
assertThat(nav.getCurrentUrl()).isEqualTo(baseUrl + "/albums");
}
버그를 찾을 수 있을까?
assertThat(nav.getCurrentUrl()).isEqualTo("http://photos.google.com//albums"); // 실제 검증하는 url 주소에 잘못된 값이 들어간다. com뒤에 '/' 2개
'+'로 인해서 테스트 코드에서는 예측하지 못하는 버그가 생기는 경우도 있다.
테스트 코드가 오래, 그리고 많이 실행되려면 직관적이고 바로 이해할 수 있게 짜는게 좋다.
'Back-End > Test Code' 카테고리의 다른 글
[트러블 슈팅 - 테스트 코드] 테스트 간 데이터 간섭과 트랜잭션 문제 해결 - @SpringBootTest 테스트 격리시키기 (2) | 2024.09.08 |
---|---|
[테스트 코드와 설계] 프로젝트 Test Code 작성과 문제 해결 (0) | 2024.09.04 |
[테스트 코드와 설계] 의존성 주입과 의존성 역전을 활용하여 테스트 가능성 높이기 (0) | 2024.09.03 |
[테스트 코드와 설계] 테스트에 대한 개발자의 고민과 이론 및 개념 (1) | 2024.09.03 |
[테스트 코드와 설계] 테스트 코드 작성과 리팩토링 (0) | 2024.08.30 |