본문 바로가기

Spring

Toby's Spring - Chap 2 - Test

※ 해당 내용은 '토비의 스프링 3.1'을 정리한 내용입니다.  

 

스프링이 개발자에게 제공하는 가장 중요한 가치 2개를 뽑자면 바로 객체지향과 테스트일 것입니다.
그 중 테스트란 개발자가 예상하고 의도했던 대로 코드가 정확히 동작하는지를 확인하기 위한 작업입니다.

웹을 개발하는 개발자들 중 대부분은 웹 화면을 통해서 테스트를 진행 할 것입니다.

DB 콜을 위한 DAO 클래스를 확인하기 위해서 DAO 클래스를 개발하고 서비스 로직, MVC 프레젠테이션 계층까지 개발후에 로컬서버에서 웹을 띄우고 input값을 입력 후 버튼을 눌러 잘 동작하는지 확인합니다.

이 방법은 가장 간단하지만 클래스 하나를 확인하기 위해 작성해야 할 부가적인 코드가 너무 많습니다.

그리고 DAO클래스를 제외하고도 에러가 날 부분이 많이 존재하기 때문에 효율적인 방법이 될 수 없습니다.

 

테스트를 개선하기 위한 방법

1. 작은 단위의 테스트 

테스트 범위를 최소화 시켜 원하는 부분에 대해서만 테스트를 진행합니다.   
하나의 관심에 집중해서 효율적으로 테스트할 만한 범위의 단위로 나눠 테스트 => 단위 테스트 (Unit test) 

2. 자동수행 테스트 코드


테스트는 자동으로 수행되는 코드로 만들어지는 것이 중요합니다.
만약 테스트를 위해서 웹 화면에서 아이디와 비밀번호를 입력하고 버튼을 눌러 확인하는 작업이 반복될 경우 엄청나게 지루하고 불편합니다.

3. 지속적인 개선과 점진적인 개발을 위한 테스트 

하나의 기능을 수정하고 개선하기 위해서 기능이 제대로 동작하는지 확인하는 테스트가 필요합니다.   
기능 개선을 위해 내부적인 코드를 수정하였는데 이 코드가 제대로 동작하는지 확인하기 위해서 테스트 코드를 새로 작성한다면 귀찮을 것입니다.
하나의 테스트를 만들어 내부적인 코드가 바뀌더라도 바로 확인할 수 있는 테스트 코드를 만드는 것이 좋습니다.

4. 수행 결과 확인 자동화 

- 테스트 코드를 돌렸을 때 테스트 결과를 하나하나 확인하지 않아도 테스트가 통과했는지 확인을 자동화 해야합니다.

 

테스트가 실패하는 두가지의 경우가 있습니다.  
- 테스트 에러 : 테스트를 실행하는 동안 에러가 발생해서 실패   
- 테스트 실패 : 테스트 작업이 완료되었지만 원하는 결과값과 실제 결과가 다르게 나온 경우

 

Junit

자바의 단위 테스트를 지원하는 테스팅 프레임워크입니다.  
스프링에서 가장 많이 사용하는 테스팅 도구이고 사용하기 위해선 두가지를 지켜줘야 합니다.  


1. 메소드가 public으로 선언되어야 합니다.
2. 메소드에 @Test 어노테이션을 붙여줍니다.

 

Junit을 사용한 코드 예제

 

import static org.assertj.core.api.Assertions.*;
...
public class UserDaoTest {
	@Test
	public void andAndGet() throws SQLException {
		GenericXmlApplicationContext context
			= new GenericXmlApplicationContext("applicationContext.xml");

		UserDao dao = context.getBean("userDao", UserDao.class);
		User user = new User();
		user.setId("gyumee");
		user.setName("박성철");
		user.setPassword("springno1");

		dao.add(user);

		User user2 = dao.get(user.getId());

		assertThat(user2.getName()).isEqualTo(user.getName());
		assertThat(user2.getPassword()).isEqualTo(user.getPassword());
	}
}

 

junit에서 제공해주는 assertEquals라는 스태틱 메소드를 사용해서 값을 검증합니다.
테스트가 성공하면 초록색 막대가 뜨면서 테스트 성공!
실패 시 빨간색 막대 뜨면서 테스트 실패가 뜹니다.   
테스트를 실행만 하면 테스트 결과를 자동으로 확인할 수 있습니다.

isEqualTo와 같은 메소드들은 결과값을 비교해주는 매처(matcher)라고 합니다.
많은 매처들이 있으니 편리하게 사용하기 위해서 어떤것들이 있는지 확인해 보는것도 좋을 것 같습니다.

 

