6. MockObject 조작하기 쉬운 재료를 이용해 추후 만들어질 제품의 외향을 흉내 낸 모조품 모듈의 겉모양이 실제 모듈과 비슷하게 보이도록 만든 가짜 객체 실제 객체를 만들기엔 비용과 시간이 많이 들거나 의존성이 길게 걸쳐져 있어 제대로 구현하기 어려운 경우 사용
7. MockObject를 사용하는 예 테스트 작성을 위한 환경 구축이 어려워서 Ex) 환경구축에 시간이 많이 소요되는 상황 사용해야하는 모듈이 개발전 타부서와 협의,정책이 필요한 경우 테스트가 특정 상황이나 순간에 의존적이라서 Ex) FTP클라이언트에서 접속시도후 5초까지 1초마다 접속 재시도하고 그 이후엔 연결 실패 메시지를 띄우는 기능 테스트와 같은경우 테스트 시간이 오래 걸려서 Ex) 네트워크를 이용해서 시간이 걸리는 경우에 영향 테스트
8. Test Double 실제 객체를 사용해서 테스트를 진행하기 어려울 경우 이를 대신해서 테스트를 진행할 수 있도록 만들어지는 객체를 지칭
10. Dummy Object 인스턴스화된 객체가 필요할 뿐 해당 객체의 기능까지는 필요하지 않는 경우에 사용 public class DummyCouponimplements ICoupon { public intgetDiscountPercent() {return 0;} public String getName() {return null;} public booleanisAppliable(Item item) {return false;} public booleanisValid() {return false;} public void doExpire() {} } 더미 객체의 메소드는호출을 가정하고 만들어진게 아니다 메소드의 리턴값은 기본값을 반환 또는 메소드 호출시 에러를 발생시키기도 한다
11. Dummy Object사용 예제 public class DummyCouponimplements ICoupon { public intgetDiscountPercent() {return 0;} public String getName() {return null;} public booleanisAppliable(Item item) {return false;} public booleanisValid() {return false;} public void doExpire() {} } public void testAddCoupon() throws Exception { User user = new User("area88"); assertEquals("쿠폰 수령", 0, user.getTotalCouponCount()); ICouponcoupon = new DummyCoupon(); user.addCoupon(coupon); assertEquals("쿠폰 수령 후", 1, user.getTotalCouponCount()); }
12. Test Stub 더미 객체가 마치 실제로 동작하는 것처럼 보이게 만들어 놓은 객체 public class StubCouponimplements ICoupon { public intgetDiscountPercent() { return 7; } public String getName() { return "VIP 고객 한가위 감사쿠폰"; } public booleanisAppliable(Item item) { return true; } public booleanisValid() { return true; } public void doExpire() {} } public void testGetLastOccupiedCoupon() throw Exception { User user = new User("area88"); ICouponeventCoupon = new StubCoupon(); user.addCoupon(eventCoupon); ICouponlastCoupon = user.getLastOccupiedCoupon(); assertEquals("쿠폰 할인율", 7, lastCoupon.getDiscountPercent()); assertEquals("쿠폰 이름", "VIP 고객 한가위 감사쿠폰", lastCoupon.getName()); }
13. Dummy Vs Stub Dummy 단지 인스턴스화 될 수 있는 객체 수준 Stub 인스턴스화된 객체가 특정 상태나 모습을 대표
14. Stub의 단점 Stub은 특정 객체의 상태가 하드 코딩된 형태 로직이 들어가는 부분의 테스트를 할 수 없다
15. Fake Object 여러 개의 인스턴스를 대표할 수 있는 경우이거나 좀더 복잡한 구현이 들어가 있는 객체 복잡한 로직, 객체 내부에서 필요로 하는 다른 외부 객체들의 동작을비교적 단순하게 구현한 객체 테스트 케이스 작성을 진행하기 위해 필요한 다른 객체들과의 의존성을 제거하기 위해 사용
16. public class FakeCouponimplements ICoupon { List<String> categoryList = new ArrayList(); public FakeCoupon() { categoryList.add("큰칼"); categoryList.add("아주큰칼"); categoryList.add("짧은"); } public booleanisAppliable(Item item) { if(this.categoryList.contains(item.getCategory())) { return true; } return false; } // …} public class PriceCalculator { public intgetOrderPrice(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)); }
17. Fake Object 사용 주의 Fake Object를 만들때 지나치게 집중하면 페이크 객체 자체를 테스트 해야할 정도로 복잡해질 수 있다 적절한 수준에서 구현을 접고 Mockframework 또는 실제 객체를 사용하여 테스트 케이스 작성을 권장
18. Test Spy 특정 객체가 사용됐는지, 특정 메소드가 호출되었는지 확인 필요할 때 사용 호출 여부를 감시하여 기록해 두었다가 요청이 들어오면 해당 기록 정보를 전달한다 public class SpyCouponimplements ICoupon { List<String> categoryListnew ArrayList(); private intappliableCallCount; public booleanisAppliable(Item item) { appliableCallCount++; if(this.categoryList.contains(item.getCategory())) { return true; } return false; } public intgetAppliableCallCount() { return this.appliableCallCount; } }
19. Test Spy 사용 주의 아주 특수한 경우를 제외하고 잘 쓰이지 않는다 테스트 스파이가 필요한 경우 Mock Framework 이용이 더 편함 대부분의 Mock Framework에서 기본적으로 Test Spy기능을 제공
20. 상태기반 테스트 Vs 행위기반 테스트 상태 기반 테스트(State Base Test) 테스트 대상 클래스의 메소드를 호출하고 그결과 값과 예상값을 비교하는 방식 행위 기반 테스트(Behavior Base Test) 올바른 로직 수행에 판단의 근거로 특정한 동작의 수행여부를 사용 메소드의 리턴값이 없거나 리턴값 확인만으로 예상대로 동작했음을 보증하기 어려움
21. 행위 기반 테스트가 필요한 예 MethodB MethodA 입력A call MethodB MethodA 입력B MethodA를 테스트 하는 경우 입력값에 따른 차이를 MethodA 리턴값 조사만으로 알수 없다 MethodB의 호출여부를 조사하지 않으면 MethodA 정상 동작 여부를 판단할 수 없다 Test Spy를 사용하거나 자체적으로 검증 기능이 있는 Mock 객체를 사용
22. Mock Object 행위를 검증하기 위해 사용되는 객체를 지칭 수동으로 만드는건 고통이 매우 크기 때문에 대부분 MockFramework를 이용
23. Mock Framework 테스트 예 일반적인 테스트 ICoupon coupon = new FakeCoupon(); user.addCoupon(coupon); assertEquals(1, user.getTotalCouponCount()); addCoupon() 내부에서 뭔가가 잘되고 있는지 테스트하기 힘들다 Mock Framework을 이용한 테스트 ICoupon coupon = mock(ICoupon.class); coupon.expects(once().method("isValid") .withAnyArguments() .will(returnValue(true)); user.addCoupon(coupon); assertEquals(1, user.getTotalCouponCount()); isValid 메소드가 1번 호출될 것을 예상 isValid에서 사용할 인자는 무엇이든 상관안함 호출시 리턴값은 true를 돌려주게 될 것임
24. Mock Object 용어에 대한 고찰 기존에 설명했던 부분에서는.. MockObject == ‘행위 기반 테스트를 위해 사용하는 객체’ 앞으로는.. Mock Object == Test Double과 동일한 의미로 사용할 예정 Mocking은 행위 기반 테스트 객체가 아니라 Test Double객체를 만드는것을 의미
25. Mock Framework또는 모의 객체 프레임워크 Mock Framework 사용시 좋은점 Mock 객체를 직접 작성해서 명시적인 클래스를 만들지 않아도 된다 Mock 객체에 대해 행위까지도 테스트 케이스에 포함시킬 수 있다 대표적인Mock Framework EasyMock jMock Mockito
27. Mock Framework 사용시 유의사항 Mock 프레임워크가 정말 필요한지 잘 따져본다 Mock 프레임워크를 사용하는 테 스트케이스의 유지 비용이 지속적으로 발생 가능하다면 설계를 바꿔서라도 Mock 필요없는 의존성 적은 구조로 만드는게 더 좋다 투자 대비 수익이 확실할 때만 사용 Mock을 사용할때는 좀더 길게 볼 필요가 있다 테스트 편의를 위해 사용하기 시작한 Mock객체들이 개발진척의 발목을 잡을수도있다
28. Mock Framework 사용시 유의사항 어떤 Mock 프레임워크를 사용하느냐는 핵심적인 문제가 아니다 유행따라 최고의 Mock 프레임워크를 찾아 떠나는 행동은 하지마라 대부분의 경우 간단한 Mock 객체만으로도 충분하다 Mock은 Mock일 뿐 Mock을 사용한 테스트에서 잘동작해서 실제 객체를 적용해서 잘되리라는 보장은 없다 초반부터 실제객체를 사용 가능하다면 Mock객체를 사용하지 말고 실제객체를 사용해라