Tdd 4장

30
Mock 을 을을을 TDD 을을을 을을을을 을을을을 을을 2011. 02. 26 을을을

Transcript of Tdd 4장

Page 1: Tdd 4장

Mock 을 이용한 TDD테스트 주도개발 실천법과 도구

2011. 02. 26원종필

Page 2: Tdd 4장

Ex) 사용자 암호 저장기능에 대한 테스트 케이스

Cipher 에서 MD5 암호화 / 복호화 처리를 해야 하는데…아직 UserRegister 구현도 안했는데 .. TDD 를 하려면 Cipher 도 구현하면서 해야하는가 ??

public void testSavePassword() throw Exception {UserRegister register = new UserRegister();

Cipher cipher = ??? // TODO 암호화 필요

String userId = "sweet88”;String password = "potato";

register.savePassword(userId, cipher.encrypt(password));String decryptedPassword =

cipher.decrypt(register.getPassword(userId));assertEquals(password, decryptedPassword)

}

?

Page 3: Tdd 4장

MD5 를 적용하고 있는 것 처럼 보이는 객체를 만들어서 개발하자 !

그래 ! 이거야 !!

Page 4: Tdd 4장

MD5 동작을 흉내내어서 ..public class MockMD5Cipher implements Cipher {

public String decrypt(String source) { return "potato” }public String encrypt(String source) {

return "8ee2027983915ec78acc45027d874316"}

}

public void testSavePassword() throw Exception {UserRegister register = new UserRegister();

Cipher cipher = new MockMD5Cipher();

String userId = "sweet88";String password = "potato";

register.savePassword(userId, cipher.encrypt(password));String decryptedPassword = cipher.decrypt(register.getPassword(userId));assertEquals(password, decryptedPassword)

}

Cipher 구현에 관계없이 UserRegister 테스트를 잘 진행할 수 있다 ~

Page 5: Tdd 4장

MockMD5Cipher 와 같이 실제 모듈과 비슷하게 보이도록 만든 각자 객체

Mock Object

Page 6: Tdd 4장

Mock Object

조작하기 쉬운 재료를 이용해 추후 만들어질 제품의 외향을 흉내 낸 모조품

모듈의 겉모양이 실제 모듈과 비슷하게 보이도록 만든 가짜 객체

실제 객체를 만들기엔 비용과 시간이 많이 들거나 의존성이 길게 걸쳐져 있어 제대로 구현하기 어려운 경우 사용

Page 7: Tdd 4장

Mock Object 를 사용하는 예

Ex) FTP 클라이언트에서 접속시도후 5 초까지 1 초마다 접속 재시도하고그 이후엔 연결 실패 메시지를 띄우는 기능 테스트와 같은경우

테스트 작성을 위한 환경 구축이 어려워서Ex) 환경구축에 시간이 많이 소요되는 상황 사용해야하는 모듈이 개발전 타부서와 협의 , 정책이 필요한 경우

테스트가 특정 상황이나 순간에 의존적이라서

테스트 시간이 오래 걸려서Ex) 네트워크를 이용해서 시간이 걸리는 경우에 영향 테스트

Page 8: Tdd 4장

Test Double실제 객체를 사용해서 테스트를 진행하기 어려울 경우 이를 대신해서 테스트를 진행할 수 있도록 만들어지는 객체를 지칭

Test Double

DummyObject

TestStub

TestSpy

MockObject

FakeObject

Page 9: Tdd 4장

Test Double 을 하나씩 살펴봅시다

Page 10: Tdd 4장

Dummy Object인스턴스화된 객체가 필요할 뿐 해당 객체의 기능까지는 필요하지 않는 경우에 사용

public class DummyCoupon implements ICoupon {public int getDiscountPercent() { return 0; }public String getName() { return null; }public boolean isAppliable(Item item) { return false; }public boolean isValid() { return false; }public void doExpire() {}

}

더미 객체의 메소드는 호출을 가정하고 만들어진게 아니다

메소드의 리턴값은 기본값을 반환또는 메소드 호출시 에러를 발생시키기도 한다

Page 11: Tdd 4장

public void testAddCoupon() throws Exception {User user = new User("area88");assertEquals(" 쿠폰 수령 ", 0, user.getTotalCouponCount());

ICoupon coupon = new DummyCoupon();

user.addCoupon(coupon);

assertEquals(" 쿠폰 수령 후 ", 1, user.getTotalCouponCount());}

