하루에 장 하나씩하는 것도 벅차네




junit의 작동방식의 특성상 테스트는 before -> test -> after 식으로 테스트 개수만큼 실행된다. 이게 spring의 경우 문제가 있는데 applicationContext가 테스트 개수만큼 생성됐다 소멸됐다 하는 것이다. 테스트 규모가 적을 경우 이는 문제가 없지만, 프로그램의 규모가 커지면 테스트하는 속도를 늦출 수 있다.  따라서 이 어플리케이션 컨텍스트를 단 한번만 생성할 수 있도록 스프링은 테스트를 지원한다.



1
2
3
4
5
6
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/applicationContext.xml")
public class UserDaoTest {
    @Autowired
    private ApplicationContext context;
cs


@RunWith는 Junit 프레임워크의 테스트 실행방법을 확장할 때 사용하는 애노테이션인데 SpringJUnit~ 는 이를 스프링용으로 확장하여 JUnit이 테스트를 진행하는 중에 테스트가 사용할 애플리케이션 컨텍스트를 만들고 관리하는 작업을 진행해준다. 

@ContextConfiguration은 설정파일의 위치를 지정한 것이다. 


이렇게 만들어진 어플리케이션 컨텍스트는 각 테스트가 여러개 진행되어도 단 하나만 존재하게 되며, 뿐만 아니라 다른 클래스에서 똑같은 어플리케이션 컨텍스트를 만들어서 테스트를 하더라도 단 하나만 존재하게 된다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/applicationContext.xml")
public class UserDaoTest {
    @Autowired
    private ApplicationContext context;
}
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/applicationContext.xml")
public class GroupDaoTest {
    @Autowired
    private ApplicationContext context;
}
 
cs


즉 위와같이 클래스가 다른 테스트 에서도 애플리케이션 컨텍스트는 단 하나만 생성된다.


이때 사용되는 어노테이션 @Autowired라는 친구를 변수에 붙이면 테스트 컨텍스트 프레임워크는 변수타입과 일치하는 컨텍스트 내의 빈을 찾아 해당 변수에 넣어준다. 애플리케이션 컨텍스트는 최초 초기화시에 자기자신을 빈으로 등록하므로 위와 같은 코드가 정상 작동하는 것이다.

 위처럼 UserDao도 @Autowired를 통해서 자동으로 빈을 DI받을수있는데 단, 같은 타입의 빈이 두 개 이상 있는 경우에는 어떤 빈을 가져올지 결정할 수 없다.이럴 경우 예외가 발생하게된다. 

 테스트의 경우에도 되도록 느슨하게 결합하기 위해 인터페이스를 활용하는게 좋지만, 필요에 따라 특정 타입의 구체화된 인스턴스를 의도적으로 테스트할 필요성이 있을 수 있기에 밀접한 관계를 가져도 상관없다.


 테스트에서 때로는 의존관계를 수동으로 DI해줘야 할 경우가 있다. 예를 들자면, 우리가 작성한 테스트가 실 서비스상에서 제공된다고 가정해보자. 그렇다면 deleteAll을 실행하는 순간 모든 데이터가 날아가 버릴 것이다. 이건 너무 위험천만한 행위이므로 우리가 임의적으로 testdb에서 실험을 진행할 수 있다. 이 때, 우리가 수동으로 DataSource를 만들어서 테스트디비에 접속하게 만들 수 있다. 다만, 이런 방식의 테스트는 애플리케이션 컨텍스트를 직접 수정하는 것이므로 위험할 수 있다. 애플리케이션은 한번 생성되어 다른 테스트들에게도 영향을 줄 수 있기 때문이다. 

 이럴 때 사용하는 어노테이션이 있는데 바로 @DirtiesConext이다. 간단하게 설명하자면 해당 테스트에서 컨텍스트를 더럽힐 것이므로 테스트 종료 후 새로운 어플리케이션컨텍스트를 생성하라는 어노테이션이다. 이는 클래스에도, 메소드에도 붙일 수있다. 

 하지만 이러는 것보다 그냥 테스트용 설정 파일(xml)을 만드는 것을 추천한다.(아니 왜설명한거야... )



2.5 학습 테스트

 학습 테스트는 자신이 만든 코드에 관한 테스트가 아니라 앞으로 사용하고자하는 api나 프레임워크의 기능을 테스트하는 것을 말한다. 이런 테스트는 검증에 목적이 있지 않고, 해당 기술의 학습에 목적이 있다. 

 

 길게 써놨는데 간략히 요약하자면, 레퍼런스가 될 수도 있고, 이미 작성되어있는 학습 테스트로 부터 레퍼런스 이상의 사용법을 배울 수 있으며, 해당 기술의 테스트방법을 공부할 수 있다는 의견인것 같다. 


