테스트주도개발 04

Updated:

테스트 주도개발 4장

4.1 Mock 객체

Mock 객체란?

  • 모듈의 겉모양이 실제 모듈과 비슷하게 보이도록 만든 가짜 객체
  • 실제 객체를 만들기에 비용과 시간이 많이 들거나 의존성이 긴 경우 사용
  • 예를 들어 암호 저장 기능에 대한 케이스를 만들 경우 다음과 같다.
대상 문자열 해시 문자열
potato 8ee2027983915ec78acc45027d874316
  • 사용자 암호 저장 기능에 대한 테스트 케이스
@Test
public void testSavePassword() throws Exception {
    UserRegister register = new UserRegister();
    Cipher cipher = ???? // 암호화할 클래스가 필요
    String userId = "sweet88";
    String password = "potato";
    register.savePassword( userId, cipher.encrypt(password) );
    
    String decryptedPassword = cipher.decrypt(register.getPassword(userId));
    assertEquals(password, decrytedPassword);
}
  • 테스트 케이스에 사용할 수 있도록 동작을 흉내 내어 구성한 MockMD5Cipher 클래스
public class MockMD5Cipher implements Cipher {
    @Override
    public String decrypt (String source) {
    	return "potato";
    }
    @Override
    public String encrypt (String source) {
    	return "8ee2027983915ec78acc45027d874316"; // 강제적으로 예상 결과 값을 넣어줌
	}
}
  • savePassword 메소드 구현을 시작할 수 있도록 준비를 마친 테스트 케이스
@Test
public void testSavePassword() throws Exception {
    UserRegister register = new UserRegister();
    Cipher cipher = new MockMD5Cipher();
    String password = "potato";
    String userId = "sweet88";
    register.savePassword(userId, cipher.encrypt(password));
    
    String decryptedPassword = cipher.decryption(register.getPassword(userId));
    assertEquals(password, decryptedPassword);
}

언제 Mock 객체를 만들 것인가?

  • 의존성이 근본적인 원인
  1. 테스트 작성을 위한 환경 구축이 어려워서 ex) DB설치, 웹서버 설치, 방화벽 등
  2. 테스트 특정 경우나 순간에 의존적이라서 ex) 예외 처리
  3. 테스 시간이 오래 걸려서 ex) 모듈 호출시 수행 결과가 오래 걸릴 경우

4.2 Mock에 대한 기본적인 분류 개념, 테스트 더블

  • 제라드 메스자로스가 만들어낸 용어 테스트 더블
  • 테스트 더블은 대역, 스턴트 맨을 의미하는 스턴트 더블에서 차용
  • 오리지널 객체를 이용해서 테스트가 어려울 경우 이를 대신 해서 진행하도록 만들어주는 객체를 지칭

  • Mock 객체와 의미가 겹치는데 제라드는 Mock 객체를 좀 더 프레임 워크와 밀접한 형태로 설명하면서 테스트 더블의 하위로 분류함
  • 그러나 대부분 그냥 포괄적이고 보편적으로 Mock이라고 씀. 그냥 알아두면 좋다의 느낌

  • 위에 사진을 설명하기 위한 예제로 초기 설정은 다음과 같다.
public interface ICoupon {
    public String getName(); // 쿠폰 이름
    public boolean isValid(); // 쿠폰 유효 여부 확인
    public int getDiscountPercent(); // 할인율
    public boolean isAppliable(Item item); // 해당 아이템에 적용 가능 여부
    public void doExpire(); // 사용할 수 없는 쿠폰으로 만듦
}
  • 테스트 시나리오는 다음과 같다.
유저 ID를 기준으로 신규유저 객체를 생성
 현재 쿠폰 확인
 신규 쿠폰 받기
 유저의 보유 쿠폰 개수 확인
(우선은 보유 쿠폰 수가 증가됐는지 판단하는 수준에서 작성)
  • TDD 방식으로 테스트 구현
public class UserTest {
    @Test
    public void testAddCoupon() throws Exception {
        User user = new User("area88");
        assertEquals("쿠폰 수령 전", 0, user.getTotalCouponCount());
        ICoupon coupon = ?????? // 아! 여길 어떻게 처리해야 하지?
        user.addCoupon(coupon);
        assertEquals("쿠폰 수령 후", 1, user.getTotalCouponCount());
    }
}