junit.sourceforge.net/javadoc/org/junit/Assert.html

 

테스트의 일관성

 

테스트를 반복적을 실행했을 때 테스트가 실패하기도 하고 성공하기도 하면 좋은 테스트라고 볼 수 없습니다.
코드에 변경사항이 없다면 테스트는 항상 동일한 결과를 내야 합니다.

 

포괄적인 테스트

개발자들이 테스트를 직접 만들 때 자주 하는 실수가 있습니다.
바로 성공하는 케이스만 골라서 만드는 것입니다.

테스트 코드를 안 만드는 것도 위험한 일이지만, 성의 없이 테스트를 만드는 바람에 문제가 있는 코드인데도 테스트가 성공하게 만드는 건 더 위험합니다.

테스트를 작성할 때 부정적인 케이스를 먼저 만드는 습관을 들이는게 좋습니다.

 

 

예외조건에 대한 테스트

import static org.assertj.core.api.Assertions.*;
...
public class UserDaoTest {
	@Test
	public void andAndGet() throws SQLException {
		GenericXmlApplicationContext context
			= new GenericXmlApplicationContext("applicationContext.xml");

		UserDao dao = context.getBean("userDao", UserDao.class);
		
		assertThrows(NullPointerException.class, () -> dao.get(user.getId()));
	}
}

 

Junit4 에서는 @Test(expected=NullPointerException.class) 와 같이 예상되는 에러를 @Test 어노테이션의 property값으로 넘겨주었습니다.

Junit5 에서는 assertThrows() 메서드가 추가되어 예상되는 부분에 대한 에러를 람다식을 이용하여 확인할 수 있습니다.
해당 메소드는 명시된 에러에서 파생된 에러 발생 시에도 테스트가 통과됩니다.
아래의 코드는 위의 코드와 같은 결과를 보입니다.

 

import static org.assertj.core.api.Assertions.*;
...
public class UserDaoTest {
	@Test
	public void andAndGet() throws SQLException {
		GenericXmlApplicationContext context
			= new GenericXmlApplicationContext("applicationContext.xml");

		UserDao dao = context.getBean("userDao", UserDao.class);
		
		assertThrows(Exception.class, () -> dao.get(user.getId()));
	}
}

 

테스트 주도 개발 (TDD, Test Driven Development)

 

테스트를 먼저 만들고, 테스트를 성공하게 해주는 코드를 작성하는 개발 방식입니다.

테스트를 작성하고 이를 성공시키는 코드를 만드는 작업의 주기를 가능한 한 짧게 가져가도록 권장합니다.

테스트 없이 한 번에 너무 많은 코드를 만드는 것은 좋지 않습니다.

테스트를 만들어두고 코드를 작성 후 테스트를 바로 하게 되면 코드에 대한 피드백을 매우 빠르게 받을 수 있습니다. 

테스트를 만들고 자주 실행하면 개발이 지연되지 않을까 생각할 수 있지만, 테스트 코드를 만들지않고 웹 화면에서 테스트 시에는 에러 발생 시 수정 후 다시 서버를 띄우고 테스트를 해야합니다. 

테스트 코드를 사용하면 오류를 빨리 잡아낼 수 있게 되고 전체적인 개발 속도는 오히려 빨라집니다.

 

테스트 클래스

 

테스트 메소드에서 미리 준비되어야 하는 로직은 @Before 메소드로 따로 뺄 수있습니다.
그리고 테스트 수행 후 처리해야할 로직은 @After 매소드를 사용 할 수 있습니다.

Jnuit이 하나의 테스트 클래스를 가져와 수행하는 방식

1. 테스트 클래스에서 @Test가 붙은 public이고 void형이며 파라미터가 없는 테스트 메소드를 모두 찾습니다.
2. 테스트 클래스의 오브젝트를 하나 만듭니다.
3. @Before가 붙은 메소드가 있으면 실행합니다.
4. @Test가 붙은 메소드를 하나 호출하고 테스트 결과를 저장해둡니다.
5. @After가 붙은 메소드가 있으면 실행합니다.
6. 나머지 테스트 메소드에 대해 2~5번을 반복합니다.
7. 모든 테스트의 결과를 종합해서 돌려줍니다.

 

주의할 점

 

1. 테스트 메소드에서 @Before, @After 메소드를 직접 호출하지 않습니다. 
주고받을 정보나 오브젝트가 있다면 인스턴스 변수를 이용해야 합니다.


2. 각 테스트를 실핼할 때마다 테스트 클래스 오브젝트를 새로 만듭니다.
한번 만들어진 테스트 클래스의 오브젝트는 하나의 테스트 메소드를 사용하고 나면 버려집니다.

