# Writing Testable Code by Miško Hevery

[Flaw #1: Constructor does Real Work](https://github.com/mhevery/guide-to-testable-code/blob/main/flaw-constructor-does-work.md)

**Warning Signs**

* new keyword in a constructor or at field declaration
* Static method calls in a constructor or at field declaration
* Anything more than field assignment in constructors
* Object not fully initialized after the constructor finishes (watch out for initialize methods)
* Control flow (conditional or looping logic) in a constructor
* Code does complex object graph construction inside a constructor rather than using a factory or builder
* Adding or using an initialization block

[Flaw #2: Digging into Collaborators](https://github.com/mhevery/guide-to-testable-code/blob/main/flaw-digging-into-colaborators.md)

**Warning Signs**

* Objects are passed in but never used directly (only used to get access to other objects)
* Law of Demeter violation: method call chain walks an object graph with more than one dot (.)
* Suspicious names: context, environment, principal, container, or manager

[Flaw #3: Brittle Global State & Singletons](https://github.com/mhevery/guide-to-testable-code/blob/main/flaw-brittle-global-state-singletons.md)

**Warning Signs**

* Adding or using singletons
* Adding or using static fields or static methods
* Adding or using static initialization blocks
* Adding or using registries
* Adding or using service locators

[Flaw #4: Class Does Too Much](https://github.com/mhevery/guide-to-testable-code/blob/main/flaw-class-does-too-much.md)

**Warning Signs**

* Summing up what the class does includes the word “and”
* Class would be challenging for new team members to read and quickly “get it”
* Class has fields that are only used in some methods
* Class has static methods that only operate on parameters





6. **Static methods: (or living in a procedural world)**:&#x20;

#### 🔎 **왜 Static 메서드는 테스트하기 어려운가?**

#### **Polymorphism(다형성) 부재란?**

* **Polymorphism(다형성)**: 동일한 메서드 호출이 **다른 구현**으로 동작할 수 있는 성질.
* **Static 메서드**는 **컴파일 시점**에 호출 대상이 **고정**되어 있어 **다형성을 제공하지 못함**.

***

#### 📊 **1. 다형성의 차이: Static 메서드 vs 인스턴스 메서드**

| 특징            | **Static 메서드**                     | **인스턴스 메서드**                      |
| ------------- | ---------------------------------- | --------------------------------- |
| **호출 시점**     | **컴파일 시점** (compile-time binding)  | **실행 시점** (run-time binding)      |
| **다형성 지원 여부** | ❌ **불가능**                          | ✅ **가능 (오버라이딩, 인터페이스 구현)**        |
| **변경 가능성**    | ❌ **불변** (항상 같은 메서드 호출)            | ✅ **유연** (동적 구현 변경 가능)            |
| **모킹(Mock)**  | ❌ **불가능**                          | ✅ **가능 (인터페이스 활용 시)**             |
| **활용 예시**     | `Math.abs()`, `Collections.sort()` | `PaymentService.processPayment()` |

***

#### 🔍 **2. Static 메서드는 다형성이 불가능한 이유**

1.  **컴파일 시점 고정**

    * Static 메서드는 **클래스 레벨**에서 정의되며, **객체 생성과 무관**하게 동작.
    * 호출 시점에 **메서드 구현체**가 고정되기 때문에, 다른 구현체로 **변경 불가**.

    ```java
    public class PaymentUtil {
        public static void processPayment(double amount) {
            System.out.println("Processing payment: " + amount);
        }
    }

    public class Main {
        public static void main(String[] args) {
            PaymentUtil.processPayment(1000.0);
        }
    }
    ```

    > 위 예제에서는 `PaymentUtil.processPayment()`를 호출할 때\
    > **항상 같은 메서드**가 실행됨.\
    > → 런타임에 다른 구현으로 **교체 불가**.

***

#### 🔄 **3. 인스턴스 메서드는 다형성이 가능한 이유**

1.  **실행 시점 바인딩**

    * 인스턴스 메서드는 **객체 생성**에 기반하며, 실행 시점에 **적합한 구현체**를 결정.
    * **인터페이스**나 **추상 클래스**로 정의한 후, 다양한 구현을 선택 가능.

    ```java
    // 인터페이스 정의
    public interface PaymentService {
        void processPayment(double amount);
    }

    // 구현체 1: 카드 결제
    public class CardPaymentService implements PaymentService {
        @Override
        public void processPayment(double amount) {
            System.out.println("Processing card payment: " + amount);
        }
    }

    // 구현체 2: 계좌 이체
    public class BankTransferService implements PaymentService {
        @Override
        public void processPayment(double amount) {
            System.out.println("Processing bank transfer: " + amount);
        }
    }

    // 런타임에 다른 구현 선택
    public class Main {
        public static void main(String[] args) {
            PaymentService service = new CardPaymentService();
            service.processPayment(1000.0);  // 카드 결제 로직 실행

            service = new BankTransferService();
            service.processPayment(2000.0);  // 계좌 이체 로직 실행
        }
    }
    ```

    > ✅ **장점**: `PaymentService`의 구현체를 **런타임**에 변경 가능.
    >
    > * Static 메서드로 작성했다면, 변경이 불가능함.

***

#### 🔨 **4. Static 메서드의 문제점: 테스트 어려움**

* **문제점**: Static 메서드는 **외부 의존성**을 가질 경우, **Mocking** 불가.
* **해결책**: Static 메서드를 **인터페이스**로 변경해 **의존성 주입(DI)** 활용.

**❌ 잘못된 예제: Static 메서드로 결제 로직 구현**

```java
public class PaymentUtil {
    public static boolean processPayment(double amount) {
        // 외부 결제 서비스 연동 (모킹 불가)
        return PaymentGateway.charge(amount);
    }
}
```

* **문제**: `PaymentGateway`는 Static 메서드라 Mocking이 불가능.
* **해결책**: 인터페이스로 변경하여 **다형성** 확보.

#### ✅ 인스턴스 메서드**로 Mocking 가능한 테스트 (Mockito)  예제**

#### 📌 **1. 결제 로직 코드**

```java
// 결제 처리 인터페이스
public interface PaymentProcessor {
    boolean processPayment(double amount);
}

// 실제 결제 구현체
public class RealPaymentProcessor implements PaymentProcessor {
    @Override
    public boolean processPayment(double amount) {
        return PaymentGateway.charge(amount); // 외부 결제 서비스 호출
    }
}

// 결제 서비스 (의존성 주입 사용)
public class PaymentService {
    private final PaymentProcessor paymentProcessor;

    public PaymentService(PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }

    public boolean process(double amount) {
        return paymentProcessor.processPayment(amount);
    }
}
```

***

#### 📊 **2. Mocking 가능한 테스트 코드**

```java
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;

public class PaymentServiceTest {
    @Test
    public void testProcessPayment() {
        // 1. PaymentProcessor의 Mock 객체 생성
        PaymentProcessor mockProcessor = mock(PaymentProcessor.class);
        
        // 2. Mock 객체의 동작 정의
        when(mockProcessor.processPayment(1000.0)).thenReturn(true);
        
        // 3. PaymentService에 Mock 주입
        PaymentService paymentService = new PaymentService(mockProcessor);
        
        // 4. 메서드 실행 및 결과 검증
        assertTrue(paymentService.process(1000.0));
        
        // 5. Mock 객체 호출 검증
        verify(mockProcessor).processPayment(1000.0);
    }
}
```

***

#### 📌 **5. 언제 Static 메서드를 사용해도 괜찮은가?**

* **순수 함수(Pure Function)**: 외부 의존성 없음, 입력 → 출력만 반환.
  * 예: `Math.abs()`, `Objects.requireNonNull()`
* **유틸리티 메서드**: 상태를 변경하지 않고, 보조 기능만 수행.
  * 예: `Collections.sort()`, `LocalDate.now()`

**✅ Static으로 작성해도 괜찮은 예:**

```java
public class MathUtils {
    public static int square(int number) {
        return number * number;
    }
}
```

***

#### 📊 **정리: Static 메서드 vs 인스턴스 메서드**

| 구분                     | Static 메서드                    | 인스턴스 메서드                              |
| ---------------------- | ----------------------------- | ------------------------------------- |
| **다형성 (Polymorphism)** | ❌ 불가능 (컴파일 시점에 고정)            | ✅ 가능 (런타임에 구현체 변경 가능)                 |
| **테스트(Mock)**          | ❌ 불가능 (외부 의존성 모킹 불가)          | ✅ 가능 (의존성 주입으로 모킹 용이)                 |
| **유연성**                | ❌ 구현 변경 불가                    | ✅ 다양한 구현을 런타임에 선택 가능                  |
| **확장성**                | ❌ 기능 확장 어려움                   | ✅ 새로운 구현 추가 용이                        |
| **사용 사례**              | 순수 함수, 유틸리티 (예: `Math.abs()`) | 비즈니스 로직, 외부 의존성 (예: `PaymentService`) |



#### **7.  "Favor Composition Over Inheritance"**&#x20;

***

#### 📊 **1. 핵심 개념**

* **상속(Inheritance)**: 부모 클래스를 확장(extends)해 기능을 재사용
  * 단점: 부모 클래스에 의존 → 변경이 어려움, 테스트 복잡성 증가
* **합성(Composition)**: 필요한 기능을 \*\*객체 주입(Dependency Injection, DI)\*\*으로 결합
  * 장점: 유연성 증가, **Mocking**을 통한 테스트 용이

***

#### 📌 **2. 문제점: 상속을 남용하면 생기는 문제**

* **테스트 어려움**: 부모 클래스 로직까지 모두 실행 → **독립적 테스트 불가**
* **유연성 부족**: 런타임에 부모 클래스를 변경 불가
* **복잡성 증가**: 부모-자식-손자 관계로 갈수록 복잡해짐

***

#### ❌ **잘못된 예제: 상속 남용**

```java
// 부모 클래스: 인증 로직 포함 (테스트 어려움)
public class AuthenticatedServlet {
    public void service() {
        authenticate(); // 인증 로직 (항상 실행됨)
        handleRequest();
    }

    private void authenticate() {
        System.out.println("🔴 사용자 인증 수행");
    }

    protected void handleRequest() {
        System.out.println("🔴 기본 요청 처리");
    }
}

// 자식 클래스: 부모의 인증 로직 상속
public class UserServlet extends AuthenticatedServlet {
    @Override
    protected void handleRequest() {
        System.out.println("🟡 사용자 정보 처리");
    }
}
```

#### 📉 **문제점**

1. `UserServlet` 테스트 시 **항상 `authenticate()` 실행됨**
2. 테스트하려면 **인증 로직까지 Mocking**해야 해서 복잡해짐
3. 부모-자식이 강하게 결합 → **유연성 부족**

***

#### ✅ **3. 해결책: 합성(Composition) 사용**

> **상속 대신** 인증 로직을 **인터페이스로 분리**하고 **의존성 주입(DI)** 활용!

***

#### 🟢 **리팩토링: 합성 적용**

```java
// 1. 인증 로직을 인터페이스로 분리
public interface Authenticator {
    void authenticate();
}

// 2. 실제 인증 구현체
public class RealAuthenticator implements Authenticator {
    @Override
    public void authenticate() {
        System.out.println("✅ 실제 사용자 인증 수행");
    }
}

// 3. UserServlet에서 인증을 합성(주입)으로 사용
public class UserService {
    private final Authenticator authenticator;

    public UserService(Authenticator authenticator) {
        this.authenticator = authenticator;
    }

    public void process() {
        authenticator.authenticate();  // 인증 수행
        System.out.println("🟢 사용자 정보 처리");
    }
}
```

***

#### ✅ **4. 테스트: Mocking으로 독립적 테스트**

```java
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;

public class UserServiceTest {
    @Test
    public void testProcess() {
        // 1. Authenticator의 Mock 객체 생성
        Authenticator mockAuthenticator = mock(Authenticator.class);

        // 2. UserService에 Mock 객체 주입
        UserService userService = new UserService(mockAuthenticator);

        // 3. 메서드 실행
        userService.process();

        // 4. Mock 객체 호출 검증 (인증 로직만 확인 가능)
        verify(mockAuthenticator).authenticate();
    }
}
```

***

#### 📊 **5. 비교: 상속 vs 합성**

| 항목      | **상속(Inheritance)**  | **합성(Composition)**          |
| ------- | -------------------- | ---------------------------- |
| **유연성** | ❌ 부모 클래스 고정 (변경 어려움) | ✅ 주입 객체 변경 가능 (Mock으로 대체 가능) |
| **테스트** | ❌ 부모 로직까지 Mocking 필요 | ✅ 개별 로직만 Mocking으로 독립적 테스트   |
| **결합도** | ❌ 강한 결합 (부모와 자식 의존)  | ✅ 약한 결합 (인터페이스 기반)           |
| **확장성** | ❌ 계층 구조 깊어질수록 복잡     | ✅ 새로운 기능 추가 시 인터페이스로 확장      |

***

#### 📌 **6. 결론**

* **상속**: **재사용**에는 좋지만, **유연성**과 **테스트 용이성** 부족
* **합성**: **Mocking** 가능 → **독립적 테스트** 쉬움, **유연성** 증가

> **👉 합성을 우선하라**\
> 필요한 기능은 **인터페이스**로 분리하고, **DI**로 주입



{% embed url="https://github.com/mhevery/guide-to-testable-code?tab=readme-ov-file" %}

{% embed url="https://testing.googleblog.com/2008/08/by-miko-hevery-so-you-decided-to.html" %}

