본문 바로가기

테스트

[단위 테스트] 테스트 간의 높은 결합도는 안티 패턴이다.

단위테스트 책의 c# 예시를 자바 코드랑 비슷하게 작성하였다.

자바 문법에 맞지 않거나, Junit5의 문법과 일치하지 않더라도 이해 바랍니다.


public class CustomerTests {
	
    private Store store;
    private Customer sut;
    
    // 클래스 내 각 테스트 이전에 호출
    // Junit5에서는 @BeforeAll 과 동일
    public CustomerTests() {
    	store = new Store();
        store.addInventory(Product.Shampoo, 10);
        sut = new Customer();
    }
    
    void 재고가_충분할때_구매_성공() {
    	boolean success = sut.purchase(store, Product.Shampoo, 5);
        Assert.True(success);
      	Assert.Equal(5, store.getInventory(Product.Shampoo);
    }
    
    void 재고가_충분하지_않을때_구매_실패() {
    	boolean success = sut.purchase(store, Product.Shampoo, 15
        Assert.false(success);
      	Assert.Equal(10, store.getInventory(Product.Shampoo);
    }
    
}

 

위의 두개의 테스트는 공통된 구성 로직이 있다.

실제로 준비 구절이 동일하므로 CustomerTests의 생성자로 완전히 추출할 수 있었고 예제와 같이 작성했다.

테스트에는 더 이상 준비 구절이 있지 않다.

 

이 방법으로는 테스트 코드의 양을 크게 줄일 수 있으며, 테스트에서 테스트 픽스처 구성을 전부 또는 대부분 제거할 수 있다.

 

그러나 이 기법은 두 가지 중요한 단점이 있다.

  • 테스트 간 결합도가 높아진다.
  • 테스트 가독성이 떨어진다.

 

테스트 간의 높은 결합도는 안티 패턴이다.

위의 두개의 테스트는 모든 테스트가 서로 결합돼 있다.

즉, 테스트의 준비 로직을 수정하면 클래스의 모든 테스트에 영향을 미친다.

public CustomerTests() {
	store.addInventory(Product.Shampoo, 10);
}

// 위 코드를 아래 코드처럼 변경한다면?

public CustomerTests() {
	store.addInventory(Product.Shampoo, 15);
}

 

위 코드를 아래 코드처럼 변경한다면,

상점의 초기 상태에 대한 가정을 무효화하므로 쓸데없이 테스트가 실패하게 된다.

 

이는 중요한 지침을 위반한다. 테스트를 수정해도 다른 테스트에 영향을 주어서는 안된다.

완전히 같지는 않지만, 테스트는 서로 격리돼 실행해야 한다는 것과 비슷하다.

 

테스트를 수정해도 다른 테스트에 영향을 주어서는 안된다

 

위 지침을 따르려면 테스트 클래스에 공유 상태를 두지 말아야 한다.

 

다음 두 가지의 비공개 필드를 이러한 공유 상태의 예로 들 수 있다.

private Store store;
private Customer sut;

 

 

테스트 가독성을 떨어뜨리는 생성자 사용

준비 코드를 생성자로 추출할 때의 또 다른 단점은 테스트 가독성을 떨어뜨리는 것이다.

테스트만 보고는 더 이상 전체 그림을 볼 수 없다.

테스트 메서드가 무엇을 하는지 이해하려면 클래스의 다른 부분도 봐야 한다.

 

준비로직이 별로 없더라도 테스트 메서드로 바로 옮기는 것이 좋다.

그렇지 않으면 단순히 인스턴스를 만드는 것일까? 아니면 다른 무언가를 환경 설정을 하는 것일까? 라는 불확실성을 독립적인 테스트는 두지 않는다.

 

 


참고

단위 테스트 블라디미르 코리코프 지음