1. 더미 객체 (Dummy Object)

  • 오로지 인스턴스화 될 수 있는 수준으로만 ICoupon 인터페이스를 구현한 객체
  • 단순 인스턴스화 된 객체가 필요할 때만 사용. 기능은 필요 없음
  • 따라서 더미 객체 메소드를 호출 할 때는 정상 동작이 보장되지 않음

  • 위의 TDD 예제를 통과 하기 위해 임의 클래스를 만듬
public class DummyCoupon implements ICoupon {
    @Override
    public int getDiscountPercent() {
   	 return 0;
    }
    @Override
    public String getName() {
    	return null;
    }
    @Override
    public boolean isAppliable(Item item) {
    	return false;
    }
    @Override
    public boolean isValid() {
    	return false;
    }
    @Override
    public void doExpire() {
    }
}
  • 이후 다시 작성된 UserTest 클래스의 testAddCoupon 테스트 메소드
@Test
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());
}

2. 테스트 스텁(Test Stub)

  • 더미 객체에서 생성된 메소드를 사용해야 할 경우 사용
  • 즉, 더미 객체가 마치 실제로 동작하는 것처럼 보이게 만들어 놓은 객체
  • 4.1의 예제 MockMD5Cipher가 이에 해당.

  • 예제는 다음과 같다. DummyCoupon 클래스와 같지만 단순 return 값을 넣어줌
public class StubCoupon implements ICoupon {
    @Override
    public int getDiscountPercent() {
    	return 7;
    }
    @Override
    public String getName() {
    	return "VIP 고객 한가위 감사쿠폰";
    }
    
    @Override
    public boolean isAppliable(Item item) { return true; }
    
    @Override
    public boolean isValid() { return true; }
    
    @Override
    public void doExpire() { }
}
  • 위와 같이 만든 코드를 테스트 더블 or 테스트 스텁이라 부름
  • “VIP 고객 한가위 감사쿠폰”을 테스트 픽처스로 사용할 수 있게 됨.

  • 그러나 위는 한 가지 경우에 대해서만 통과. 여러 개의 경우에 대해선 어떻게 할 것인가?

3. 페이크 객체(Fake Object)

  • 스텁은 하나의 인스턴스를 대표할 때 쓰인다
  • 페이크는 여러 개의 인스턴스를 대표할 때 쓰인다. (더 복잡한 구현이 들어가 있는 객체)

  • 아래는 페이크 객체의 방식을 그림으로 표현

  • 너무 구현을 하다보면 페이크 객체 자체를 테스트 해야할 정도로 복잡해짐
  • 따라서 페이크 객체를 만들 때는, 적당히 구현. 아니면 Mock 프레임워크를 사용.

4. 테스트 스파이(Test Spy)

  • 특정 메소드의 정상호출 여부 확인을 목적으로 구현
  • 더미부터 시작해서 페이크 객체이르기 까지 테스트 더블로 구현된 객체 전 범위의 기능을 추가 할 수 있음
  • 원하는 동작을 하면서 다른 동작을 함
  • isAppliable 메소드가 1번 호출되어서 계산에 사용되어야 한다는 가정으로 예시는 다음과 같다.
@Test
public void testGetOrderPrice_discountableItem() throws Exception {
    PriceCalculator calculator = new PriceCalculator();
    Item item = new Item("LightSavor","부엌칼",100000);
    ICoupon coupon = new FakeCoupon();
    assertEquals("쿠폰 할인된 가격", 93000, calculator.getOrderPrice(item,coupon));
}
  • 여기서 isAppliable 메소드 호출 횟수 확인해보고 싶다. 그래서 아래와 같이 SpyCoupon을 구현
public class SpyCoupon implements ICoupon {
    List<String> categoryList = new ArrayList();
    private int isAppliableCallCount;
    
    @Override
    public boolean isAppliable(Item item) {
        isAppliableCallCount++; // 호출되면 증가
        
        if(this.categoryList.contains(item.getCategory())) {
        	return true;
        }
        return false;
    }
    public int getIsAppliableCallCount(){ // 몇 번 호출됐나?
    	return this.isAppliableCallCount;
    }
}
  • getIsAppliableCallCount() 메소드의 경우 스펙에 없어도 만들어도 되는 이유는 어차피 가짜 객체이고 테스트를 위해 만든 것이므로 크게 상관 없다.
