ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•œ 객체, ν…ŒμŠ€νŠΈ 더블(Test Double)

 

 

🧐 Test Doubleμ΄λž€?

ν…ŒμŠ€νŠΈ λ”λΈ”μ΄λž€ μ‹€μ œ κ΅¬ν˜„μ²΄λ‘œ ν…ŒμŠ€νŠΈλ₯Ό μ§„ν–‰ν•˜κΈ° μ–΄λ €μš΄ 경우, 이λ₯Ό λŒ€μ‹ ν•΄μ„œ ν…ŒμŠ€νŠΈλ₯Ό 진행할 수 μžˆλ„λ‘ λ§Œλ“€μ–΄μ§€λŠ” 객체이닀.

 

Test Double μ΄λž€ λͺ…μΉ­μ˜ 유래 πŸ’­

μ˜ν™”λ₯Ό μ΄¬μ˜ν•˜λŠ” 경우, μœ„ν—˜ν•œ μž₯λ©΄ μ΄¬μ˜ν•  λ•Œ μ‹€μ œ 배우λ₯Ό λŒ€μ‹ ν•΄μ„œ μ΄¬μ˜ν•˜λŠ” μŠ€ν„΄νŠΈ λ”λΈ”μ—μ„œ 유래된 단어이닀.

 

μ‚¬μš© 이유 πŸ’­

이전에 'ν…ŒμŠ€νŠΈ μ£Όλ„κ°œλ°œ μ‹œμž‘ν•˜κΈ°' λΌλŠ” 책을 읽은 적이 μžˆλŠ”λ°, ν•΄λ‹Ή μ±… μ €μžμ˜ 말을 빌리자면

ν…ŒμŠ€νŠΈ 더블은 μ™ΈλΆ€ μš”μΈμ— μ˜μ‘΄ν•˜λŠ” 객체에 λŒ€ν•œ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•  λ•Œ "μ‹€νŒ¨ν•˜λŠ” ν…ŒμŠ€νŠΈλŠ” 항상 μ‹€νŒ¨ν•˜κ³ , μ„±κ³΅ν•˜λŠ” ν…ŒμŠ€νŠΈλŠ” 항상 μ„±κ³΅ν•œλ‹€" λΌλŠ” ν…ŒμŠ€νŠΈμ˜ 일관성을 지킀기 μœ„ν•΄ μ‚¬μš©ν•œλ‹€κ³  ν–ˆλ‹€.

 

예λ₯Ό λ“€μ–΄ DB, μ™ΈλΆ€ API 등을 μ‚¬μš©ν•  λ•Œ, λ„€νŠΈμ›Œν¬ μ—°κ²° μƒνƒœ 등을 ν¬ν•¨ν•œ λ‹€μ–‘ν•œ 변칙적 μ΄μœ μ— μ˜ν•΄μ„œ ν…ŒμŠ€νŠΈκ°€ μ‹€νŒ¨ν•˜λŠ” 상황이 λ°œμƒν•  수 μžˆμ„ 것이닀.

μ΄λŸ¬ν•œ λ¬Έμ œμ— λŒ€ν•΄ μ‹€μ œ κ΅¬ν˜„μ²΄μ˜ λŒ€μ—­μΈ ν…ŒμŠ€νŠΈ 더블을 μ‚¬μš©ν•˜μ—¬ ν…ŒμŠ€νŠΈκ°€ μ–΄λ–€ 상황이든 간에 λ™μž‘ν•  수 μžˆλ„λ‘ λ§Œλ“€μ–΄μ£ΌλŠ” 것이닀.

 

'μ™ΈλΆ€ μš”μΈ' 의 λ²”μœ„

μ½”λ“œ 리뷰λ₯Ό μ£Όκ³ λ°›μœΌλ©° ν˜„μ—…μžμΈ λ¦¬λ·°μ–΄λŠ” ν…ŒμŠ€νŠΈ λ”λΈ”λ‘œ μ²˜λ¦¬ν•΄μ•Όν•˜λŠ” μ™ΈλΆ€ μš”μΈμ˜ λ²”μœ„λ₯Ό μ§€μ •ν•˜λŠ” 것이 κ°€μž₯ μ€‘μš”ν•˜λ‹€κ³  μ–˜κΈ°ν•΄μ£Όμ…¨λ‹€.

 

이에 λŒ€ν•΄μ„œ 본인의 기쀀을 ν•œ 쀄 μ μ–΄λ³΄μžλ©΄

API μ„œλ²„ κ°œλ°œμ— μ‚¬μš©ν•˜λŠ” μŠ€ν”„λ§λΆ€νŠΈ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ μ•„λ‹Œ λͺ¨λ“  μ™ΈλΆ€ μ˜μ‘΄μ„±λ“€μ„ μ™ΈλΆ€ μš”μΈμ΄λΌκ³ λŠ” μƒκ°ν•˜μ§€λŠ” μ•ŠλŠ”λ‹€.

예λ₯Ό λ“€μ–΄ H2 DBλ₯Ό In-Memory & Embedded λͺ¨λ“œλ‘œ μ‚¬μš©ν•œλ‹€λ©΄, μ΄λŠ” μŠ€ν”„λ§λΆ€νŠΈ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ‚΄λΆ€μ—μ„œ μž‘λ™ν•˜λ©° λ„€νŠΈμ›Œν¬κ°€ 없이도 μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ‚΄λΆ€μ—μ„œ μ •μƒμ μœΌλ‘œ μž‘λ™ν•  수 μ—†μœΌλ‹ˆ μ™ΈλΆ€ μ˜μ‘΄μ„±μΈ DB라고 해도 μ™ΈλΆ€ μš”μΈμœΌλ‘œ μƒκ°ν•˜μ§€ μ•ŠλŠ”λ‹€.

 

