diff --git "a/\353\217\204\354\204\234/\354\236\220\353\260\224 \354\212\244\355\224\204\353\247\201 \352\260\234\353\260\234\354\236\220\353\245\274 \354\234\204\355\225\234 \354\213\244\354\232\251\354\243\274\354\235\230 \355\224\204\353\241\234\352\267\270\353\236\230\353\260\215/3. \355\205\214\354\212\244\355\212\270.md" "b/\353\217\204\354\204\234/\354\236\220\353\260\224 \354\212\244\355\224\204\353\247\201 \352\260\234\353\260\234\354\236\220\353\245\274 \354\234\204\355\225\234 \354\213\244\354\232\251\354\243\274\354\235\230 \355\224\204\353\241\234\352\267\270\353\236\230\353\260\215/3. \355\205\214\354\212\244\355\212\270.md"
new file mode 100644
index 0000000..f338991
--- /dev/null
+++ "b/\353\217\204\354\204\234/\354\236\220\353\260\224 \354\212\244\355\224\204\353\247\201 \352\260\234\353\260\234\354\236\220\353\245\274 \354\234\204\355\225\234 \354\213\244\354\232\251\354\243\274\354\235\230 \355\224\204\353\241\234\352\267\270\353\236\230\353\260\215/3. \355\205\214\354\212\244\355\212\270.md"
@@ -0,0 +1,247 @@
+# 3부 테스트
+
+### 목차
+
+- [12. 자동 테스트](#12-자동-테스트)
+- [13. 테스트 피라미드](#13-테스트-피라미드)
+- [14. 테스트 대역](#14-테스트-대역)
+- [15. 테스트 가능성](#15-테스트-가능성)
+- [16. 테스트와 설계](#16-테스트와-설계)
+- [17. 테스트와 개발 방법론](#17-테스트와-개발-방법론)
+
+
+
+
+## [12. 자동 테스트](#목차)
+
+
+
+
+
+## [13. 테스트 피라미드](#목차)
+
+
+
+
+
+
+## [14. 테스트 대역](#목차)
+
+
+
+
+
+
+
+## [15. 테스트 가능성](#목차)
+
+> 테스트를 사용하는 목적에는 크게 `회귀 버그 방지`와 `좋은 설계를 얻기 위함`이 있다.
+
+테스트 자체는 검증의 수단에 불과하지만, 테스트를 `개발이 완료된 후 작성하는 것`이 아닌 `개발 전에 미리 작성하는 것` 또는 `개발을 하면서 함께 작성하는 것`으로 본다면 이야기가 달라진다.
+
+테스트를 어떻게 바라보느냐에 따라 테스트의 가치가 다르게 결정될 수 있다. 테스트를 회귀 버그 방지를 위한 수단으로만 바라보면 테스트가 주는 가치를 온전히 누릴 수 없고, 테스트를 좋은 설계를 갖춘 시스템을 얻기 위한 도구로 봐야 가치를 더 누릴 수 있다.
+
+
+
+### 15.1 테스트를 어렵게 만드는 요소
+
+> `Testabity`: '테스트 가능성' 이라는 뜻으로, 테스트하기 쉬운 코드일수록 Testabity가 높다.
+> 테스트하기 쉬운 코드일수록 좋은 설계일 확률이 높다.
+
+어떤 코드가 테스트하기 쉬운 코드인지에 대한 해답은 테스트하려는 대상의 `입력`과 `출력`에 있다.
+- 테스트는 테스트하려는 대상의 입력을 쉽게 변경할 수 있고, 출력을 쉽게 검증할 수 있을 때 작성하기 쉽다.
+- 반면 테스트하려는 대상에 **숨겨진 입력**이 존재하거나 **숨겨진 출력**이 있을 때 테스트를 검증하기가 어려워진다.
+ - 숨겨진 입력과 숨겨진 의존성은 외부 사용자가 코드를 사용할 때 코드가 어떤 식으로 동작할지 예상할 수 없게 만든다. 또한, 사용자가 코드를 제어할 수 없게 된다.
+ - 따라서 의존하는 코드와 필요한 입력이 있다면 이를 외부로 드러내는 것이 좋다.
+ - 숨겨진 출력이 너무 많다면 메서드 호출한 결과로 어떤 결과가 나올지 고민해야 하며, 개발자는 메서드 호출 결과를 예측할 수 없게 된다.
+ - 따라서 숨겨진 출력을 드러내는 것이 좋다.
+
+의존성 주입과 의존성 역전을 확용하면 더 유연한 변경이 가능하게 할 수도 있다.
+- 의존성 주입과 의존성 역전을 사용하는 코드는 구현체에 의존하지 않고 인터페이스와 책임에 집중하기 때문에, 환경에 따라 다른 구현체가 실행되게 할 수 있다.
+- 책임에 의존하는 코드를 실행하면서도 배포 환경에서는 실제 구현체를 사용하게 만들고, 테스트 환경에서는 테스트 대역을 사용하도록 구성할 수 있다.
+
+테스트하기 쉬운 코드라면 좋은 설계일 확률이 높다.
+- 테스트하기 쉬운 코드는 의존성이 드러나 있는 코드이고, 유연한 코드일 확률이 높다.
+- 따라서 어떤 코드가 더 나은 방식인지 고민된다면 테스트하기 쉬운쪽을 선택하자.
+- 테스트하기 쉬운 코드라면 어떤 코드여도 괜찮다.
+
+> 숨겨진 출력은 메서드 반환값 외에 존재하는 모든 부수적인 출력을 뜻한다.
+
+
+
+### 15.2 테스트가 보내는 신호
+
+개발자가 느낄 수 있는 테스트가 보내는 몇 가지 신호들이 있다.
+- 테스트의 입출력을 확인할 수 없는데 어떻게 하지?
+- private 메서드는 어떻게 테스트하지?
+- 서비스 컴포넌트의 간단한 메서드를 테스트하고 싶을 뿐인데 , 이를 위해 필요도 없는 객체를 너무 많이 주입해야 하네?
+
+그리고 이러한 신호들은 `설계가 잘못됐을 확률이 높으니 좋은 설계로 변경해 봐`라는 말을 하고 있다.
+
+
+
+> 어떤 라이브러리를 사용할지 보다 **어떤 것을 테스트해야 할지**, **어떻게 테스트해야 할지**, **어떻게 코드를 작성해야 테스트가 쉬워질지**를 고민하는 것이 더 중요하다.
+
+
+
+
+## [16. 테스트와 설계](#목차)
+
+> 좋은 소프트웨어 설계와 테스트가 추구하는 목표가 일정 부분 같기 때문에 테스트와 소프트웨어 설꼐는 긴밀한 상관관계를 맺는다.
+> 좋은 설계를 따르는 시스템은 배포 환경이나 테스트 환경을 가리지 않고 이식 가능하다. 따라서 대부분 테스트하기도 쉽다.
+
+> 중요한 것은 테스트를 작성함으로써 원칙을 고민할 기회가 생겼다는 것이다. 테스트는 신호만 보낼 뿐, 받아들일지 말지는 개발자의 몫이다.
+
+### 16.1 테스트와 SRP
+
+서비스는 신뢰할 수 있는 컴포넌트여야 하고, 서비스의 완전성을 해치는 멤버 변수가 null이 되는 상황은 피해야 한다.
+- 테스트 코드를 작성하면서 의존성을 고민할 수 있고, 불필요한 의존성을 체감할 수 있다. 이를 분리하자.
+- 테스트 클래스가 컴포넌트 단위를 다시 고민하게 만들 수 있고, 이러한 이유로 테스트는 단일 책임 원칙을 고민하게 만든다.
+ - UserService에서 UserRegister와 AuthenticationService로 분리하고, 테스트도 회원가입과 로그인이라는 목적으로 나누어 테스트의 목적이 한눈에 들어오게 관리할 수 있다.
+
+
+
+### 16.2 테스트와 ISP
+
+인터페이스를 분리하면 테스트를 위한 구현체를 구현하기 쉬워진다.
+- 인터페이스가 통합되어 있으면 테스트 관심사 밖에 있는 메서드도 불필요하게 모두 구현해야 한다.
+- 테스트를 만들 때 `우리가 테스트하고 싶은 것`에만 관심을 두고 싶고, 그 외의 불필요한 의존과 인터페이스에는 관심을 두고 싶지 않다.
+ - 이 또한 테스트가 인터페이스를 분리하라는 신호를 보내고 있는 것이다.
+ - VerificationEmailSender 인터페이스일 때는 이러한 고민이 필요없었지만 EmailSender라는 통합된 인터페이스를 사용하고 나니 이러한 고민이 시작되었다. 따라서 처음부터 인터페이스는 상세히 분리하는 것이 좋다.
+- 특수한 목적을 처리하는 인터페이스를 만드는 편이 테스트에 훨씬 유리하다.
+
+
+
+### 16.3 테스트와 OCP, DIP
+
+> OCP는 `시스템은 유연하게 확장할 수 있어야 하고 변경으로 생길 수 있는 영향 범위는 최소화할 수 있어야 한다`는 목표를 가진다.
+
+코드가 유연한 설계를 따르고 있는지 판단하는 가장 원시적이면서도 확실한 방법은 컴포넌트 간 의존 관계를 전부 파악해보는 것이다.
+- 의존 관계를 추적해서 모든 컴포넌트의 대체 가능성과 확장 가능성을 판한해본다.
+- 하지만 설계는 계속 변하고, 코드가 변경되면 코드의 가치도 변하기 때문에 매번 이 작업을 할 수는 없다. 또한, 객관적으로 측정할 수 있는 수단이 없다.
+
+지속 가능하고 객관적인 가치 판단 방법으로 테스트를 활용할 수 있다.
+- 테스트가 실행되는 환경은 배포 환경과 다르며, 테스트 환경은 배포 환경의 요구사항과 다른 새로운 요구사항에 대처하는 곳이다.
+- 따라서 테스트를 작성하는 개발자는 시스템을 개발할 때 배포 환경과 테스트 환경 둘 다 고려해야 하며, 그 결과 코드는 여러 환경에서 실행가능한 코드가 된다. 즉, 유연해진다.
+
+자연스럽게 테스트 대역을 사용할 수 있는 시스템에서는 새 컴포넌트를 만들고 갈아끼우듯 교체할 수 있다.
+- 테스트를 작성하기 위해서는 개발자가 시스템 배포 환경이 아닌 테스트 환경에서도 돌아갈 수 있게 만들어야 한다.
+- 이로 인해 테스트 하기 쉬운 구조를 추구하는 것이 시스템이 다양한 환경에서 대응할 수 있게 만드는 데 도움이 된다.
+
+의존성 역전 원칙도 테스트를 고민하다 보면 자연스럽게 따라 온다.
+- 테스트 대역 클래스가 인터페이스(verificationEmailSender)와 같은 추상에 의존하는 것이 아닌 구현체(verificationEmailSenderImpl) 컴포넌트에 직접 의존한다면, 이 테스트 대역은 해당 컴포넌트의 변경에 자유롭지 못하다.
+ - 구현체의 코드 변경에 따라서 테스트가 영향을 받게 된다.
+- 구현체를 상속해 테스트 대역을 만드려는 시도는 테스트를 복잡하게 만든다. 서비스가 추상에 의존하게 만들면 이러한 고민을 할 필요가 없다.
+- 역할에 의존하는 코드를 만들고 역할에 충실한 컴포넌트를 배포 환경이나 테스트 환경에 만들면 된다.
+
+
+
+
+### 16.4 테스트와 LSP
+
+> 지금까지 자동 테스트와 테스트의 목적, 대역, 테스트 가능성, 테스트가 왜 필요하고 어떤 식으로 테스트해야 하는지에 대해 이야기 했다.
+> 이제 `어떤 것을 테스트해야 할지`가 궁금할 것이다.
+
+아래의 두 가지 원칙들도 있지만 더 어울리는 답변이 존재한다.
+- `Right-BICEP` 원칙: 어떤 것을 테스트 해야하는지 알려준다.
+- `CORRECT` 원칙: 테스트 환경을 가정할 때 데이터의 경계 조건에는 어떤 것이 있는지 알려준다.
+
+시스템이 유지하고 싶은 상태가 있다면 이를 테스트로 작성하라
+- 개발을 마친 후에 전체 테스트를 실행해 시스템의 안정성을 평가하며, 이상 징후를 탐지하고 문제가 있다면 해당 정보를 알려주기를 원한다.
+- 이를 위해서 어떤 메서드나 시스템의 실행 결과를 이리 작성하고 유지했으면 하는 시스템의 모든 상태를 테스트로 작성해야 한다.
+- 테스트는 시스템의 상태를 검증하는 수단이다. 따라서 개발자는 유지하고 싶은 상태가 있다면 역할과 책임 관점에서 개발자의 모든 의도를 테스트로 작성해둬야 한다.
+
+테스트와 리스코프 치환 원칙을 연결지어 생각해보자
+- 테스트는 시스템의 상태를 검증할 수 있고, 리스코프 치환 역시 시스템이 유지하고 싶은 상태 중 하나이다. 그래서 이 둘은 궁합이 잘 맞다.
+- 리스코프 치환을 검증하는 테스트를 작성함으로써 시스템이 리스코프 치환 원칙을 상시로 유지하고 있는지 검사할 수 있다.
+
+리스코프 치환이 깨지는 상황
+1. 원칙을 지키고 있는 파생 클래스를 수정했더니 기본 클래스를 대체하지 못하는 경우
+ - 리스코프 치환을 검증하는 테스트가 미리 작성돼 있었다면 파생 클래스가 수정되었을 때 원칙 검증이 이루어졌을 것이다.
+ - 코드 변경으로 원칙이 깨는 순간 테스트는 바로 회귀 버그를 감지하고 개발자는 이를 수정할 수 있다.
+2. 새로운 파생 클래스가 처음부터 기본 클래스를 대체하지 못하는 경우
+ 1. 추상 테스트 클래스 이용: 파생 클래스의 테스트나 기본 클래스의 테스트가 추상 클래스를 상속하도록 만든다.
+ - 기본 클래스에 대한 테스트 코드를 작성하는데 이는 추상 클래스이며, 추상 메서드도 정의하여 다른 테스트 코드에 적용해 활용할 수 있다.
+ - 이렇게 되면 파생 클래스의 테스트를 실행하면서 동시에 상위에 선언된 테스트도 함께 실행할 수 있다.
+ - 따라서 리스코프 치환을 함께 검증하게 되며, 이는 테스트 코드에 템플릿 메서드 패턴을 적용한 사례로 볼 수 있다.
+ - 하지만 파생 테스트 클래스들의 세부 구현이 강제화 된다.
+ 2. 테스트용 인터페이스 이용: 인터페이스를 미리 만들어두고 파생 클래스를 테스트할 때는 테스트도 상위 인터페이스를 구현하도록 의무화한다.
+ - 개발자는 의식적으로 파생 클래스의 리스코프 치환 테스트를 작성해야 한다고 느끼고 이를 챙기게 될 것이다.
+ 3. 매개변수 값 변경 테스트 방식: 같은 시나리오의 테스트를 입력 값만 바꾸어 실행할 수 있는 방법이다.
+
+리스코프 치환 원칙 검증의 어려움
+- Square가 Rectangle을 상속받을 때, Square의 setHeight 또는 setWidth 메서드 호출 결과가 모순적인 상황을 초래한다.
+ - setHeight(5)를 호출한 후 getWidth() 결과가 5인 경우에는 Rectangle의 동작과 달라 LSP를 위반하고, getWidth()가 기존 값을 유지한다면 이는 더 이상 정사각형이 아니게 되어 데이터 모순이 발생한다.
+- 결국 이를 테스트하려 해도 어떤 기대값을 설정해야 할지 불명확하며, LSP 위반 문제를 해결하지 못한다.
+ - 근본적인 문제를 해결하기 위해 상속이 아닌 인터페이스로 공통적인 역할을 분리하여 구현하도록 변경한다.
+- LSP 위반이 발생할 수 있는 설계는 테스트로 해결하려 하지 말고, 애초에 올바른 설계를 적용해야 한다.
+
+결과적으로 테스트를 고민함으로써 리스코프 치환을 유지할 수 없는 설계의 잘못된 점을 발견할 수 있었다. 그리고 구조를 변경해 스코프 치환을 유지할 수 있고, 테스트할 수 있는 코드로 변경할 수 있었다.
+
+> LSP: 상속을 통해 파생된 클래스가 기본 클래스를 대체할 수 있어야 한다는 원칙
+
+
+
+
+## [17. 테스트와 개발 방법론](#목차)
+
+
+
+### 17.1 TDD(test-driven development)
+
+> 개발자가 코드를 작성하기 전에 해당 코드의 테스트 케이스를 먼저 작성하게 한 후 해당 테스트를 통과할 수 있는 코드를 작성하는 방식
+
+1. Red 단계
+ - 아직 구현되지 않은 기능을 테스트하는 케이스를 작성한다.
+ - 이 시점에서 테스트는 당연히 실패한다.
+2. Green 단계
+ - 테스트를 통과시키기 위한 **최소한**의 코드를 작성한다.
+ - 코드의 품질은 신경 쓰지 않으며, 오롯이 최소한의 기능을 개발해서 테스트를 통과시키는 데만 집중한다.
+3. Blue 단계
+ - Refactor 단계로, Green 단계에서 작성한 코드를 리팩터링한다.
+ - 코드의 가독성과 유지보수성, 성능을 높이는데 집중한다.
+
+TDD를 이용하면 디버깅 시간이 단축되고, 코드의 문서 역할을 해주며 개발자는 코드 변경에 주저하지 않아도 된다. 하지만 요구사항이 명확하지 않은 상황에서 TDD는 오히려 독이 될 수 있다. 따라서 TDD나 테스트의 사용은 상황에 따라 결정해야 한다.
+
+TDD는 무엇을, 어떻게 테스트해야 하는지 설명하지 않기 때문에 시스템 설계를 하기에 TDD만으로는 부족하다.
+- TDD로 만들어진 코드는 객체지향적이지 않다. TDD는 그저 테스트를 먼저 작성하고 구현체를 만드는게 전부이다. 따라서 TDD는 객체지향을 보장하지 않는다.
+- 객체지향을 추구하는 개발자들에게는 이것이 TDD의 한계처럼 느껴지기도 한다.
+- TDD를 하면서도 객체지향을 보장하는 방법은 없을까? 이 때 DDD(도메인 주도 설계)가 등장한다.
+
+BDD는 TDD를 하면서도 객체지향적인 설계를 얻기 위해 만든 이론으로, TDD에 DDD를 얹은 것이다.
+- TDD는 설계 이론이고, DDD는 객체지향 설계 이론이다.
+ - TDD는 기능을 테스트하고 구축함으로써 안정성과 유연성을 확보하는 데 중점을 두는 반면, 객체지향을 보장하지 않는다.
+ - DDD는 도메인 모델을 중심으로 비즈니스 요구사항을 이해하고 설계하는 데 초점을 두어 객체지향을 추구할 수 있는 반면, 소프트웨어의 안정성을 확보하기 위한 시스템적인 해결책은 아니다.
+- 따라서 이 둘은 찰떡이다. TDD에 DDD의 이론을 차용하면 TDD가 고려하지 못하는 맥락적이고 서술적인 설계 내용을 보완할 수 있다.
+- 도메인 분석 단계에서 사용자 위주의 스토리를 만들고(DDD), 이를 바탕으로 테스트 코드를 작성해보자(TDD).
+
+
+
+### 17.2 BDD(behavior-driven development)
+
+> 소프트웨어 개발 과정에서 비즈니스 요구사항과 소프트웨어의 행동을 강조하는 개발 방법론으로, TDD에서 파생되었다.
+
+TDD에서 `사용자 행동`이라는 가치를 덧붙이고 이를 강조한다.
+- BDD 에서는 사용자 행동을 `행동 명세` 같은 요구사항으로 먼저 만들고, 이것이 테스트로 표현될 수 있게 만든다.
+- 즉, 테스트가 구사항 문서이자 기획 문서가 될 수 있게 만드는 것이다.
+
+TDD에 DDD를 끼얹은 것이 BDD이다.
+- BDD는 DDD와 마찬가지로 개발자(개발팀)와 비개발자(비즈니스팀) 사이의 협업을 강조한다.
+- 테스트 코드를 문서화하여 비개발자들이 열람할 수 있게 하고, 유비쿼터스 언어를 만들어 의사소통에 문제가 없도록 해야 한다고 한다고 말한다.
+- 공통된 언어를 바탕으로 요구사항 문서가 사용자 스토리 기반으로 작성돼야 한다는 것을 강조한다.
+
+사용자 스토리 기반의 요구사항은 요구사항 문서를 행동 명세에 맞게 작성하는 것을 의미한다.
+- 행동 명세는 `어떤 사용자가 어떤 상황에서(given), 어떤 행동을 할 때(when), 그러면(then) 어떤 일이 발생한다`를 기술한다.
+- 이는 요구사항을 뚜렷하게 명시하면서도 테스트 코드로 작성하기 편리하다.
+- 따라서 개발자는 Given-When-Then 형식으로 테스트를 작성하기만 하면 된다.
+
+행동과 행동에 따른 결과, 역할을 검증하는 테스트가 되며, 이러한 테스트를 기반으로 설계가 따라오면 객체지향에 좀 더 가까워질 수 있다.
+
+
+
+> Given-When-Then 형식으로 테스트를 작성하는 것은 BDD를 실천하는 여러 방법 중 하나이다.
+> - 어떤 상황에서(given), 어떤 동작이 주어질 때(when), 요구되는 상태는 어떤 것인지(then)
+
+
+
+