본문 바로가기
Spring

Spring 단위테스트 - 1

by 스르나 2021. 2. 28.

Spring Boot 단위테스트

 

단위테스트란 앱을 모듈단위로 각각 테스트하는것이다.

 

스프링에서 단위테스트를 어떤식으로 진행하는지에 대해 적어본다.

 

우선  단위테스트를 위해 한가지 예시코드를 만들었다.

 

public interface RepositoryInterface {
    boolean save(UserDto userDto);
}
@Repository
public class MyRepository implements RepositoryInterface{
    private Hashtable<String, UserDto> userTable=new Hashtable<>();

    @Override
    public boolean save(UserDto userDto){
        userTable.put(userDto.getName(),userDto);

        return userTable.contains(userDto);
    }
}

우선 위의 코드는 데이터베이스에 저장하는것을 모의로 만든것이다. 굳이 인터페이스를 만든이유는 의존성을 분리하기 위함이다.

 

@Service
public class MyService {

    private RepositoryInterface repositoryInterface;

    public MyService(RepositoryInterface repositoryInterface){
        this.repositoryInterface=repositoryInterface;
    }

    public boolean saveUser(UserDto userDto){
        return repositoryInterface.save(userDto);
    }
}

위의 코드가 이번에 테스트할 service클래스이다. 우리는 이 service를 단위테스트할 것이다.

 

그럼 테스트에 앞서 단위테스트의 중요한 법칙인 FIRST법칙을 우선 알아보자.

 

1.Fast

단위테스트는 빠르게 진행되어야 한다. 다시말하면 테스트 코드를 실행시키고 결과가 나오기까지 시간이 짧아야한다는 뜻이다. 그이유는 단위테스트이기 때문에 테스트할 내용이 많지 않기 때문이다.

 

2.Independent

단위테스트는 다른모튤, 메소드등에 의존해서는 안된다. 단위테스트는 테스트하려는 모듈만을 테스트해야 하는데 해당 모튤이 다른 모듈에 의존하고 있다면 여기에 대해 대책이 마련되어야한다. -> spring은 mockito를 통해 이부분을 해결한다.

 

3.Repeatable

단위테스트는 여러번 반복이 가능해야한다. 단위테스트를 여러차례 반복할때 아무런 문제가 없어야하는데 만약 실제 DB와 연관된 코드라면 여기에 대해 대처가 가능해야한다. 만약 테스트를 할때 실제 사용중인 DB에 영향이 가면 반복이 불가능하니 여기에 대한 대책이 마련되어야 한다. -> 스프링은 @Tranjectional을 통해 이부분을 해결할 수 있다.

 

4.Self-validating
단위테스트는 자체검증이 가능해야한다. 개발자가 결과를 일일이 확인할 필요가 없어야한다는 뜻이다. 스프링은 assert로 해결가능하다.

 

5.Timely
단위테스트를 통과하는 실제 제품코드가 만들어지기 전에 단위테스트 코드가 먼저 만들어져야한다. 이부분이 특이한데
이말은 우리가 실제  사용될 코드를 작성하기 전에 먼저 단위테스트를 하라는 뜻입니다. 당연히 실패하는데(구현이 안됐는데 성공할 수가 없지...) 이 실패하는 코드(테스트 코드)를 성공하게 제품코드를 만드는것입니다. (TDD가 이런식으로 진행되는것이다. 선실패 후구현)

 

우리는 FIRST의 마지막은 Timely만 제외하고 이번에 모두 지켜가며 테스트하는 방법을 간단하게 볼것입니다.

 

@ExtendWith(MockitoExtension.class)
public class ServiceTest {

    @InjectMocks
    MyService myService;

    @Mock
    MyRepository repository;

    @Test
    void saveTest(){
        UserDto userDto=new UserDto("test","hello");
        given(repository.save(userDto)).willReturn(true);

        assertThat(myService.saveUser(userDto)).isEqualTo(true);
    }
}

위 코드가 service를 테스트하는 코드인데 기존 MyService가 내용이 없다보니 테스트코드도 별 내용이 없다. 하지만 위 테스트 코드도 FIRST규칙을 모두 지키고 있다.

 

우선 테스트 코드를 천천히 설명해보자.

 

@ExtendWith(MockitoExtension.class)

위 어노테이션은 스프링 단위테스트의 확장을 위해 사용된다. JUnit5버전 부터 사용가능하다.

 

@InjectMocks
MyService myService;

@Mock
MyRepository repository;

이 코드가 단위테스트의 핵심적인 부분이다. 우선 이 코드를 알기 위해서는 Mock이 무엇인지 알아야한다. Mock이란 단위테스트의 대상 모듈이 다른 모듈을 사용할때 사용하는 모듈을 대체하는 것이다(가짜 객체이다). 위 코드에서 service는 repository를 사용한다. 그런데 service를 테스트하기 위해서 repository를 생성하는 것은 FIRST의 independent에 어긋난다. 그래서 repository는 Mock로 대체한다.

 

우선 @Mock는 대체할 모듈위에 붙이고 @InjectMocks는 테스트할 모듈에 붙인다. 그러면 @Mock라 붙은 모둘이 @InjectMocks가 붙은 모듈에 주입된다.

 

given(repository.save(userDto)).willReturn(true);

다음으로는 위코드인데 위크드는 대체할 모듈의 어떤 함수를 호출때 어떤 값을 return하는지 정하는 것이다. 테스트를 위해 우리가 대체모듈의 실행 결과를 컨트롤할 수 있다. 추가적으로 저렇게 하면 실제 save메소드는 작동하지 않고 그냥 바로 true를 돌려준다. 한마디로 save 메소드안에 아무리 복잡한 코드가 있어도 실행하지 않고 그냥 결과만을 return한다.

 

assertThat(myService.saveUser(userDto)).isEqualTo(true);

위코드는 service의 saveUser 메소드를 테스트한것이다.

'Spring' 카테고리의 다른 글

Spring Lifecycle Callback  (0) 2021.03.20
Spring 단위테스트 -2 Data  (0) 2021.02.28
InMemory-H2  (0) 2021.02.23
스프링 HttpMessageConverters  (0) 2021.01.27
Autowired  (0) 2020.05.11