μ™ΈλΆ€ API, λ°μ΄ν„°λ² μ΄μŠ€ μ„œλ²„, 그리고 λ‹€λ₯Έ ν¬νŠΈμ—μ„œ λ™μž‘ν•˜λŠ” λ‹€λ₯Έ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ“±
μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ λ™μž‘μ— 영ν–₯을 λ―ΈμΉ  수 μžˆλŠ” μ™ΈλΆ€ μ˜μ‘΄μ„±μ„ μ™ΈλΆ€ μš”μΈμ΄λΌκ³  μƒκ°ν–ˆκ³ 
이에 λŒ€ν•΄ νƒ€λ‹Ήν•œ 의견인 것 κ°™λ‹€λŠ” 리뷰도 λ°›μ•„ 합리적인 생각에 가깝닀고 νŒλ‹¨ν•  수 μžˆμ—ˆλ‹€.

ν•˜μ§€λ§Œ λͺ¨λ“  μƒν™©λ§ˆλ‹€ λ²”μœ„λŠ” λ‹€λ₯Ό 수 있고 κ°œλ°œμ— 정닡은 μ—†μœΌλ‹ˆ 참고만 ν•˜κΈΈ λ°”λž€λ‹€.

 

 

κ°€μ§œλ₯Ό μ‚¬μš©ν•˜λ©΄ μ˜λ―Έκ°€ 없지 μ•Šμ„κΉŒ? πŸ’­

본인은 κ°€μ§œ 객체인 ν…ŒμŠ€νŠΈ λ”λΈ”μ˜ ν…ŒμŠ€νŠΈ κ²°κ³ΌλŠ” μ‹€μ œ κ΅¬ν˜„μ²΄μ˜ 객체의 결과와 λ‹€λ₯΄κΈ° λ•Œλ¬Έμ— 별 μ˜λ―Έκ°€ μ—†λŠ” 것 μ•„λ‹κΉŒ? 라고 μƒκ°ν–ˆμ—ˆλ‹€.

ν•˜μ§€λ§Œ μ‹€μ œλ‘œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ— λŒ€ν•΄ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜λ©΄μ„œ ν…ŒμŠ€νŠΈ λ”λΈ”μ˜ ν•„μš”μ„±μ„ 느끼게 λ˜μ—ˆλ‹€.

 

예λ₯Ό λ“€μ–΄ 0~10 μ‚¬μ΄μ˜ λ‚œμˆ˜λ₯Ό λ°œμƒμ‹œν‚€λŠ” generate() λ©”μ„œλ“œλ₯Ό 가진 RandomNumberGeneratorκ°€ 있고 μ™ΈλΆ€μ—μ„œλŠ” ν•΄λ‹Ή λ‚œμˆ˜ 값이 0~4λ©΄ false, 5~10이면 trueλ₯Ό returnν•˜λŠ” 둜직이 μ‘΄μž¬ν•œλ‹€κ³  κ°€μ •ν•˜μž.

 

μ΄λŸ¬ν•œ λ‘œμ§μ„ ν…ŒμŠ€νŠΈν•΄μ•Όν•˜λŠ” μƒν™©μ—μ„œ, λ‚œμˆ˜λŠ” μš°λ¦¬κ°€ μ‘°μž‘ν•  수 μ—†λŠ” 값이기 λ•Œλ¬Έμ— ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜κΈ° κ½€λ‚˜ κ³€λž€ν•΄μ§„λ‹€.

이런 경우 μš°λ¦¬λŠ” ν…ŒμŠ€νŠΈ 더블을 톡해 κ°„λ‹¨νžˆ 문제λ₯Ό ν•΄κ²°ν•  수 μžˆλ‹€.

 

NumberGeneratorStub을 λ§Œλ“€κ³  generate() λ©”μ„œλ“œμ˜ return값을 0~10 쀑 ν…ŒμŠ€νŠΈμ— ν•„μš”ν•œ κ°’μœΌλ‘œ 직접 μ„€μ •ν•˜μ—¬ μš°λ¦¬κ°€ μ›ν•˜λŠ” νŠΉμ •κ°’μ„ 얻도둝 ν•  수 μžˆλ‹€.

 

λ¬Όλ‘  ν•΄λ‹Ή κ²°κ³ΌλŠ” μš°λ¦¬κ°€ μ‘°μž‘ν•œ κ°€μ§œμ΄μ§€λ§Œ, μ΄λ ‡κ²Œ ν†΅μ œκ°€ 100% κ°€λŠ₯ν•œ 상황이 μ•„λ‹Œ λ•Œμ—λ§Œ 적절히 ν™œμš©ν•œλ‹€λ©΄ ν…ŒμŠ€νŠΈλ₯Ό 톡해 κΈ°λŠ₯에 λŒ€ν•΄ μ‰½κ²Œ νŒŒμ•…ν•  수 있고, λ‚΄λΆ€μ μœΌλ‘œ μ–΄λ–»κ²Œ λ™μž‘ν•˜λŠ” 지 μ΄ν•΄ν•˜λŠ” 것에 큰 도움을 쀄 수 μžˆλŠ” ν›Œλ₯­ν•œ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±μ— 큰 도움을 쀄 것이닀.

 