=> 테스트 독립적 실행 보장


3. 테스트 메소드의 실행 순서는 정해져 있지 않습니다.

 

픽스처 

테스트를 수행하는데 필요한 정보나 오브젝트  
픽스처 생성 로직이 흩어져 있는 것보다 모여 있는 편이 나을 테니 가능한 한 @Before 메소드를 이용합니다.

 

어플리케이션 컨텍스트 관리

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = MyApplication.class)
public class SpringTestSupport {

    @Autowired
	private ApplicationContext context;


}

 

@ExtendWith는 Junit4의 RunWith(SpringRunner.class)와 비슷하다고 생각하면 됩니다.

@SpringBootTest는 @SpringBootApplication이 붙은 애너테이션을 찾아 context를 찾습니다.
@SpringBootApplication을 사용하지 않고 다음과 같이 애너테이션을 풀어 개발했다면 위와 같이 classes필드를 통해 지정해야합니다.

 

테스트를 여러개 진행할 경우 context를 매번 만들게 되면 시간이 오래걸립니다.
이렇게 @Autowired 어노테이션을 사용해서 스프링에 의해서 DI 받을 수 있습니다.

 

@Autowired 작동 원리

 

@Autowired가 붙은 인스턴스 변수가 있으면, 테스트 컨텍스트 프레임워크는 변수타입과 일치하는 컨텍스트 내 빈을 찾습니다.

타입이 일치하는 빈이 있으면 인스턴스 변수에 주입합니다.
=> 타입에 의한 자동 와이어링

스프링 어플리케이션 컨텍스트는 초기화할 때 자기 자신도 빈으로 등록하기 때문에 컨텍스트를 빈으로 주입받을 수 있습니다.

@Autowired는 타입으로 빈을 찾습니다. 만약 같은 타입이 두개가 선언되어있으면 이름을 확인해서 빈을 주입한다. 만약 이름으로도 찾을 수 없으면 예외가 발생합니다.

 

DI와 테스트

테스트 코드에서 테스트용 DI받는 방식은 여러가지가 있습니다..

1. 테스트 코드에 의한 DI
    - @Autowired로 빈 주입을 받고 해당 객체를 수정하는 방식입니다.   
    빈 정보를 수정하기 때문에 위험한 방식입니다.

2. 테스트를 위한 별도의 DI 설정
    - 테스트 전용 설정 파일을 만들어 테스트에서만 사용합니다. 

3. 컨테이너 없는 DI 테스트
    - 빈 주입 없이 테스트 코드 내에서 해당 객체를 만들어 인스턴스 변수로 선언하고 사용합니다.
    - 애플리케이션 컨텍스트를 사용하지 않으니 코드 간결, 단순해집니다.
    - 테스트 시 가장 우선적으로 고려해야할 방법입니다.

 

학습테스트

 

자신이 만들지 않은 프레임워크나 다른 개발팀에서 만들어서 제공한 라이브러리 등에 대해서 테스트하는 방법입니다. 

장점 

1. 다양한 조건에 따른 기능을 손쉽게 확인해 볼 수 있습니다. 
    - 해당 라이브러리가 다양한 조건에 대해서 어떻게 동작하는지 빠르게 확인 해 볼 수 있습니다.

2. 학습 테스트 코드를 개발 중에 참고할 수 있습니다.
    - 테스트 시 다양한 기능에 대한 코드를 작성하니 실제 개발에서도 코드를 참고할 수 있습니다.

3. 프레임워크나 제품을 업그레이드할 때 호환성 검증을 도와줍니다.
    - 제품 업그레이드 시 이미 작성해둔 테스트를 돌려보고 에러 발생 시 다운그레이드합니다.

4. 테스트 작성에 대한 좋은 훈련이 됩니다.
    
5. 새로운 기술을 공부하는 과정이 즐거워집니다.

 

버그 테스트

코드에 오류가 있을 때 그 오류를 가장 잘 드러내줄 수 있는 테스트입니다.

장점 

1. 테스트의 완성도를 높여줍니다.

2. 버그의 내용을 명확하게 분석하게 해줍니다.

3. 기술적인 문제를 해결하는 데 도움이 됩니다.

 

 

 

'Spring' 카테고리의 다른 글

Toby's Spring - Chap 6 - AOP  (0) 2020.07.19
Toby's Spring - Chap 4 - Exception  (0) 2020.06.29
Spring - Assert  (0) 2020.06.28
Toby's Spring - Chap 3 - Template/Callback practice  (0) 2020.06.23
Toby's Spring - Chap 3 - Template  (0) 2020.06.23