@Test
public void testGetOrderPrice_discountableItem() throws Exception {
    PriceCalculator calculator = new PriceCalculator();
    Item item = new Item("LightSavor","Kitchen knife",100000);
    ICoupon coupon = new SpyCoupon();
    assertEquals("쿠폰할인된 가격", 93000, calculator.getOrderPrice(item, coupon));
    
    int methodCallCount = ((SpyCoupon)coupon).getIsAppliableCallCount();
    assertEquals("coupon.isAppliable 메소드 호출 횟수", 1, methodCallCount);
}
  • 이렇게 스냅샷 같은 감시가 필요한 경우에 유용하게 사용.
  • 보통은 잘 안씀. Mock 프레임워크가 강력, 편하고 스파이 기능도 제공함.
** 상태기반 테스트 vs 행위 기반 테스트 **

  • 상태 기반 테스트란 테스트 대상 클래스의 메소드를 호출하고, 그 결과값과 예상값을 비교하는 방식
  • 예를 들어서 setName(“신선호”)를 호출하면 객체의 속성이 변경된다. 이때 객체의 속성이 원하는 값으로 변경되었는지 getName()으로 확인해보는 방식이다.

  • 댄 노스가 창시자

  • 행위 기반 테스트는 올바른 로직 수행에 대한 판단의 근거로 특정한 동작의 수행 여부를 이용한다.
  • 보통 메소드의 리턴값이 없거나 리턴값을 확인하는 것만으로는 예상 동작 했음을 보증하기 어려울때 사용
  • 위의 그림처럼 methodA에 A가 입력되면 methodB는 호출되지 않아야 정상이다. 그리고 B가 입력되면 methodB가 호출돼야 정상이다. 하지만 테스트 대상이 되는 methodA만 놓고 봤을 때는 입력 값에 따른 차이를 methodA만으로는 알아낼 수가 없다. 즉 methodB의 호출 여부를 조사하지 않으면, methodA의 정상 동작 여부를 판단할 수 없다. 만일 methodA가 정상 동작했을 경우 methodB가 반드시 호출되는 구성이라면, 반대로 methodB의 호출 여부로 methodA의 정상 여 부를 판단할 수 있다고 보는 것이다. 따라서 이럴 때는 methodB의 호출 여부를 확인하는 것이 테스트 시나리오의 종료조건이 된다. 하지만 전통적인 테스트 케이스 작성 방식, 즉 상태 기반 테스트에선, 사실상 이런 상황에 대한 테스트 케이스를 작성하기가 매우 어렵거나 불편했다. 테 스트 대상인 A가 상태를 갖고 있지 않기 때문이다.
    • 이건 긁어와야 했다…. 이렇게 봐야 이해
  • 이럴 때 사용하는 것이 스파이 객체 or Mock 객체를 따로 만들어서 테스트 케이스 작성
  • 따라서 예상하는 행위들을 미리 시나리오로 만들어놓고 해당 시나리오대로 동작이 발생했는지 여부를 확인하는 것이 핵심
** 상태 기반으로 가능하면 굳이 행위 기반으로 하지 않는 것이 좋다 **

5. Mock 객체

  • 일반적인 테스트 더블은 상태(state)를 기반으로 테스트 케이스를 작성한다.

  • Mock 객체는 행위(behavior)를 기반으로 테스트 케이스를 작성한다.

위의 쿠폰 예제에 ‘유저가 쿠폰을 받을 때, 쿠폰이 유효한 상태일 경우에만 다운 받을 수 있다’는 규칙을 추가

  • 이 규칙으로 user.addCoupon(coupon) 메소드가 정상 호출 되었다면
  • 반드시 coupon.isValid()도 함께 호출되어, 쿠폰의 유효 여부를 확인하는 과정이 필요.
  • 그리고 나서 해당 쿠폰을 취득할수 있게 해야함.
  • 다음은 일반적인 상태 기반 테스트 케이스
ICoupon coupon = new FakeCoupon();
user.addCoupon(coupon);
assertEquals(1, user.getTotalCouponCount());
  • 상태(1이라는 값)가 정상적으로 보이는지 확인한다.
  • 문제는 추가된 규칙이 addCoupon에 들어있는지 그리고 정상적으로 호출되어 사용되었는지 확인할 방법이 없음.
  • 아래는 jMock 라이브러리를 사용한 예제. 오래된 문법이므로 뉘앙스를 느껴라.
ICoupon coupon = mock(ICoupon.class);
coupon.expects(once()).method("isValid") // ➊
.withAnyArguments() // ➋
.will(returnValue(true)); // ➌
user.addCoupon(coupon);
assertEquals(1, user.getTotalCouponCount());
  • ➊ Mock으로 만들어진 coupon 객체의 isValid 메소드가 한 번 호출될 것을 예상함
  • ➋ isValid에서 사용할 인자는 무엇이 됐든 상관 안 함
  • ➌ 호출 시에 리턴값은 true를 돌려주게 될 것임

