> For the complete documentation index, see [llms.txt](https://develop-footprint.gitbook.io/o/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://develop-footprint.gitbook.io/o/programming/clean-architecture/clean-book/5.md).

# 아키텍처 요소 테스트

### 테스트 피라미드

<figure><img src="https://946524419-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZqOTWctN1y9hEXEl4sKd%2Fuploads%2F869ynPLfvRRoTVlCLeHH%2FUntitled.png?alt=media&amp;token=e7cffe37-a005-44b9-9ce5-11d68b2e8386" alt="" width="375"><figcaption></figcaption></figure>

* 만드는 비용이 적고 유지보수하기 쉽고 빨리 실행되고 안정적인 작은 크기의 테스트들에 대해 높은 커버리지를 유지하는 것이 중요하다.
* 테스트가 비싸질수록 테스트의 커버리지 목표는 낮게 잡아야 시간을 덜 쓸 수 있다.
* 단위테스트
  * 피라미드의 토대, 하나의 클래스를 인스턴스화하고 클래스의 인터페이스를 통해 기능을 테스트
  * 다른 클래스에 의존하면 인스턴스화 대신 mock으로 대체한다
* 통합테스트
  * 연결된 여러 유닛을 인스턴스화하고 시작점이 되는 클래스의 인터페이스로 데이터 보낸 후 유닛들이 잘 동작하는지 검증
* 시스템 테스트
  * 애플리케이션을 구성하는 모든 객체 네트워크를 가동해 특정 유스케이스가 전 계층에서 동작하는지 검증
  * UI를 포함하는 end-to-end 테스트층이 있을 수 있다.

### 도메인 엔티티 테스트

* 단위테스트를 사용해 간단하게 테스트할 수 있다.
* 비즈니스 규칙을 검증하기에 적절하다.
* 도메인 엔티티의 행동은 다른 클래스에 거의 의존하지 않아 단위 테스트 말고 다른 종류의 테스트는 필요하지 않다.

### 유스케이스 테스트

* 단위테스트이긴 하지만 의존성의 상호작용을 테스트하므로 통합테스트라는 의미에 가깝지만 실제 의존성 관리를 하지 않으므로 완전한 통합테스트는 아님
* 행동-주도 개발(behavior driven develpment)에서 일반적으로 사용되는 방식대로 `given/when/then` 으로 나눈다.
* Mockito를 사용해 목 객체를 생성하고 메서드 행동을 정의할 수 있다.
* 테스트 중인 서비스는 상태가 없어 `then` 에서 특정 상태를 검증할 수 없다.
* **서비스가 의존 대상의 특정 메서드와 상호작용했는지 여부를 테스트**하는것이 목적
* 테스트가 코드의 행동 변경뿐만 아니라 구조 변경에도 취약해지므로 모든 동작을 검증하는 대신 핵심만 골라 테스트하는 것이 좋다.
* 모든 동작을 검증하려하면 클래스가 조금 바뀌어도 테스트를 변경해야 하므로 테스트의 가치가 떨어진다.

### 웹 어댑터 테스트

* `@MockMvcTest` 를 사용해 웹 컨트롤러 테스트하는 방법을 사용한다.
* 위 어노테이션을 사용하면 스프링이 특정 요청 경로, 자바와 JSON 매핑, HTTP 입력 검증에 필요한 전체 객체 네트워크를 인스턴스화하므로 **통합 테스트이다.**
* JSON 문자열 등의 형태로 HTTP를 통해 입력받고 → 유효성 검증하고 → 유스케이스 포맷으로 매핑하고 → 유스케이스로 전달하는 과정을 테스트한다.
* 웹 컨트롤러는 스프링 프레임워크에 강하게 묶여있으므로 이 프레임워크와 통합된 상태로 테스트하는 것이 합리적이다.

### 영속성 어댑터 테스트

* 단순 어댑터 로직 뿐만 아니라 데이터베이스 매핑도 검증하므로 통합 테스트를 적용한다.
* `@DataJpaTest` 어노테이션으로 데이터베이스 접근에 필요한 객체 네트워크를 인스턴스화 해야한다는 것을 스프링에게 알려준다.
* `@Import` 어노테이션을 추가해서 특정 객체가 이 네트워크에 추가됐다는 것을 표현한다.
* `@Sql("XXX.sql")` 어노테이션을 사용해 데이터베이스를 특정 상태로 만들 수 있다.
* 데이터베이스를 모킹하지 않고 실제로 접근
* 실제 연동했을 때 SQL 구문의 오류나 테이블과 자바 객체 간의 매핑 에러로 문제 생길 확률이 높아진다.
* 따라서 프로덕션 환경에서는 실제 데이터베이스를 대상으로 테스트 진행해야 한다.

  > Testcontainers같은 라이브러리는 필요하느데이터베이스를 도커 컨테이너에 띄울 수 있어 유용

### 주요 경로 테스트

* 전체 애플리케이션을 띄우고 API를 통해 요청 보내고 모든 계층이 동작하는지 시스템 테스트를 진행
* 유스케이스의 시스템 테스트
* `@SpringBootTest` 어노테이션 사용해 스프링이 애플리케이션을 구성하는 모든 객체 네트워크를 띄우고, `webEnvironment = WebEnvironment.RANDOM_PORT` 한다.
* TestRestTemplate을 사용하면 실제 HTTP 통신을 통해 요청을 보낸다.
* 다른 시스템과 통신하는 애플리케이션의 경우 일부 출력 포트 인터페이스만 모킹하면 된다.
* 헬퍼 메서드를 사용해 여러 상태 검증 시 사용할 수 있는 Domain Specific Language 를 형성
* 사용자 관점에서 애플리케이션을 검증할 수 있다.
* 단위 테스트와 통합 테스트를 만들었다면 시스템 테스트는 앞서 커버한 코드와 겹치는 부분이 많지만, 단위/통합테스트로는 못잡는 계층간 매핑 버그 등을 찾아낼 수 있다.
* 여러 유스케이스를 결합해 시나리오를 만들어 시나리오들을 커버한다면 최신 변경 사항에도 애플리케이션이 망가지지 않았음을 보장할 수 있다.

### 테스트 커버리지

* 라인 커버리지는 테스트 성공을 측정하는 데 있어서 잘못된 지표
* 테스트의 성공 기준은 얼마나 마음편하게 소프트웨어를 배포할 수 있는지이다.
* 자주 배포할수록 테스트를 더 신뢰할 수 있다.
* 프로덕션의 버그를 수정하고 이로부터 배우는 것을 우선순위로 하다보면 점점 방대한 케이스를 커버할 수 있는 테스트가 될 것