 테스트 방법으로 동등분할과 경계값 분석이 있다. 동등 분할은 같은 결과를 내는 값의 범위를 구분해서 대표값으로 테스트 하는 것을 말하고, 경계값 분석은 0이나 그 주변 값 또는 최대값, 최소값등으로 테스트하는 것을 의미한다.


소스코드 : https://github.com/choiking10/spring_test/tree/vol1_2.4_2.6/lifeignite

블로그 쓰면서 공부하려니 넘나 힘든 것... 


이게 나중에 도움이 되겠지?... 


딱히 누구 보라고 작성하는 건 아니긴 한데... 흠... 




2.1 <- 테스트의 필요성이다. 

당욘한거죠!

넘어가


2.2 UserDaoTest

기존의 방식은 테스트의 결과 확인을 직접해야하는 번거로움이 있었다. 이것을 이제 테스트의 결과를 확인하기 쉽도록 UserDaoTest를 수정해보도록 하자



1
2
3
System.out.println(user2.getName());
System.out.println(user2.getPassword());
System.out.println(user2.getId()+" 조회 성공");
cs


이게 원래 수정 전 테스트 코드이다.


이 경우에는 실제 조회가 성공했는지 눈으로 확인해야한다. 이걸 자동화 시켜보도록 하자.



1
2
3
4
5
6
7
8
if(!user.getName().equals(user2.getName())){
    System.out.println("테스트 실패 (name)");
}
if(!user.getPassword().equals(user2.getPassword())){
    System.out.println("테스트 실패 (password)");
}else {
    System.out.println("조회 테스트 성공");
}
cs


자동화 성공!

...

별거없다.


2.3 JUnit 테스트로 전환하기

일단 junit을 사용하기위해 dependency를 pom.xml에 추가해주도록 하자.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <exclusions>
        <exclusion>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-all</artifactId>
    <version>1.3</version>
</dependency>
cs

그리고 다음을 static import한다 


1
2
import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.is;
cs


자 그러면  다음과 같은 모습이 되는데


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package lifeignite;
 
import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.is;
 
import org.junit.Test;
import org.junit.runner.JUnitCore;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import user.User;
import user.UserDao;
 
import java.sql.SQLException;
 
public class UserDaoTest {
    public static void main(String[] args) {
        JUnitCore.main("lifeignite.UserDaoTest");
    }
 
    @Test
    public void addAndGet() throws ClassNotFoundException, SQLException{
        ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
 
        UserDao dao = context.getBean("userDao", UserDao.class);
 
        User user = new User();
        user.setId("lifeignite");
        user.setName("광전사");
        user.setPassword("for");
 
        dao.add(user);
 
        User user2 = dao.get(user.getId());
 
        assertThat(user2.getName(),is(user.getName()));
        assertThat(user2.getPassword(),is(user.getPassword()));
    }
}
 
cs


조작을해보면서 성공시와 실패시 어떤 방식으로 뜨는지 체크할 수 있다. 


기존의 방식은 디비에서 직접 추가된 user를 삭제하고, 테스트를 돌리는 방식이었는데 넘나 귀찮다. 따라서 이제 삭제하는 메소드도 만드려고한다. 또 user테이플의 개수를 가져오는 것도 만들고 뚝딱뚝딱



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class UserDaoTest {
    @Test
    public void addAndGet() throws ClassNotFoundException, SQLException{
        ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
 
        UserDao dao = context.getBean("userDao", UserDao.class);
 
        dao.deleteAll();
        assertThat(dao.getCount(), is(0));
 
        User user = new User();
        user.setId("lifeignite");
        user.setName("광전사");
        user.setPassword("for");
 
        dao.add(user);
        assertThat(dao.getCount(),is(1));
 
        User user2 = dao.get(user.getId());
 
        assertThat(user2.getName(),is(user.getName()));
        assertThat(user2.getPassword(),is(user.getPassword()));
    }
}
 
cs


중요한 내용은 아니었고...


junit에서 테스트 중에 발생될 것으로 기대하는 예외 클래스를 지정해 줄 수 있다.

@Test(expected=somethingException.class)

이런방식으로 설정해둔 테스트에서는 예외가 발생하면 성공이고 아니라면 실패가 된다.

@Before 어노테이션은 테스트를 시작하기전에 먼저 실행해야하는 메소드를 정의하는데 사용된다.

@After 어노테이션은 테스트가 끝나고 난 후에 실행해야하는 메소드를 정의하는데 사용된다. 


JUNIT에서 테스트를 진행하는 과정을 간단히 정리하자면 아래와 같다.

1) 테스트 클래스에서 @Test가 붙은 public이고 void형이며 파라미터가 없는 테스트 메소드를 찾는다.

2) 테스트 클래스의 오브젝트를 하나 만든다.

3) @Before가 붙은 메소드가 있으면 실행한다.

4) @Test가 붙은 메소드를 하나 호출하고 테스트 결과를 저장해둔다

5) @After가 붙은 메소드가 있으면 실행한다.

6) 나머지 테스트 메소드에 대해 2~5번을 반복한다

7. 모든 테스트의 결과를 종합해서 돌려준다.



TDD에 관한 설명이 나오는데 너무 아름다우니까 두번 외치자


테스트를 만들고 테스트에 맞춰서 구현을 한다. 


알고리즘 사이트에서 테스트 케이스 만들고 구현하는 느낌인데 되게 새롭고 참신하고 매력적인 방법인것 같다.

이런방식으로 개발해야지


소스코드 : https://github.com/choiking10/spring_test/tree/vol1_2.1_2.3/lifeignite




+ Recent posts