Mock 객체 용어가 주는 혼란스러움 정리

  • Mock 객체라는 단어의 의미는 제라드의 분류법 보단 ‘가상 임시 구현체’의 의미로 사용되는 경우가 더 많음.
  • 보통 테스트 더블과 Mock 객체가 동등한 의미로 사용
  • 그래서 이 책에서는 같은 의미로 사용. 단지 이 장에서는 제라드 분류법으로 설명한 것 뿐
  • Mocking의 의미도 테스트 더블 객체를 만드는 것을 의미로 생각하자.
  • Mock 프레임워크로 클래스 하나 인스턴스화하면 더미 객체와 동일
** 테스트 더블 익명 클래스로 만들기 **
  • 매번 새로운 더미 객체를 만들기 위해서 클래스를 만들어야 한다.
  • 결국 클래스 개수가 늘어남. 관리하는 소스가 늘어난다는 이야기
  • 이때 익명 클래스를 만들면 좋다.
@Test
public void testAddCoupon() throws Exception {
    User user = new User("area88");
    assertEquals("최초 쿠폰 수", 0, user.getTotalCouponCount());
    ICoupon coupon = new ICoupon(){
        @Override
        public void doExpire() {
        }
        
        @Override
        public int getDiscountPercent() {
        	return 0;
        }
        
        @Override
        public String getName() {
        	return null;
        }
        
        @Override
        public boolean isAppliable(Item item) {
        	return false;
        }
        
        @Override
        public boolean isValid() {
        	return false;
        }
    };
    user.addCoupon( coupon );
    assertEquals("쿠폰 수령 후", 1, user.getTotalCouponCount());
}
  • 이렇게 만들어 놓고 필요한 내용 넣으면 스텁도 되고 페이크도 되고 스파이도 된다.
  • 더 필요하다면 coupon을 필드 변수로 뽑아 내고 익명 클래스로 만든다. 그리고 인터페이스를 구현하고 있는 부분을 setUp 메소드로 이동시키면 더 깰끔.

4.3 프레임워크

  • Mock 객체를 직접 작성해서 명시적인 클래스 만들지 않아도 됨
  • Mock 객체에 대해서 행위까지도 테스트 케이스에 포함 가능
  • jMock, EasyMock, Mockito등 여러가지가 있다.
  • 책에는 jMock, EasyMock의 간단한 사용법이 있으나 나는 Mockito만 보겠다.
고전주의 TDD 개발자 vs Mock 주도 개발자
  • Mock은 행위 기반 테스트 같은 곤란한 상황에 대한 테스트도 지원해서 좋다.

  • 그러나 고전 상태 기반 테스트을 지지하는 사람들이 싫어(?)했다?ㅋㅋㅋ
  • 저자는 상태기반 테스트가 좋다고 생각
  • 근데 역시나 상황에 따라 사용이 다르다는게 중립 기어

Mockito

  • 최근 떠오르는 Mock 프레임워크
  • 수제빵 파베르가 개발
Mock 프레임워크는 어때야 하는가?
  • 단순해야함.
  • DSL로 만들지 말자. (특정 문제 영역을 해결하기 위해 만들어진 언어나 스펙. 결국 테스트를 위해 또 다른 언어나 스펙을 만들면 복잡해진다라는 뜻)
  • 문자열을 메소드 대신 사용하지 말자
  • 익명 inner 클래스 사용하지 말자
  • 리펙토링이 어려워서 안된다.
Mockito 차별점은?
  • 테스트 그 자체에 집중
  • 테스트 스텁을 만드는 것과 검증을 분리시켰다.
  • Mock 만드는 방법을 단일화했다.
  • 테스트 스텁을 만들기 쉽다.
  • API가 간단하다
  • 프레임워크가 지원해주지 않으면 안 되는 코드를 최대한 배제
  • 실패 시에 발생하는 에러추적이 깔끔하다.
1. Mock 객체 만들기
// Mockito.mock(타깃 인터페이스);

import static org.mockito.Mockito.*;
List mockedList = mock(List.class);
System.out.println(mockedList.size()); // 결과 값 0
  • 리턴 타입(return type)의 기본값으로 동작한다. int는 0, boolean은 false, 클래스 타 입들은 null을 돌려줌