Dummy Object 사용 예제public class DummyCoupon implements ICoupon {

public int getDiscountPercent() { return 0; }public String getName() { return null; }public boolean isAppliable(Item item) { return false; }public boolean isValid() { return false; }public void doExpire() {}

}

Page 12: Tdd 4장

Test Stub더미 객체가 마치 실제로 동작하는 것처럼 보이게 만들어 놓은 객체

public class StubCoupon implements ICoupon {public int getDiscountPercent() { return 7; }public String getName() { return "VIP 고객 한가위 감사쿠폰 "; }public boolean isAppliable(Item item) { return true; }public boolean isValid() { return true; }public void doExpire() {}

}

public void testGetLastOccupiedCoupon() throw Exception {User user = new User("area88");ICoupon eventCoupon = new StubCoupon();user.addCoupon(eventCoupon);ICoupon lastCoupon = user.getLastOccupiedCoupon();assertEquals(" 쿠폰 할인율 ", 7, lastCoupon.getDiscountPercent());assertEquals(" 쿠폰 이름 ", "VIP 고객 한가위 감사쿠폰 ",

lastCoupon.getName());}

Page 13: Tdd 4장

Dummy Vs Stub

Dummy단지 인스턴스화 될 수 있는 객체 수준

Stub인스턴스화된 객체가 특정 상태나 모습을 대표

Page 14: Tdd 4장

Stub 의 단점

Stub 은 특정 객체의 상태가 하드 코딩된 형태

로직이 들어가는 부분의 테스트를 할 수 없다

Page 15: Tdd 4장

Fake Object여러 개의 인스턴스를 대표할 수 있는 경우이거나좀더 복잡한 구현이 들어가 있는 객체

복잡한 로직 , 객체 내부에서 필요로 하는 다른 외부 객체들의 동작을 비교적 단순하게 구현한 객체

테스트 케이스 작성을 진행하기 위해 필요한 다른 객체들과의 의존성을 제거하기 위해 사용

Page 16: Tdd 4장