이렇듯 μš°λ¦¬κ°€ ν†΅μ œν•  수 μ—†λŠ” μš”μΈμ— λŒ€ν•΄μ„œ ν…ŒμŠ€νŠΈ 더블을 μ‚¬μš©ν•  λ•Œ, νš¨μš©μ„ 얻을 수 μžˆμ„ 것이닀.

 

 

 

πŸ“ƒ Test Double의 μ’…λ₯˜

좜처: https://tecoble.techcourse.co.kr/post/2020-09-19-what-is-test-double/

ν…ŒμŠ€νŠΈ λ”λΈ”μ˜ μ’…λ₯˜λŠ” μœ„μ™€ 같이 κ½€λ‚˜ λ‹€μ–‘ν•˜λ‹€.

 

πŸ”‘ Dummy

λ”λ―ΈλŠ” μ–΄λ– ν•œ λ™μž‘λ„ ν•„μš”ν•˜μ§€ μ•Šμ§€λ§Œ, μ–΄λ– ν•œ κΈ°λŠ₯을 μ‚¬μš©ν•  λ•Œ νŠΉμ • 객체의 μΈμŠ€ν„΄μŠ€λ§Œμ΄ ν•„μš”ν•  λ•Œ μ‚¬μš©λœλ‹€.

λ‹€μ‹œ 말해, λ”λ―ΈλŠ” λ™μž‘ν•˜μ§€ μ•Šμ•„λ„ ν…ŒμŠ€νŠΈμ— μ–΄λ– ν•œ 영ν–₯도 λ―ΈμΉ˜μ§€ μ•ŠλŠ” 객체λ₯Ό λœ»ν•œλ‹€.

예제λ₯Ό 톡해 μ •ν™•ν•œ μ‚¬μš© 상황을 μ‚΄νŽ΄λ³΄μž

 

 

Accelerator.java

public interface Accelerator {
    int push();
}

μžλ™μ°¨λ₯Ό μ•žμœΌλ‘œ κ°€κ²Œν•˜κΈ° μœ„ν•œ μžλ™μ°¨μ˜ 힘(int)을 returnν•˜λŠ” push()λΌλŠ” ν–‰μœ„μ— μ±…μž„μ„ 가진 Accelerator κ°€ μ‘΄μž¬ν•œλ‹€.

 

 

RandomMoveAccelerator.java

public class RandomMoveAccelerator implements Accelerator {
    public static final int MIN_ACCEL_POWER = 0;
    public static final int MAX_ACCEL_POWER = 9;
    public static final int MIN_MOVABLE_POWER = 4;

    public int push() {
        return RandomNumberUtils.generate(MIN_ACCEL_POWER, MAX_ACCEL_POWER);
    }
}

μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ ν”„λ‘œλ•μ…˜ μ½”λ“œλŠ” push() λ©”μ„œλ“œ μ‹€ν–‰ μ‹œμ—, 0~9 μ‚¬μ΄μ˜ λ‚œμˆ˜λ₯Ό μƒμ„±ν•˜μ—¬ returnν•΄μ•Όν•œλ‹€.

 

 

Car.java

public class Car {
    private final CarName name;
    private int position;
    private final Accelerator accelerator;

    public Car(String name, Accelerator accelerator) {
        this.name = new CarName(name);
        this.position = 0;
        this.accelerator = accelerator;
    }

    public void pushAccelerator() {
        moveForward(accelerator.push());
    }

    private void moveForward(int power) {
        if (power >= RandomMoveAccelerator.MIN_MOVABLE_POWER) {
            position++;
        }
    }

 

Car ν΄λž˜μŠ€λŠ” μ΄λŸ¬ν•œ RandomMoveAcceleratorλ₯Ό μƒμ„±μž μ£Όμž…λ°›μ•„, μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ‚΄λΆ€μ—μ„œ λžœλ€κ°’μ„ ν™œμš©ν•˜μ—¬ position을 μ¦κ°€μ‹œν‚¨λ‹€.

 

ν•˜μ§€λ§Œ ν…ŒμŠ€νŠΈμ—μ„œ μ΄λŸ¬ν•œ Car 객체λ₯Ό 생성할 λ•Œ, RandomMoveAcceleratorλŠ” μ‚¬μš©ν•˜μ§€ μ•Šμ§€λ§Œ, Car 객체λ₯Ό μƒμ„±ν•˜κΈ° μœ„ν•΄ AcceleratorλŠ” μ£Όμž…ν•΄μ•Όμ£Όμ–΄μ•Ό ν•˜λŠ” κ²½μš°μ—λŠ” μ–΄λ–»κ²Œ ν• κΉŒ?

 

μ΄λ•Œ μ‚¬μš©ν•˜λŠ” 것이 더미이닀.

 

 

BridgeGeneratorDummy.java

public class AcceleratorDummy implements Accelerator {
    @Override
    public int push() {
        return 0;
    }
}

 

μ΄λŸ¬ν•œ 상황에 λΉ—λŒ€μ–΄ λ³΄μ•˜μ„ λ•Œ, μœ„μ™€ 같이 μ•„λ¬΄λŸ° λ™μž‘λ„ ν•˜μ§€ μ•ŠλŠ” AcceleratorDummy 클래슀λ₯Ό 톡해 ”μ–΄λ– ν•œ λ™μž‘λ„ ν•˜μ§€ μ•Šκ³  μΈμŠ€ν„΄μŠ€ μžμ²΄λ§Œμ„ μ œκ³΅ν•œλ‹€” λΌλŠ” Dummy의 역할에 λŒ€ν•΄ μ§κ΄€μ μœΌλ‘œ 이해할 수 μžˆλ‹€.