2. 예상값 지정
  • 예상 값을 지정하는 것이 아니라 필요시에 스텁만 만들고 나중에 호출 여부를 확인하는 방식
  • 기존에는 스텁 → 예상 → 수행 → 검증

  • Mockito는 스텁 → 수행 → 검증 이다.
  • 그래서 Mockito는 예상값 지정 부분이 없다.
3. 테스트에 사용할 스텁 만들기
  • 스텁은 필요할 때만 만드는 것이 원칙
  • 메소드 호출 여부를 검증만 할 때는 사용하지 않는다.

when(Mock 객체의 메소드).thenReturn(리턴값); when(Mock 객체의 메소드).thenThrow(예외);

List mockedList = mock(List.class); 
// Mock 객체를 사용한다.
mockedList.add("item");
mockedList.clear(); 

// 검증
verify(mockedList).add("item");
verify(mockedList).clear();
  • 위와 같이 Mock 객체를 생성만 하고, 나중에 호출을 검증하는 식으로 테스트 코드가 작성
  • 만일 테스트를 위해 Mock 객체의 특정 메소드를 사용해야 할 필요가 생기면 그때 만든다.
// Stub 만들기
when(mockedList.get(0)).thenReturn("item");
when(mockedList.size()).thenReturn(1);
when(mockedList.get(1)).thenThrow(new RuntimeException());

System.out.println(mockedList.get(0));
System.out.println(mockedList.size());
System.out.println(mockedList.get(2));
System.out.println(mockedList.get(1));

//-----실행 결과-----
Item
1
null
Java.lang.RuntimeException at SimpleMockTest.testMockList(SimpleMockTest.Java:25)
4. 검증

verify(Mock 객체).Mock 객체의 메소드;

verify(Mock 객체, 호출횟수지정 메소드).Mock 객체의 메소드;

호출횟수 지정 메소드 설명
times(n) n번 호출됐는지 확인. n=0은 times를 지정하지 않았을 때와 동일하다.
never 호출되지 않았어야 함
atLeastOnce 최소 한 번은 호출됐어야 함
atLeast(n) 적어도 n번은 호출됐어야 함
atMost(n) 최대 n번 이상 호출되면 안 됨
  • 예제
verify(mockedList).add("item");
verify(mockedList, times(1)).add("item");
verify(mockedList, times(2)).add(box);
verify(mockedList, never()).add(car);
verify(mockedList, atLeastOnce()).removeAll();
verify(mockedList, atLeast(2)).size();
verify(mockedList, atMost(5)).add(box); 

import static org.mockito.Mockito.*;
public class SimpleMockTest {
    @Test
    public void testMockList() throws Exception {
   		List mockedList = mock(List.class);
    	verify(mockedList).size();
    }
}
//-----실행 결과-----
Wanted but not invoked:
list.size();-> at SimpleMockTest.testMockList(SimpleMockTest.Java:19)
    Actually, there were zero interactions with this mock.
a. 검증에서 인자 값으로 사용되는 Argument Matcher

예시: verify(mockedList, times(5)).add( any() );

해석: 어떤 객체가 add 메소드의 인자로 와도 무방하고 대신 5번 호출됐다면 성공이란 뜻

메소드 설명
any 타입 anyInteger( ), anyBoolean 등 Java 타입에 해당하는 any 시리즈가 있다.
이런 시리즈들은 null이거나 해당 타입이면 만족한다
anyCollection
anyCollectionOf
List, Map, Set 등 Collection 객체이면 무방.
anyCollectionOf는 anyCollection과 동일하다.
argThat(HamcrestMatcher) Mockito도 Hamcrest Matcher를 사용하고 있다.
Hamcrest에서 사용하던 Matcher를 그대로 이 부분에서 사용할 수 있다
eq Argument Matcher가 한번 사용된 부분에선 Java의 타입을 그대로는 더 이상 쓸 수가 없다.
verify(mock).add(anyString(), “item”);
즉, 위와 같이는 쓸 수 없다. 이럴 때 아래와 같이 eq를 이용한다. verify(mock).put(anyString(9), eq(“item”));
anyVararg 여러 개의 인자를 지칭할 때 사용한다.
예시)
// verification
mock.foo(1, 2);
mock.foo(1, 2, 3, 4);
verify(mock, times(2)).foo(anyVararg( ));

