반응형
TDD 학습 및 정리3에 이어 정리합니다.
Mock 객체란
- 하나의 예시로 자동차 설계 시 실 재료를 사용하면 비용이 많이듭니다.
나무를 대신해서 설계 뜬 자동차 모형을 Mock이라고 합니다.
- 제품의 외양을 흉내 낸 모조품
Mock 객체를 사용 해야 하는 시기
1. 테스트 작성을 위한 환경 구축
- 환경 구축을 위한 작업 시간이 많이 필요 한 경우에 Mock객체를 사용합니다.
- 특정 모듈을 아직 갖고 있지 않아서 테스트를 하지 못할 경우입니다.
- 타 부서와 협의나 정책이 필요한 경우에도 필요합니다.
2. 테스트가 특정 경우나 순간에 의존적 일 경우
- 예를 들어 네트워크 연결의 접속 시간 제한을 구현하는 경우
접속 시도 실패 후 5초까지 1초마다 재접속 시도 하고 이후엔 에러 메세지를 만들 때
- 파일 쓰기 시 데이터가 부정확하게 입력 되는 경우
3. 테스트 시간이 오래 걸릴 경우
- 특정 모듈을 호출 했을 때 시간이 오래 걸리는 경우
Mock의 기본
테스트 더블
오리지널 객체를 사용해서 테스트 진행이 어려울 경우 이를 대신하는 객체
테스트 더블의 하위 객체
- 더미 객체(Dummy Object) : 단순한 껍데기로 객체로써 역할은 하지 못합니다./ 메소드 호출 안함을 전제
- 테스트 스텁(Test Stub) : 더미 객체가 실제로 동작하는 것처럼 보이게 만든 객체입니다. / 특정 값으로 하드코딩
- 페이크 객체(Fake Object) : 여러 개의 객체를 대표하거나 복잡한 구현이 들어가 있는 객체입니다. / 객체 인척
- 테스트 스파이(Test Spy) : 특정 메소드의 정상 호출 여부 확인을 목적으로 합니다. / 더미부터 페이크까지 포함하고 감시 대상이 되는 건 무엇이든 기록합니다.
- Mock 객체(Mock Object) : 행위 기반으로 테스트 케이스를 작성합니다. / ?
Mock 실습하기
비밀번호 암호화/복호화
1. Mycipher 클래스를 다른 팀원이 만들어 준다는 전제
2. 아직 MockMD5Cipher 클래스는 우리에게 없다는 전제
3. Mock 클래스를 만들어 테스트 주도 개발을 함
MockTest.java
package mock;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class MockTest {
@Test
public void savePassword_패스워드저장() throws Exception{
UserRegister register = new UserRegister();
MyCipher cipher = new MockMD5Cipher();
String userId = "test";
String passwd = "potato";
register.savePassword(userId, cipher.encrypt(passwd));
String decrytedPassword = cipher.decrypt(register.getPaswd());
assertEquals(passwd, decrytedPassword);
}
}
MyCiphr
package mock;
public interface MyCipher {
public String encrypt(String source);
public String decrypt(String source);
}
MockMD5Cipher.java
package mock;
public class MockMD5Cipher implements MyCipher{
@Override
public String encrypt(String source) {
return "8ee2027983915ec78acc45027d874316";
}
@Override
public String decrypt(String source) {
return "potato";
}
}
테스트 더블
스턴트맨이라는 단어에서 차용
1. 유저의 쿠폰을 추가하는 상황을 전제
2. coupon 객체가 없는 상황을 전제
3. 각 단계별로 확인하는 예제
더미 객체, 테스트 스텁, 페이크 파크, 테스트 스파이
더미 객체, 테스트 스텁, 페이크 파크, 테스트 스파이
더미 객체
없는 객체를 임시로 만들어 테스트가 가능하도록 합니다.
단 해당 클래스는 호출 되지 않음을 전제합니다.
UserTest.java
package mock.testDouble;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class UserTest {
@Test
public void addCoupon_쿠폰추가하기() throws Exception{
User user = new User("test");
assertEquals("쿠폰 수령전", 0, user.getTotalCouponCount());
ICoupon coupon = new DoummyCoupon();
user.addCoupon(coupon);
assertEquals("쿠폰 추가 후", 1, user.getTotalCouponCount());
}
}
ICoupn.java
package mock.testDouble;
public interface ICoupon {
public String getName();
public boolean isValid();
public int getDiscountPercent();
public boolean isAppliable(Item item);
public void doExpire();
}
User.java
package mock.testDouble;
public class User {
private String name;
private ICoupon[] coupon;
private int couponCount;
public User() {
couponCount = 0;
}
public User(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getTotalCouponCount() {
return this.couponCount;
}
public void addCoupon(ICoupon coupon) {
this.couponCount++;
}
}
DummyCoupon.java
package mock.testDouble;
public class DoummyCoupon implements ICoupon {
@Override
public String getName() {
throw new UnsupportedOperationException("호출 되지 않는 메소드");
}
@Override
public boolean isValid() {
return false;
}
@Override
public int getDiscountPercent() {
return 0;
}
@Override
public boolean isAppliable(Item item) {
return false;
}
@Override
public void doExpire() {
}
}
테스트 스텁
객체를 생성하여 하드코딩하여 특정 구문에서만 구동 가능한 클래스를 생성합니다.
UserTest.java
@Test
public void getLastOccupiedCoupon_이벤트쿠폰발행() throws Exception {
User user = new User("test");
ICoupon eventCoupone = new StubCoupon();
user.addCoupon(eventCoupone);
ICoupon lastCoupon = user.getLastOccupiedCoupon();
assertEquals("구폰 할인율", 7, lastCoupon.getDiscountPercent());
assertEquals("쿠폰 이름", "VIP 고객 한가위 감사쿠폰", lastCoupon.getName());
}
@Test
public void getOrderPrice_discounttableItem_할인되는물건찾기() throws Exception{
PriceCalculator calculator = new PriceCalculator();
Item item = new Item("LightSavor", "부엌칼", 100000);
ICoupon coupon = new StubCoupon();
assertEquals("쿠폰으로 인해 할인 된 가격", 93000, calculator.getOrderPrice(item,coupon));
}
StubCoupon.java
package mock.testDouble;
public class StubCoupon implements ICoupon {
@Override
public String getName() {
return "VIP 고객 한가위 감사쿠폰";
}
@Override
public boolean isValid() {
return true;
}
@Override
public int getDiscountPercent() {
return 7;
}
@Override
public boolean isAppliable(Item item) {
return true;
}
@Override
public void doExpire() {
}
}
User.java
package mock.testDouble;
import java.util.ArrayList;
public class User {
private String name;
private ArrayList<ICoupon> coupon;
public User() {
this.coupon = new ArrayList<ICoupon>();
}
public User(String name) {
super();
this.coupon = new ArrayList<ICoupon>();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getTotalCouponCount() {
return this.coupon.size();
}
public void addCoupon(ICoupon coupon) {
this.coupon.add(coupon);
}
public ICoupon getLastOccupiedCoupon() {
return this.coupon.get(this.coupon.size()-1);
}
}
item.java
package mock.testDouble;
public class Item {
private String name;
private String category;
private int price;
public Item() {
}
public Item(String name, String category, int price) {
super();
this.name = name;
this.category = category;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
}
PriceCalculator.java
package mock.testDouble;
public class PriceCalculator {
public int getOrderPrice(Item item, ICoupon coupon) {
if (coupon.isValid() && coupon.isAppliable(item)) {
return (int) (item.getPrice() * getDiscountRate(coupon.getDiscountPercent()));
}
return item.getPrice();
}
private double getDiscountRate(int percent) {
return (100 - percent) / 100d;
}
}
하드 코딩 되지 않은 다른 물건을 호출할 경우 페이크 스텁에서 오류가 발생합니다.
페이크 스텁
특정 상황이 아닌 다른 여러 상황에서 동작 가능하도록 합니다.
UserTest.java
@Test
public void getOrderPrice_undiscounttableItem_할인되지_않은_물건() throws Exception{
PriceCalculator calculator = new PriceCalculator();
Item item = new Item("R2D2", "알람시계", 20000);
// ICoupon coupon = new StubCoupon();
ICoupon coupon = new FakeCoupon();
assertEquals("쿠폰 적용 안되는 아이템", 20000, calculator.getOrderPrice(item, coupon));
}
FakeCoupon.java
package mock.testDouble;
import java.util.ArrayList;
import java.util.List;
public class FakeCoupon implements ICoupon {
List<String> categoryList = new ArrayList<>();
public FakeCoupon() {
categoryList.add("부엌칼");
categoryList.add("아동 장난감");
categoryList.add("조리 기구");
}
@Override
public String getName() {
return null;
}
@Override
public boolean isValid() {
return false;
}
@Override
public int getDiscountPercent() {
return 0;
}
@Override
public boolean isAppliable(Item item) {
if(this.categoryList.contains(item.getCategory())) {
return true;
}
return false;
}
@Override
public void doExpire() {
}
}
테스트 스파이
더미 객체부터 페이크 스텁까지 포함하며 다른 동작을 하면서 스파이 기능까지 하는경우 잇습니다.
더미 객체부터 페이크 스텁까지의 기능을 포함하며 그외의 쿠폰 횟수를 확인하는 기능을 테스트합니다.
UserTest.java
@Test
public void getOrderPrice_discounttableItem_할인되었고_몇번호출되었는지_획인() throws Exception{
PriceCalculator calc = new PriceCalculator();
Item item = new Item("LightSavor", "Kitchen knife", 100000);
ICoupon coupon = new SpyCoupon();
assertEquals("쿠폰으로 인해 할인 된 가격", 93000, calc.getOrderPrice(item, coupon));
int methodCallCount = ((SpyCoupon)coupon).getIsAppliableCallCount();
assertEquals("coupon.isAppliable 메소드 호출 횟수", 1, methodCallCount);
}
SpyCoupon.java
package mock.testDouble;
import java.util.ArrayList;
import java.util.List;
public class SpyCoupon implements ICoupon {
List<String> categoryList = new ArrayList<>();
private int isAppliableCallCount;
public SpyCoupon() {
categoryList.add("부엌칼");
categoryList.add("아동 장난감");
categoryList.add("조리 기구");
categoryList.add("Kitchen knife");
}
@Override
public String getName() {
return null;
}
@Override
public boolean isValid() {
return true;
}
@Override
public int getDiscountPercent() {
return 7;
}
@Override
public boolean isAppliable(Item item) {
isAppliableCallCount++;
if(this.categoryList.contains(item.getCategory())) {
return true;
}
return false;
}
@Override
public void doExpire() {
}
public int getIsAppliableCallCount() {
return this.isAppliableCallCount;
}
}
반응형
'개발(합니다) > 방법론' 카테고리의 다른 글
TDD 학습 및 실습 정리6(Unitils) (0) | 2018.12.27 |
---|---|
TDD 학습 및 실습 정리5(DbUnits) (0) | 2018.12.27 |
TDD 학습 및 실습 정리3(메소드작성법과한계) (0) | 2018.12.26 |
TDD 학습 및 실습 정리2(JUnit에 대하여) (0) | 2018.12.22 |
TDD 학습 및 실습 정리1(TDD의 기본사용법) (1) | 2018.12.22 |