  • λ¬Όλ‘  return type이 intν˜•μ΄κΈ° λ•Œλ¬Έμ— 0을 returnν•˜κΈ΄ ν•˜μ§€λ§Œ, μ–΄λ– ν•œ λ‘œμ§μ²˜λ¦¬λ„ ν•˜μ§€ μ•ŠμœΌλ©° κ·Έμ € 0을 λ¦¬ν„΄ν•œλ‹€.
  • Dummy둜 μ‚¬μš©λ  것이기 λ•Œλ¬Έμ— push λΌλŠ” λ©”μ„œλ“œκ°€ μ‚¬μš©λ  일이 μ—†λŠ” κ²½μš°μ—λ§Œ μ‚¬μš©ν•œλ‹€.

 

μ–΄μ§œν”Ό μ‹€ν–‰μ‹œν‚€μ§€ μ•Šμ„ κ±°λ©΄ ν”„λ‘œλ•μ…˜ μ½”λ“œμ— κ΅¬ν˜„ν•΄λ†“μ€ κ΅¬ν˜„μ²΄λ₯Ό μ‚¬μš©ν•˜λ©΄ λ˜μ§€ μ•Šμ„κΉŒ?

λ¬Όλ‘  κ°€λŠ₯ν•˜λ‹€κ³  μƒκ°ν•œλ‹€. ν•˜μ§€λ§Œ 이 기쀀은 상황에 따라 λ‹€λ₯Ό 것 κ°™λ‹€.

ν•˜μ§€λ§Œ λ§Œμ•½ ν”„λ‘œλ•μ…˜ μ½”λ“œμ— κ΅¬ν˜„ν•΄λ†“μ€ κ΅¬ν˜„μ²΄κ°€ μ‹±κΈ€ν†€μœΌλ‘œ μ‚¬μš©λ˜κ³  μžˆλ‹€λ©΄, ν…ŒμŠ€νŠΈμ—μ„œλ„ ν”„λ‘œλ•μ…˜ μ½”λ“œμ—μ„œ μ‚¬μš©μ€‘μΈ μΈμŠ€ν„΄μŠ€μ™€ 같은 μΈμŠ€ν„΄μŠ€λ₯Ό μ‚¬μš©ν•˜κ²Œ λœλ‹€.

 

μ΄λ•Œ ν”„λ‘œλ•μ…˜ μ½”λ“œμ˜ κ΅¬ν˜„μ²΄ 내뢀에 μ •μ μœΌλ‘œ μƒνƒœλ₯Ό 가지고 μžˆλŠ” 클래슀 λ³€μˆ˜κ°€ μžˆλ‹€κ³  ν•˜λ©΄? ν…ŒμŠ€νŠΈμ˜ λ™μž‘μ΄ μ‹€μ œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μž‘λ™μ— 영ν–₯을 끼칠 μˆ˜λ„ μžˆλ‹€.

 

μ΄λŸ¬ν•œ κ²½μš°μ— 더미λ₯Ό μ‚¬μš©ν•˜λ©΄ μ μ ˆν•˜μ§€ μ•Šλ‚˜ μ‹Άλ‹€.

 

 


 

πŸ”‘ Fake

페이크 κ°μ²΄λŠ” λ³΅μž‘ν•œ λ‘œμ§μ΄λ‚˜, κ°μ²΄μ—μ„œ ν•„μš”λ‘œν•˜λŠ” λ‹€λ₯Έ μ™ΈλΆ€ κ°μ²΄λ“€μ˜ λ™μž‘μ„ λ‹¨μˆœν™”ν•˜μ—¬ κ΅¬ν˜„ν•œ 객체이닀.

페이크 κ°μ²΄μ—μ„œ λ‹¨μˆœν™”λœ λ™μž‘μ€ μ‹€μ œ ν”Œλ‘œμš°μ™€ μœ μ‚¬ν•˜κ²Œ κ΅¬ν˜„μ€ λ˜μ–΄ μžˆμ§€λ§Œ, μ‹€μ œ ν”„λ‘œλ•μ…˜ μ½”λ“œμ—λŠ” μ ν•©ν•˜μ§€ μ•ŠλŠ” μ½”λ“œμ΄λ‹€.

예제λ₯Ό 톡해 μžμ„Ένžˆ μ‚΄νŽ΄λ³΄μž

 

 

예제 μ½”λ“œ

 

MemberRepository.java

public interface MemberRepository {
    Member save(Member member);
}

Member 객체λ₯Ό μž…λ ₯λ°›μ•„μ„œ μ €μž₯μ†Œμ— μ €μž₯ν•œλ‹€λŠ” ν–‰μœ„μ˜ μ±…μž„μ„ 가진 MemberRepository μΈν„°νŽ˜μ΄μŠ€μ˜ save() λ©”μ„œλ“œκ°€ μ‘΄μž¬ν•œλ‹€κ³  κ°€μ •ν•˜μž.

 

MemberRepositoryImpl.java

public class MemberRepositoryImpl {
    Member save(Member member) {
				// DB μ €μž₯ 둜직..
		}
}

μ‹€μ œ ν”„λ‘œλ•μ…˜ μ½”λ“œμ—μ„  MemberRepositoryImplμ΄λΌλŠ” κ΅¬ν˜„μ²΄κ°€ Member 객체λ₯Ό DB에 μ €μž₯ν•˜λŠ” λ‘œμ§μ„ μˆ˜ν–‰ν•  것이닀.

그리고 μ €μž₯λ˜λŠ” Member μ—”ν‹°ν‹°μ˜ PKλŠ” 1λ²ˆλΆ€ν„° μžλ™μœΌλ‘œ μ¦κ°€ν•˜λŠ” Auto Increment 섀정이 λ˜μ–΄μžˆλ‹€.

μš°λ¦¬λŠ” 이제 이 λ‘œμ§μ— λŒ€ν•΄ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•΄λ³΄λ €κ³  ν–ˆμ§€λ§Œ 문제λ₯Ό λ°œκ²¬ν•˜κ²Œ λœλ‹€.

 

DBλŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ„œλ²„μ˜ μ™ΈλΆ€λ‘œ λΆ„λ¦¬λ˜μ–΄ μžˆλ‹€. λ•Œλ¬Έμ— λ„€νŠΈμ›Œν¬μ— λ¬Έμ œκ°€ λ°œμƒν•˜λ©΄, DB에 μ ‘κ·Όν•΄μ„œ 데이터λ₯Ό μΆ”κ°€ν•˜λŠ” λ‘œμ§μ„ ν…ŒμŠ€νŠΈν•˜λŠ” κ³Όμ •μ—μ„œ μ—λŸ¬κ°€ λ°œμƒν•œλ‹€. λ„€νŠΈμ›Œν¬μ— 따라 ν…ŒμŠ€νŠΈκ°€ μ‹€νŒ¨ν•  μˆ˜λ„ 있게 λœλ‹€λŠ” 것이닀.

μ΄λŸ¬ν•œ λ„€νŠΈμ›Œν¬ 영ν–₯을 μ—†μ• λ©΄μ„œ λ˜‘κ°™μ€ λ™μž‘μ„ μˆ˜ν–‰ν•˜μ—¬ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό 톡해 κΈ°λŠ₯이 μ–΄λ–»κ²Œ μž‘λ™ν•˜λŠ” 지 μ§κ΄€μ μœΌλ‘œ κ°œλ°œμžμ—κ²Œ μΈμ‹μ‹œμΌœμ£ΌκΈ° μœ„ν•œ λ°©λ²•μœΌλ‘œ 무엇이 μžˆμ„κΉŒ?

μ΄λ•Œ μ‚¬μš©λ˜λŠ” 것이 λ°”λ‘œ Fake 객체이닀.

 

FakeMemberRepository.java

public class FakeMemberRepository implements MemberRepository {
    private static final Map<Long, Member> members = new ConcurrentHashMap<>();
    private static Long idSequence = 1L;
    