// Stubbing:
when(mock.foo(anyVararg( )).thenReturn(100);
matches(String regex) 정규식 문자열로 인자(argument) 대상을 지칭한다.
startswith(String)
endWith(String
특정 문자열로 시작하거나 끝나면 OK
anyList
anyCollection
anySet
anyMap의 좀 더 디테일한 버전이다. 해당 타입이기만 하면 OK
isA(Class) 해당 클래스 타입이기만 하면 된다
startswith(String)
endWith(String)
특정 문자열로 시작하거나 끝나면 OK
isNull Null이면 OK
isNotNull null만 아니면 OK
b. 순서검증
  • Stub으로 만들어진 Mock 객체 메소드의 호출 순서까지 검증하고 싶다면 InOrder 클래스를 이용한다.
List firstMock = mock(List.class);
List secondMock = mock(List.class);

firstMock.add("item1");
secondMock.add("item2");

InOrder inOrder = inOrder(firstMock, secondMock);
inOrder.verify(firstMock).add("item1");
inOrder.verify(secondMock).add("item2");
  • add 메소드의 호출이 item1 → item2 순으로 이뤄지지 않았다면 실패로 간주

Mockito의 특징적인 기능

1. void 메소드를 Stub으로 만들기

doThrow(예외).when(Mock_객체).voidMethod();

  • void 메소드는 리턴이 없어서 stub을 만들 일이 없다.
  • 단, 예외 발생하는 경우 Stub으로 구현해야 함.
  • 이때 doThrow를 사용
doThrow(new RuntimeException()).when(mockList).clear();
2. 콜백으로 Stub 만들기 : thenAnswer
  • Mock은 보통 하드코딩된 값만 돌려줌
  • 그러나 특정 Mock 메소드에 대해 실제 로직을 구현하고자 할때 콜백 기법을 사용
  • 그러나 TDD 자체에서 권장하지 않음
when(rs.getInt("no")).thenAnswer( new Answer<Integer>(){
    public Integer answer(InvocationOnMock invocation) throws Throwable {
        if (currentRecord = = null)
        	throw new SQLException("access fields is empty");
        return ((Integer)currentRecord[0]).intValue();
    }
});
3. 실체 객체를 Stub으로 만들기 : SPY
  • 실 객체도 Mock으로 만들 수 있다.
  • 강력한 나머지 가끔 문제가 될 수 도 있다.
  • 서드파티(3rd Party) 제품, 고칠 수 없는 라이브러리만 남아 있는 코드에서만 사용
  • 모키토 저자는 왠만하면 spy 기능을 쓰는건 잘못됐다고 한다.

  • 참고로 final로 선언된 메소드는 stub으로 만들 수 없다.
ArrayList<String> realList = new ArrayList<String>();
realList.add("Hello");
System.out.println(realList.get(0));

List mockedList = spy( realList );
when(mockedList.get(0)).thenReturn("item");
System.out.println(mockedList.get(0));
//-----실행 결과-----
Hello
item
4. 똑똑한 NULL 처리 : SMART NULLS
  • 스텁으로 만들지 않은 기본형 외의 값을 리턴하는 메소드는 null 값이 찍힌다.
  • 이렇게 만들어진건 null 에러를 유발
  • 필요에 따라 유용하게 기본값으로 찍히게 만드는 것이 SMART NULLS라는 방식
List mockedList = mock(List.class);
List smartMockedList = mock(List.class, RETURNS_SMART_NULLS);
System.out.println(mockedList.toArray());
System.out.println(smartMockedList.toArray());
//-----실행 결과-----
null
[LJava.lang.Object;@3eca90

SMART NULLS 규칙

  1. 기본형 래퍼 클래스는 해당 기본형 값으로 바꾼다.
  2. String은 ““로 바꾼다.
  3. 배열은 크기 0인 기본 배열 객체로 만들어준다.
  4. Collection 계열은 빈 Collection 객체로 만든다.
5. 행위 주도 개발(BDD) 스타일 지원
  • Mockito는 //given //when //then 식의 행위 주도 개발(Behavior-Driven Development, BDD) 스타일로 테스트 케이스 작성할 수 있게 지원
  • BDD 스타 일을 사용하려면 Mockito 클래스 대신 BDDMockito를 static import 한다
import static org.mockito.BDDMockito.*;

Seller seller = mock(Seller.class);
Shop shop = new Shop(seller);

public void shouldBuyBread() throws Exception {
    // given
    given(seller.askForBread()).willReturn(new Bread());
    // when
    Goods goods = shop.buyBread();
    // then
    assertThat(goods, containBread());
} 

Tags:

Categories:

Updated: