대신 테스트한다! '테스트 더블'

60jong·2023년 1월 12일
0

Test

목록 보기
1/2
post-thumbnail

노가다 테스트...

개발을 진행하며 테스트의 중요성을 요즘 정말 많이 느끼고 있다. 현재는 유닛테스트는 무조건 진행하고, 가능하다면 TDD로 진행하려고 한다.

하지만, 처음으로 진행했던 프로젝트에서는 단 하나의 테스트도 작성하지 않고 개발했었다... 그 때는 일일이 눈으로 확인 + '이렇게 되겠지.' 하는 생각으로 개발했었다. 이런 개발은 변화에 취약하다고 생각이 든다.

테스트 없는 개발이 안 좋은 이유

  • 테스트를 생각하면, 기능을 최소한으로 나누거나 메서드는 나누게 된다.
    • 그러지 않았기에 하나의 메서드는 여러 기능을 가지고 있고, 가독성도 좋지 않다.
  • 개발에 너무 많은 시간이 걸린다.
    • 실제 request를 받고 response를 보내는 기능들에 대해서는 일일이 서버를 빌드 + 배포 + 확인을 통해서 진행했었다. 엄청난... 시간 낭비이다.

이외에도 정말 많은 이유를 찾을 수 있고,

테스트에 관심을 가지지 않을 수가 없다!

💡테스트 더블

테스트 더블이란, 테스트하기 어려운 상황에서 이를 대신해서 테스트를 진행해주는 객체이다. 영화 촬영에서 배우를 대신해서 위험한 장면을 대신 연기해주는 스턴트 더블(Stunt Double)에서 유래한 단어이다.

가장 흔한 예시로, DB와 connection을 통해 기능을 하는 메서드가 있다고 할 때, 그 메서드는 DB connection에 의존성이 존재한다. 이 의존성으로 DB와 connection에 문제가 생기게 된다면 메서드는 기능을 하지 못할 것이다. 이러한 의존성에서 벗어나 테스트를 진행하기 위해 테스트 더블이 존재한다.

테스트 더블 종류

테스트에서 수행하는 역할에 따라 테스트 더블도 여러 종류로 나뉘게 된다. Dummy, Fake, Stub, Spy, Mock 객체가 존재한다.

Dummy

Dummy 객체란, 아무 동작을 하지 않는 객체이다. 주로 파라미터로 전달되는 객체에 대한 테스트 더블이다. 인터페이스를 상속받아 파라미터로 전달하는 방식으로 사용된다.

public interface MyInterface {
	void print();
}

public class MyDummyClass implements MyInterface {
	@Override
    public void print() {
    	// do nothing;
    }
}

// 테스트 메서드
@Test
public void doSomethingTest() {
	// Dummy 객체 생성
    MyInterface myInterface = new MyDummyClass();
    
    // 파라미터로 전송
    doSomething(otherParams, myInterface);
}

Stub

Stub 객체는, Dummy 객체와는 다르게 어느정도 작동을 한다. 정해진 값을 반환하도록 구현을 한 객체이다.

public interface MyInterface {
	int getResult(int param);
}

public class MyStubClass implements MyInterface {
	@Override
    public int getResult(int param) {
    	// 정해진 값 return   <--> Dummy와의 차이점
        return 1;
    }
}

// 테스트 메서드
@Test
public void doSomethingTest() {
	// Dummy 객체 생성
    MyInterface myInterface = new MyStubClass();
    
    // 파라미터로 전송
    doSomething(myInterface.getResult()); // doSomething(1); 의 역할을 하게 됨.
}

Fake


Fake 객체는 Dummy와는 다르게 실제 작동하는 객체인데, 그림처럼 예측할 수 없는 의존성 객체에 대해(DataSource 등) 예측할 수 있도록 구현한 테스트 더블이다.

login() 메서드는 account 정보가 담긴 DB로의 접근에 의존하게 되어 테스트를 하기에 적합하지 않다. 따라서 DB대신 DB의 역할을 할 수 있는 HashMap을 선언해 작동을 하게 함으로써 login() 메서드는 테스트하게 할 수 있다.

public class FakeUserRepository implements UserRepository {
    private static Long sequence = 0L;
    
    // DB 대신 HashMap -> Fake
    private Map<Long, User> fakeUserRepository = new HashMap<>();
    
    @Override
    public Long save(User user) {
        fakeUserRepository.put(sequence++, user);
        return sequence;
    }
}

Spy

Spy 객체는 Stub 객체 + 약간의 정보를 기록하는 객체이다. 주로 메소드 호출이 제대로 이루어졌는지 count를 세거나 하는 등의 정보를 기록하게 된다.


Mock

Mock 객체는 행위에 대한 기대를 명세하고, 그 내용을 토대로 동작하는 테스트 더블 객체이다. 설정에 따라 Dummy, Stub, Fake, Spy처럼 동작할 수 있는 강력한 테스트 더블이라고 한다.

Java에서는 Mockito라는 프레임워크를 통해 Mock 객체를 제공한다.

public class UserServiceTest {
    @Mock
    private UserRepository userRepository;

    @Test
    public void userSaveTest() {
        User user = new User("60jong");
        when(userRepository.save(user)).thenReturn(1L);
    }
}

Mockito 프레임워크를 사용해 UserRepository 객체를 Mock 객체로 등록했고, 여러 설정을 통해 테스트를 진행할 수 있다.


정리

주로 Mockito 프레임워크를 통해 Mock 객체를 활용한 테스트를 많이 활용하고자 한다. 특히, Data Repository를 이용하는 메서드들의 경우, Mock을 Stub처럼 활용하거나, 때에 따라 Fake처럼 활용한다면, 더 용이한 테스트가 가능할 것 같다!

출처

https://blog.pragmatists.com/test-doubles-fakes-mocks-and-stubs-1a7491dfa3da
https://hudi.blog/test-double/
https://tecoble.techcourse.co.kr/post/2020-09-19-what-is-test-double/

profile
울릉도에 별장 짓고 싶다

0개의 댓글