    @Override
    public Member save(Member member) {
        members.put(idSequence++, member);
        return member;
    }
}

 

FakeMemberRepositoryλŠ” μ‹€μ œ DBκ°€ μ•„λ‹Œ Map에 데이터λ₯Ό μ €μž₯ν•¨μœΌλ‘œμ¨ 데이터가 μ €μž₯μ†Œμ— μ €μž₯λ˜λŠ” 것과, idSequenceλ₯Ό μ‚¬μš©ν•¨μœΌλ‘œμ¨ Member의 PKκ°€ Auto Increment λ˜λŠ” 것을 μ •ν™•νžˆ κ΅¬ν˜„ν•œ Fake 객체이닀.

 

μ΄λŸ¬ν•œ λ‚΄λΆ€ κ΅¬ν˜„ λ‹¨μˆœν™”λ₯Ό 톡해 ν…ŒμŠ€νŠΈλ₯Ό ν•˜λŠ” κ°œλ°œμžμ—κ²Œ DB에 μˆ˜ν–‰λ˜λŠ” 것과 λ™μΌν•œ κ²°κ³Όλ₯Ό μ „λ‹¬ν•˜λ©°, μ™ΈλΆ€ μš”μΈμ— 영ν–₯을 받지 μ•Šκ³  항상 μ„±κ³΅ν•˜λŠ” ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•  수 μžˆλ„λ‘ λ•λŠ” 것이 λ°”λ‘œ Fake 객체의 역할이닀.

 

 


 

πŸ”‘ Stub

μŠ€ν…μ€ μœ„μ—μ„œ μ†Œκ°œν•œ 더미 객체가 μ‹€μ œ λ™μž‘ν•˜λŠ” κ²ƒμ²˜λŸΌ 보이게 λ§Œλ“€μ–΄λ†“μ€ 객체이닀.

μΈν„°νŽ˜μ΄μŠ€ λ˜λŠ” κΈ°λ³Έ ν΄λž˜μŠ€κ°€ μ΅œμ†Œν•œμœΌλ‘œλ§Œ κ΅¬ν˜„λœ μƒνƒœμ΄λ©°, ν…ŒμŠ€νŠΈμ—μ„œ 호좜된 μš”μ²­μ— λŒ€ν•΄ 미리 μ€€λΉ„λœ κ²°κ³Όλ₯Ό μ œκ³΅ν•œλ‹€.

μ‰½κ²Œ 말해 μŠ€ν…μ€, μƒνƒœ 검증을 μœ„ν•œ ν…ŒμŠ€νŠΈ 더블이닀.

 

μ •λ¦¬ν•˜μžλ©΄ λ‚΄λΆ€ λ™μž‘μ— λŒ€ν•œ κ΅¬ν˜„μ€ λͺ¨λ‘ μ œμ™Έν•˜κ³ , λ™μž‘μ— λŒ€ν•œ κ²°κ³Όλ§Œμ„ μ–»κ³  싢을 λ•Œ μ‚¬μš©ν•œλ‹€.

μ•„λž˜ μ˜ˆμ œλŠ” μ΄ˆλ°˜μ— κ°€μž 객체λ₯Ό μ‚¬μš©ν•˜λ©΄ μ˜λ―Έκ°€ 없지 μ•Šμ„κΉŒ? μ—μ„œ μ„€λͺ…ν•œ λ‚΄μš©κ³Ό λ™μΌν•œ μ˜ˆμ œμ΄λ‹€.

예제λ₯Ό 톡해 μŠ€ν…μ„ μ΄ν•΄ν•΄λ³΄μž

 

 

예제 μ½”λ“œ

 

Accelerator.java

public interface Accelerator {
    int push();
}

μžλ™μ°¨λ₯Ό μ•žμœΌλ‘œ κ°€κ²Œν•˜κΈ° μœ„ν•œ μžλ™μ°¨μ˜ 힘(int)을 returnν•˜λŠ” push()λΌλŠ” ν–‰μœ„μ— μ±…μž„μ„ 가진 Accelerator κ°€ μ‘΄μž¬ν•œλ‹€.

 

RandomMoveAccelerator.java

public class RandomMoveAccelerator implements Accelerator {
    public static final int MIN_ACCEL_POWER = 0;
    public static final int MAX_ACCEL_POWER = 9;
    public static final int MIN_MOVABLE_POWER = 4;