public class FakeCoupon implements ICoupon {List<String> categoryList = new ArrayList();public FakeCoupon() {

categoryList.add(" 큰칼 ");categoryList.add(" 아주큰칼 ");categoryList.add(" 짧은 ");

}public boolean isAppliable(Item item) {

if(this.categoryList.contains(item.getCategory())) { return true; }return false;

} // …}

public class PriceCalculator {public int getOrderPrice(Item item, ICoupon coupon) {

if(coupon.isValid() && coupon.isAppliable(item)) { //discount}}

}public void testGetOderPrice() throws Exception() {

PriceCalculator calculator = new PriceCalculator();Item discount_item = new Item("LightSavor", " 큰칼 ", 10000);ICoupon coupon = new FakeCoupon();asserEquals(" 할인된 가격 ", 9300, calculator.getOrderPrice(item,coupon));Item not_discount_item = new Item("Sasimi", " 사시미 ", 10);assertEquals(" 할인안된 가격 ", 10, calculator.getOrderPrice(item, coupon));

}

Page 17: Tdd 4장

Fake Object 사용 주의

Fake Object 를 만들때 지나치게 집중하면 페이크 객체 자체를 테스트 해야할 정도로 복잡해질 수 있다

적절한 수준에서 구현을 접고 Mock framework 또는 실제 객체를 사용하여 테스트 케이스 작성을 권장

Page 18: Tdd 4장

Test Spy특정 객체가 사용됐는지 , 특정 메소드가 호출되었는지 확인 필요할 때 사용

호출 여부를 감시하여 기록해 두었다가 요청이 들어오면 해당 기록 정보를 전달한다

public class SpyCoupon implements ICoupon {List<String> categoryList new ArrayList();private int appliableCallCount;

public boolean isAppliable(Item item) {appliableCallCount++;if(this.categoryList.contains(item.getCategory())) {

return true;}return false;

}public int getAppliableCallCount() { return this.appliableCallCount; }

}

Page 19: Tdd 4장

Test Spy 사용 주의

아주 특수한 경우를 제외하고 잘 쓰이지 않는다

테스트 스파이가 필요한 경우 Mock Framework이용이 더 편함 대부분의 Mock Framework 에서 기본적으로 Test Spy 기능을 제공

Page 20: Tdd 4장

상태기반 테스트 Vs 행위기반 테스트

상태 기반 테스트 (State Base Test)테스트 대상 클래스의 메소드를 호출하고 그결과 값과 예상값을 비교하는 방식

행위 기반 테스트 ( Behavior Base Test)올바른 로직 수행에 판단의 근거로 특정한 동작의 수행여부를 사용메소드의 리턴값이 없거나 리턴값 확인만으로 예상대로 동작했음을 보증하기 어려움

Page 21: Tdd 4장

MethodBMethodA

MethodBMethodAcall

입력 A

입력 B

MethodA 를 테스트 하는 경우 입력값에 따른 차이를 MethodA 리턴값 조사만으로 알수 없다

MethodB 의 호출여부를 조사하지 않으면 MethodA 정상 동작 여부를 판단할 수 없다

Test Spy 를 사용하거나 자체적으로 검증 기능이 있는 Mock 객체를 사용

행위 기반 테스트가 필요한 예

Page 22: Tdd 4장

Mock Object

행위를 검증하기 위해 사용되는 객체를 지칭

수동으로 만드는건 고통이 매우 크기 때문에 대부분 Mock Framework 를 이용

Page 23: Tdd 4장

ICoupon coupon = new FakeCoupon();user.addCoupon(coupon);assertEquals(1, user.getTotalCouponCount());

ICoupon coupon = mock(ICoupon.class);coupon.expects(once().method("isValid")

.withAnyArguments()

.will(returnValue(true));user.addCoupon(coupon);assertEquals(1, user.getTotalCouponCount());

addCoupon() 내부에서 뭔가가 잘되고 있는지 테스트하기 힘들다

isValid 메소드가 1 번 호출될 것을 예상isValid 에서 사용할 인자는 무엇이든 상관안함 호출시 리턴값은 true 를 돌려주게 될 것임

일반적인 테스트

Mock Framework 을 이용한 테스트

Mock Framework 테스트 예

Page 24: Tdd 4장

Mock Object 용어에 대한 고찰

Mock Object == ‘행위 기반 테스트를 위해 사용하는 객체’

Mocking 은 행위 기반 테스트 객체가 아니라 Test Double 객체를 만드는것을 의미

Mock Object == Test Double 과 동일한 의미로 사용할 예정

기존에 설명했던 부분에서는 ..

앞으로는 ..

Page 25: Tdd 4장

Mock Framework 또는 모의 객체 프레임워크

Mock 객체를 직접 작성해서 명시적인 클래스를 만들지 않아도 된다

EasyMockjMockMockito

Mock Framework 사용시 좋은점

Mock 객체에 대해 행위까지도테스트 케이스에 포함시킬 수 있다

대표적인 Mock Framework

Page 26: Tdd 4장

대표적인 Mock Framework 의 사용법

책을 읽으면서 실습으로 배워보세요

사용법은 비슷비슷합니다

각자 알아서 ..스스로 학습…

Page 27: Tdd 4장

Mock Framework 사용시 유의사항Mock 프레임워크가 정말 필요한지 잘 따져본다

Mock 프레임워크를 사용하는 테스트케이스의 유지 비용이 지속적으로 발생

가능하다면 설계를 바꿔서라도 Mock 필요없는 의존성 적은 구조로 만드는게 더 좋다

투자 대비 수익이 확실할 때만 사용Mock 을 사용할때는 좀더 길게 볼 필요가 있다테스트 편의를 위해 사용하기 시작한 Mock 객체들이 개발진척의 발목을 잡을수도 있다

Page 28: Tdd 4장

Mock Framework 사용시 유의사항

어떤 Mock 프레임워크를 사용하느냐는 핵심적인 문제가 아니다

유행따라 최고의 Mock 프레임워크를 찾아 떠나는 행동은 하지마라 대부분의 경우 간단한 Mock 객체만으로도 충분하다

Mock 은 Mock 일 뿐Mock 을 사용한 테스트에서 잘동작해서 실제 객체를 적용해서 잘되리라는 보장은 없다초반부터 실제객체를 사용 가능하다면 Mock 객체를 사용하지 말고 실제객체를 사용해라

Page 29: Tdd 4장

Q / A

Page 30: Tdd 4장

END