Tdd 4장

Post on 23-Jun-2015

175 views 8 download

Transcript of Tdd 4장

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

2011. 02. 26원종필

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)

}

?

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

그래 ! 이거야 !!

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 테스트를 잘 진행할 수 있다 ~

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

Mock Object

Mock Object

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

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

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

Mock Object 를 사용하는 예

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

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

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

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

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

Test Double

DummyObject

TestStub

TestSpy

MockObject

FakeObject

Test Double 을 하나씩 살펴봅시다

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() {}

}

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

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

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() {}

}

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());}

Dummy Vs Stub

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

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

Stub 의 단점

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

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

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

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

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

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));

}

Fake Object 사용 주의

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

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

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; }

}

Test Spy 사용 주의

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

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

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

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

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

MethodBMethodA

MethodBMethodAcall

입력 A

입력 B

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

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

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

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

Mock Object

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

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

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 테스트 예

Mock Object 용어에 대한 고찰

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

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

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

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

앞으로는 ..

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

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

EasyMockjMockMockito

Mock Framework 사용시 좋은점

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

대표적인 Mock Framework

대표적인 Mock Framework 의 사용법

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

사용법은 비슷비슷합니다

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

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

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

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

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

Mock Framework 사용시 유의사항

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

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

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

Q / A

END