    public int push() {
        return RandomNumberUtils.generate(MIN_ACCEL_POWER, MAX_ACCEL_POWER);
    }
}

μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ ν”„λ‘œλ•μ…˜ μ½”λ“œλŠ” push() λ©”μ„œλ“œ μ‹€ν–‰ μ‹œμ—, 0~9 μ‚¬μ΄μ˜ λ‚œμˆ˜λ₯Ό μƒμ„±ν•˜μ—¬ returnν•΄μ•Όν•œλ‹€.

 

Car.java

public class Car {
    private final CarName name;
    private int position;
    private final Accelerator accelerator;

    public Car(String name, Accelerator accelerator) {
        this.name = new CarName(name);
        this.position = 0;
        this.accelerator = accelerator;
    }
		
		public void pushAccelerator() {
        moveForward(accelerator.push());
    }

    private void moveForward(int power) {
        if (power >= RandomMoveAccelerator.MIN_MOVABLE_POWER) {
            position++;
        }
    }

 

Car ν΄λž˜μŠ€λŠ” Accelerator μΈν„°νŽ˜μ΄μŠ€λ₯Ό 톡해 μ΄λŸ¬ν•œ RandomMoveAcceleratorλ₯Ό μƒμ„±μž μ£Όμž…λ°›κ³ , moveForward(int power) λ©”μ„œλ“œλ₯Ό 톡해 λ‚΄λΆ€μ—μ„œ λžœλ€κ°’μ„ ν™œμš©ν•˜μ—¬ position을 μ¦κ°€μ‹œν‚¨λ‹€.

 

position μ¦κ°€λ‘œμ§μ˜ λΆ„κΈ°μ²˜λ¦¬λŠ” μ•„λž˜μ™€ κ°™λ‹€.

  • λ‚œμˆ˜κ°€ 0~4이면 position을 κ·ΈλŒ€λ‘œ
  • 5~10이면 position을 1 증가

 

ν•˜μ§€λ§Œ moveForward(int power)λŠ” λ‚œμˆ˜μ— μ˜μ‘΄ν•˜κΈ° λ•Œλ¬Έμ— moveForward의 position 증가 ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•  λ•Œ, μš°λ¦¬κ°€ μ›ν•˜λŠ” κ²°κ³Όλ₯Ό λ§Œλ“€κΈ° μ–΄λ €μ›Œλ³΄μΈλ‹€.

 

ν…ŒμŠ€νŠΈλ₯Ό ν•˜κ² λ‹€κ³  private인 moveForward의 μ ‘κ·Όμ œμ–΄μžλ₯Ό public으둜 λ³€κ²½ν•  μˆ˜λ„ μ—†λŠ” 노릇이닀.

이럴 λ•Œ Stub 객체λ₯Ό μ‚¬μš©ν•˜μ—¬, μš°λ¦¬κ°€ μ›ν•˜λŠ” 값을 returnν•˜λ„λ‘ κ΅¬ν˜„ν•˜λ©΄ moveForward에 λŒ€ν•΄ μ›ν•˜λŠ” κ²°κ³Όλ₯Ό λ§Œλ“€μ–΄λ‚Ό 수 μžˆλ‹€.

 

 

StubAccelerator.java

public class StubAccelerator implements Accelerator {
    private int power;
    
