본문 바로가기

개발 기록/스프링

[토비의 스프링 정리] #2 테스트의 대해서 리뷰 및 기록

토비의 스프링을 읽고 기록을 하기 위한 개인적인 포스팅이에요 :)


서비스는 계속 변하고 개발이 필요하다. 변화에 대응하는 첫 번째 전략은 앞에서의 학습[0]을 진행했던, 객체지향의 설계 IoC/DI 기술을 활용하는 것이다. 두 번째 전략은 코드를 확신할 수 있게 만들고, 변화에 유연하게 대처할 수 있는 자신감을 주는 테스트 기술이다. 이번 파트에서는 테스트의 대한 학습이 진행되겠다.

테스트의 필요성에 대해서 알아보자.

처음 개발을 배울 때는 기능들을 직접 눌러가면서 테스트를 한다. 이렇게 main 함수를 실행하며 웹을 통해서 직접 테스트를 하는 경우 어떤 불편함과 문제가 있을까? 모든 레이어의 기능들을 다 만들고 나서야 테스트가 가능하다.

테스트 코드를 작성하면 최초 개발을 진행했 던 기능의 동작을 보장할 수 있고, 테스트들을 진행할 때 일일이 반복되는 귀찮은 작업들을 피할 수 있다. 3~4번이야 그냥 테스틀 하는데, 범위가 넓어지고 관리해야 될 코드가 많아지면 테스트로 인해서 엄청난 공수가 들어갈 것이다.

 

단위 테스트: 하나의 관심에 집중해서 효율적으로 테스트할 만한 범위의 대해서 테스트를 수행하는 것.

 

테스트를 안 만드는 것은 위험한 일이지만, 성의 없이 테스트를 만들어 문제 있는 코드가 통과가 되면 더욱 위험한 문제를 만들 것이다. 특히 한 가지 결과만 검증하고 마는 것은 상당히 위험하다. 하루에 두 번은 정확히 맞는 시계가 있다. 바로 죽은 시계다.

발생할 수 있는 상황들에 대해서 테스트가 필요하고, 에러(예외)가 발생하는 경우에도 테스트 검증이 필요하다.

테스트 케이스 작성은 정말 중요하다. 100번 잘하다가 1번 잘못되면 얼마나 화가 나겠는가. 기존의 코드가 잘 동작을 하고 있는지에 대한 확인이 필요하기 때문에 반드시 테스트 코드는 필요하다.

기능을 추가하기 위한 수정 개발을 진행하다가, 기존 로직의 영향범위를 건드릴 수가 있다. 그럼 기존 기능이 제대로 동작이 안 될 수 있기 때문에 반드시 기능별로 테스트 케이스를 작성하자.

// 기존 로직
ResultSet rs = ps.executeQuery();
rs.next()
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));


// 수정 로직
ResultSet rs = ps.executeQuery();
// rs.next() 지웠어야 했지만 삭제를 하지 않아 rs.next() 아래 분기에서 무조건 통과되는 문제발생
// user는 무조건 null이 된다.
User user = null;
if (rs.next()) {
    user = new User();
    user.setId(rs.getString("id"));
    user.setName(rs.getString("name"));
    user.setPassword(rs.getString("password"));
}
if (user == null) throw new EmptyResultDataAccessException(1);

예외 추가를 개발하는 중 rs.next()를 지우지 않아 user가 무조건 null이 되는 문제가 발생. 테스트 코드를 작성하지 않았더라면 추가 기능인 예외 발생 테스트만을 진행했기 때문에, 기존 로직의 기능이 정상적으로 동작하는지 확인이 안 되고 장애가 발생했을 것이다.

 

테스트 코드는 기능의 따라서 한번만 작성을 해놓으면 되기 때문에 정말 요긴하게 써먹고 더 나은 개발을 할 수 있다.

TDD(테스트 주도 개발): 테스트를 먼저 만들고 테스트가 성공하도록 코드를 작성하는 방법. 코드를 작성하면서 바로바로 테스트를 해볼 수 있다.

테스트는 독립적이기 때문에 작성하는데 시간이 오래걸리지 않고, 테스트 덕분에 오류를 더욱 빨리 잡을 수 있어 개발속도는 오히려 빨라진다. 테스 주도 개발을 하게 되면 서버를 올렸다 내렸다 하는 느리고 지루한 과정을 스킵할 수 있다.

 

픽스처(fixture):테스트를 수행하는데 필요한 정보나  오브젝트를 픽스처라 부름

테스트를 하다 보면 필요한 데이터를 미리 선언을 해놓은 데이터 묶음이 있다. 이를 픽스처라 부른다.

 

Mock 테스트가 아닌, 스프링의 애플리케이션 컨텍스트를 사용한다면 스프링 테스트를 적용하자. 각각 테스트마다 AC가 만들어진다면, 테스트 마다 생성이 되면 적지 않은 시간과 리소스 소모가 발생한다.

테스트는 가능한 독립적으로 매번 새로운 오브젝트를 만드는게 원칙이다. 하지만 AC 처럼 생성에 많은 시간과 자원이 들어간다면 테스트 전체가 공요하는 오브젝트를 만들기도 한다.

 

실습 중 ApplicationContext를 싱글톤으로 만들기 위해서 스프링 테스트를 적용 중에 있다.

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {SpringPlaygroundApplication.class})
//@ContextConfiguration("classpath:applicationContext.xml") xml 사용 시 xml 위치 지정
class UserDaoTest {
}

2가지 어노테이션의 대해서 확인을 해보자.

@ExtendWith(SpringExtension.class): Junit 테스트에서 사용할 기능을 확장하기 위해 사용. Spring TestContext Framework를 Junit5에서 통합하여 사용을 하게 된다. Spring을 활용하여 테스트 진행 시 필요.  Mockito 테스트를  진행 시에는 MockitoExtensionc.class 를 사용하자.

@ContextConfiguration(classes = {SpringPlaygroundApplication.class}): 스프링 설정 파일을 읽어 오기 위해서 사용. SpringPlaygroundApplication 위치부터 컴포넌트 스캔을 진행한다. xml을 사용하게 되면 xml에 등록된 빈들을 설정.

https://github.com/tobyilee/tobyspringin5/blob/main/src/main/resources/applicationContext.xml

위의 예제 처럼 xml로 만들기도 하는데, 요즘 추세는 xml로 관리하지 않고 어노테이션으로 관리를 한다.

 

학습 중 실습 코드:

깃헙 - UserDao 테스트 코드 추가

깃헙 - 리팩토링(fixture)

깃헙 - 스프링 테스트(AC 싱글톤)

 

[0] [토비의 스프링 정리] 오브젝트와 의존관계의 대해서 리뷰 및 기록

https://mjlabs.tistory.com/24