다시 읽는게 좋을 것 같다. 🥲
요약
-
테스트 대역 : 테스트에서 가짜 의존성의 모든 유형을 설명하는 포괄적인 용어
-
목 (스파이) : 외부로 나가는 상호 작용(SUT에서 의존성으로의 호출로, 해당 의존성의 상태를 변경)을 모방하고 검사하는 데 도움이 된다.
- 명령 조회 분리 원칙에 따라 명령에 해당하는 부분을 대체하는 것은 목이다.
-
스텁 (더미, 페이크) : 내부로 들어오는 상호작용(SUT가 해당 의존성을 호출해 입력 데이터를 가져옴)을 모방하는 데 도움이 된다.
- 스텁과 상호작용을 검증하면 취약한 테스트로 이어진다.
- 명령 조회 분리 원칙에 따라 쿼리에 해당하는 부분을 대체하는 것은 스텁이다.
-
-
식별할 수 있는 동작
- 클라이언트가 목표를 달성하는 데 도움이 되는 연산을 노출
- 클라이언트가 목표를 달성하는 데 도움이 되는 상태를 노출
- 잘 설계된 코드는 식별할 수 있는 동작이 공개 API와 일치하고 구현 세부 사항이 비공개 API 뒤에 숨겨져 있는 코드다. 공개 API가 식별할 수 있는 동작 이상으로 커지면 코드는 구현 세부 사항을 유출한다.
- 캡슐화는 코드를 불변성 위반으로부터 보호하는 행위다.
-
헥사고날 아키텍처는 다음과 같은 세 가지 관점을 강조한다.
- 도메인과 애플리케이션 서비스 계층 강의 영향 분리
- 애플리케이션 서비스 계층에서 도메인 계층으로의 단방향 의존성 흐름.
- 외부 애플리케이션은 애플리케이션 서비스 계층이 유지하는 공통 인터페이스를 통해 연결된다. 아무도 도메인 계층에 직접 엑세스할 수 없다.
- 애플리케이션에는 시스템 내부 통신과 시스템 간 통신이라는 두 가지 통신 유형이 있다. 외부 시스템을 제외하고 시스템 간 통신은 식별할 수 있는 동작이다.
- 시스템 내 통신을 검증하고 목을 사용하면 취약한 테스트(리팩터링 내성이 없는 테스트)로 이어진다.
목과 스텁의 구분
- 테스트 대역의 주 용도는 테스트를 편리하게 하는 것
-
테스트 대역의 유형
-
목 (목, 스파이) : 외부로 나가는 상호 작용을 모방하고 검사하는 데 도움
- 이러한 상호 작용은 SUT가 상태를 변경하기 위한 의존성을 호출하는 것에 해당 -> 의존성 간의 상호 작용을 모방하고 검사
-
스텁 (스텁, 더미, 페이크) : 내부로 들어오는 상호 작용을 모방하는 데 도움이 된다.
- 이러한 상호 작용은 SUT가 입력 데이터를 얻기 위한 의존성을 호출하는 것에 해당 -> 모방만 함
-
-
스텁과 상호 작용을 검증하지 말라.
- 최종 결과가 아닌 사항을 검증하는 관행을 과잉 명세라고 한다.
- 스텁과의 상호 작용을 검증하는 테스트 == 구현 세부 사항을 검증하는 테스트
- 테스트에서 거짓 양성을 피하고 리팩터링 내성을 향상 시키는 법은 구현 세부 사항이 아니라 최종 결과를 검증 하는 것 뿐이다.
-
Command Query Separation (명령, 조회 분리 원칙)와 목과 스텁
-
모든 메서드는 명령이거나 조회여야 하며, 이 둘을 혼용해서는 안 된다.
- 명령 : 사이드 이펙트를 일으키고 어떤 값도 반환하지 않는 메서드 (사이드 이펙트를 초래하고 값을 반환하는 메서드도 있음. stack.pop() 같은거)
- 조회 : 사이드 이펙트를 일으키지 않고 값을 반환하는 메서드
- 명령 메서드를 대체하는 테스트 대역 == 목
- 조회 메서드를 대체하는 테스트 대역 == 스텁
-
식별할 수 있는 동작과 구현 세부 사항
-
테스트는 어떻게가 아니라 무엇에 중점을 두어야 한다.
- 리팩터링 내성이 떨어지는 테스트가 만들어지는 주요 이유는 코드의 구현 세부 사항과 결합돼 있기 때문.
- 이러한 강결합을 피하는 방법은 코드가 생성하는 최종 결과(식별할 수 있는 동작)를 검증하고 구현 세부 사항과 테스트를 가능한 한 떨어뜨리는 것.
-
식별할 수 있는 동작
- 클라이언트가 목표를 달성하는 데 도움이 되는 연산. 계산을 수행하거나 사이드 이펙트를 초래하거나 둘 다 하는 메서드
- 클라이언트가 목표를 달성하는 데 도움이 되는 상태 . 시스템의 현재 상태
- 구현 세부 사항은 위 두 가지 중 아무것도 하지 않는다.
- 이상적으로 시스템의 공개 API는 식별할 수 있는 동작과 일치해야 하며, 모든 구현 세부 사항은 클라이언트 눈에 보이지 않아야 한다.
- 단일한 목표를 달성하고자 클래스에서 호출해야 하는 연산의 수가 1보다 크면 해당 클래스에서 구현 세부 사항을 유출할 가능성이 있다.
// 구현 세부 사항을 노출하는 클래스
const normalizedName = user.NormalizeName(newName);
user.name = normalizedName
-
잘 설계된 API와 캡슐화
- 캡슐화는 불변성 위반이라는 모순을 방지하는 조치
- 구현 세부 사항을 노출하면 불변성 위반을 초래한다.
- 장기적으로 코드베이스 유지 보수에서는 캡슐화가 중요하다. -> 코드가 점점 복잡해지기 때문
-
묻지 말고 말하라
- 데이터를 연산 기능과 결합하는 것. 캡슐화의 실천
- 구현 세부 사항을 숨기고 데이터와 기능을 결합하는 것이 해당 목표를 달성하기 위한 수단.
- 구현 세부 사항을 숨기면 클라이언트의 시야에서 클래스 내부를 가릴 수 있기 때문에 내부를 손상시킬 위험이 적다.
- 데이터와 연산을 결합하면 해당 연산이 클래스의 불변성을 위반하지 않도록 할 수 있다.
헥사고날 아키텍처 (육각형), 목과 테스트 취약성 간의 관계
-
도메인 계층과 애플리케이션 서비스 계층 간의 관심사 분리
- 비즈니스 로직은 애플리케이션의 가장 중요한 부분.
- 도메인 계층은 해당 비즈니스 호직에 대해서만 책임을 져야 하며, 다른 모든 책임에서는 제외돼야 한다.
- 도메인 계층 - 애플리케이션의 도메인 지식 모음.
- 애플리케이션 서비스 계층 - 비즈니스 유스케이스
-
애플리케이션 내부 통신
- 헥사고날 아키텍처는 애플리케이션 서비스 계층에서 도메인 계층으로 흐르는 단방향 의존성 흐름을 규정한다.
- 도메인 계층 내부 클래스는 도메인 계층 내부 클래스끼리 서로 의존하고 애플리케이션 서비스 계층의 클래스에 의존하지 않는다.
- 도메인 계층은 외부 환경에서 완전히 격리돼야 한다.
-
서로 다른 계층의 테스트는 동일한 동작을 서로 다른 수준으로 검증하는 프랙탈 특성이 있다.
- 애플리케이션 서비스 테스트는 전반적으로 비즈니스 유스케이스가 어떻게 실행되는지 확인한다.
- 도메인 클래스 테스트는 유스케이스 완료 방법에 대한 중간의 하위 목표를 검증한다.'
- 식별할 수 있는 동작은 바깥 계층에서 안쪽으로 흐른다. 외부 클라이언트에게 중요한 목표는 개별 도메인 클래스에서 달성한 하위 목표로 변환된다.
- 연산을 수행하기 위한 도메인 클래스 간의 협력은 식별할 수 있는 동작이 아니므로 시스템 내부 통신은 구현 세부 사항에 해당한다. 이러한 협력은 클라이언트의 목표와 직접적인 관계가 없다. 따라서 이러한 협력과 결합하면 테스트가 취약해진다.