    public StubAccelerator() {
        this.power = 0; // NPE 방지λ₯Ό μœ„ν•΄ κΈ°λ³Έκ°’ μ£Όμž…
    }
    
    public void setPower(final int power) {
        this.power = power;
    }

    public int push() {
        return power;
    }
}

 

μœ„μ™€ 같이 Accelerator에 λŒ€ν•œ μŠ€ν…μ„ μƒμ„±ν•˜λ©΄ push() λ©”μ„œλ“œκ°€ 랜덀 값이 μ•„λ‹Œ μš°λ¦¬κ°€ μ„€μ •ν•œ 값을 returnν•˜λ„λ‘ λ§Œλ“€ 수 μžˆλ‹€.

보톡 μœ„ μ½”λ“œμ™€ 같이 μ›ν•˜λŠ” μ„€μ • 값을 ν•„λ“œλ‘œ λ§Œλ“€μ–΄λ‘κ³  setterλ₯Ό 톡해 값을 μ„€μ •ν•˜λ„λ‘ κ΅¬ν˜„ν•œλ‹€.

  • μƒμ„±μžλ₯Ό 톡해 power의 값을 μ§€μ •ν•˜λŠ” 방법도 μžˆκ² μ§€λ§Œ,
    ν”„λ‘œλ•μ…˜ μ½”λ“œμ˜ Accelerator κ΅¬ν˜„μ²΄λŠ” powerλ₯Ό 인자둜 μž…λ ₯λ°›λŠ” μƒμ„±μžκ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ”λ‹€.
    λ•Œλ¬Έμ— ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•΄ μƒˆλ‘œμš΄ μΈμŠ€ν„΄μŠ€ 생성 κ·œμΉ™(μƒμ„±μž)을 λ§Œλ“€μ–΄μ£ΌλŠ” 것은 ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό 톡해 ν”„λ‘œλ•μ…˜ μ½”λ“œμ˜ λ™μž‘ λ‘œμ§μ„ νŒŒμ•…ν•˜λŠ” 것에 ν˜Όλž€μ„ μ•ΌκΈ°ν•  수 μžˆλ‹€.
    λ•Œλ¬Έμ— Stub μ‚¬μš©μ— μžˆμ–΄μ„œλŠ” μƒμ„±μžλ³΄λ‹¨ Setter μ‚¬μš©μ„ 지ν–₯ν•˜μž!

 

 


 

πŸ”‘ Spy

슀파이 κ°μ²΄λŠ” μŠ€ν…μ˜ 역할을 κ°€μ§€λ©΄μ„œ 객체의 μƒνƒœλ₯Ό κ΄€μ°°ν•˜κ³  κΈ°λ‘ν•˜λŠ” 데 μ‚¬μš©λ˜λ©° 주둜 λ©”μ†Œλ“œ 호좜과 κ΄€λ ¨λœ 정보λ₯Ό κΈ°λ‘ν•˜κ³  λ°˜ν™˜ν•œλ‹€. μ΄λŠ” 특히 ν…ŒμŠ€νŠΈ 쀑에 객체가 μ–΄λ–»κ²Œ μƒν˜Έμž‘μš©ν•˜λŠ”μ§€ μΆ”μ ν•˜κ³  ν™•μΈν•˜λŠ” 데 μœ μš©ν•˜λ‹€.

μŠ€ν…μ˜ 역할을 가진닀고 ν•΄μ„œ μ™„μ „νžˆ μŠ€ν…κ³Ό λ™μΌν•œ 것은 μ•„λ‹ˆλ‹€.

 

νŠΉμ • 뢀뢄은 μ‹€μ œ 객체와 λ™μΌν•˜κ²Œ κ΅¬ν˜„ν•  수 있고, νŠΉμ • 뢀뢄은 μŠ€ν…μœΌλ‘œ κ΅¬ν˜„ν•  수 μžˆλ‹€. 그리고 μŠ€ν…μ€ μ–΄λ– ν•œ ν–‰μœ„μ— λŒ€ν•œ κ²°κ³Όλ₯Ό μ–»κΈ° μœ„ν•΄ μ‚¬μš©ν•˜μ§€λ§Œ, 슀파이 κ°μ²΄λŠ” λ‚΄λΆ€ λ‘œμ§μ— μˆ¨μ–΄μ„œ μ‹€ν–‰ 횟수 λ“±μ˜ 객체의 μƒνƒœλ₯Ό κ΄€μ°°ν•˜κ³  κΈ°λ‘ν•˜λŠ” 역할을 ν•œλ‹€.

μ‹€μ œ 객체와 λ™μΌν•˜κ²Œ λ‘œμ§μ„ κ΅¬ν˜„ν•˜λŠ” μ΄μœ λŠ” 둜직의 μ‹€ν–‰ 속도 등을 μ²΄ν¬ν•΄μ•Όν•˜λŠ” 상황일 λ“― μ‹Άλ‹€. λ¬Όλ‘  μ‹€μ œ 객체와 λ™μΌν•˜κ²Œ λ™μž‘ν•˜λ„λ‘ κ΅¬ν˜„ν•  ν•„μš”κ°€ μ—†κ±°λ‚˜, κ΅¬ν˜„ν•  수 μ—†λŠ” 뢀뢄에 λŒ€ν•΄μ„œλŠ” μŠ€ν…μœΌλ‘œ κ΅¬ν˜„ν•΄λ„ λœλ‹€.

 

결둠적으둜 κ΅¬ν˜„μ€ μ‹€μ œ 객체와 μŠ€ν… μ‚¬μ΄μ—μ„œ ν•„μš”ν•œ 것을 μ·¨ν•΄μ„œ λ§Œλ“€λ©΄ λ˜λŠ” 것이고, 객체의 λ‚΄λΆ€μ—μ„œ μ–΄λ– ν•œ 정보λ₯Ό μΈ‘μ •ν•˜κ±°λ‚˜ κΈ°λ‘ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λ˜λŠ” 객체인 것이닀.

 

 

예제 μ½”λ“œ

 

SpyRandomMoveAccelerator.java

public class SpyRandomMoveAccelerator implements Accelerator {
    public static final int MIN_ACCEL_POWER = 0;
    public static final int MAX_ACCEL_POWER = 9;
    public static final int MIN_MOVABLE_POWER = 4;

    private int calledCount = 0;

    public int push() {
        calledCount++;
        return RandomNumberUtils.generate(MIN_ACCEL_POWER, MAX_ACCEL_POWER);
    }
    
    public int getCalledCount() {
        return calledCount;
    }
}

 

μœ„ μ½”λ“œλŠ” RandomMoveAccelerator의 Spy 객체이닀.

μœ„μ—μ„œ μ†Œκ°œν•œ RandomMoveAccelerator ν΄λž˜μŠ€μ™€ λ˜‘κ°™μ΄ μž‘λ™ν•˜μ§€λ§Œ, λ‚΄λΆ€μ μœΌλ‘œ calledCount λΌλŠ” μ •μˆ˜ μžλ£Œν˜•μ΄ push() λ©”μ„œλ“œμ˜ 호좜 횟수λ₯Ό κΈ°λ‘ν•œλ‹€.

 

λ˜ν•œ getCalledCount() λ©”μ„œλ“œλ₯Ό 톡해 push() λ©”μ„œλ“œκ°€ μ–Όλ§ˆλ‚˜ 호좜 λ˜μ—ˆλŠ” 지 확인할 수 μžˆλ‹€.

μ΄λ ‡κ²Œ λ©”μ„œλ“œμ˜ 호좜 횟수 λ“±κ³Ό 같은 객체의 ν–‰μœ„ λ˜λŠ” μƒνƒœ 등을 κ΄€μ°°ν•˜κ³  κΈ°λ‘ν•˜λŠ” 역할을 ν•˜λŠ” 객체가 λ°”λ‘œ 슀파이 객체이닀.

 

 


 

πŸ”‘ Mock

Mockμ΄λž€ ν•œκΈ€λ‘œ λͺ¨μ˜μ˜, κ°€μ§œμ˜ λΌλŠ” λœ»μ„ κ°€μ§€λŠ” ν…ŒμŠ€νŠΈ 더블 객체둜, μ‹€μ œ 객체와 λ™μΌν•œ λͺ¨μ˜ 객체λ₯Ό λ§Œλ“€μ–΄ ν…ŒμŠ€νŠΈμ˜ νš¨μš©μ„± 높이기 μœ„ν•΄ μ‚¬μš©ν•œλ‹€.

 

μƒνƒœλ₯Ό κ²€μ¦ν•˜λŠ” Stubκ³ΌλŠ” λ‹€λ₯΄κ²Œ, μ–΄λ– ν•œ ν–‰μœ„λ₯Ό 잘 μˆ˜ν–‰ν•˜λŠ” 지에 λŒ€ν•΄ κ²€μ¦ν•˜λŠ” ν–‰μœ„ 검증 을 μœ„ν•œ ν…ŒμŠ€νŠΈ 더블 객체이닀.

Javaν™˜κ²½μ—μ„  주둜 Mockitoλ₯Ό 톡해 ν…ŒμŠ€νŠΈλ₯Ό μˆ˜ν–‰ν•˜κ²Œ λœλ‹€.

 

Mockitoλž€?

mock을 μ‰½κ²Œ λ§Œλ“€κ³  mock의 행동을 μ •ν•˜λŠ” stubbing, μ •μƒμ μœΌλ‘œ μž‘λ™ν•˜λŠ” 지 κ²€μ¦ν•˜λŠ” verify λ“± λ‹€μ–‘ν•œ κΈ°λŠ₯을 μ œκ³΅ν•΄μ£ΌλŠ” ν”„λ ˆμž„μ›Œν¬μ΄λ‹€.

DB연동 μž‘μ—…λ“±κ³Ό 같은 ν…ŒμŠ€νŠΈ μžλ™ν™”κ°€ μ–΄λ ΅κ±°λ‚˜, μš”μ²­μ— μ‹œκ°„μ΄ 였래 κ±Έλ¦¬λŠ” λ‚΄μš©λ“€μ€ Mockitoλ₯Ό 톡해 κ°€μ§œ 객체λ₯Ό μƒμ„±ν•˜μ—¬ ν–‰μœ„μ— λŒ€ν•œ 검증을 μ‹ μ†ν•˜κ²Œ μˆ˜ν–‰ν•œλ‹€.

 

예제 μ½”λ“œ

(μ μš©ν•΄λ³΄κ³  μž‘μ„±)

 

 

 

 

 

결함 λ‚΄μ„±

(ν•™μŠ΅ν•΄μ„œ μ±„μš°κΈ°)