개요
1. 테스트 결과의 일관성
2. 포괄적인 테스트
3. 테스트 주도 개발
본문
1. 테스트 결과의 일관성
책에서 소개하고 있는 JUnit의 사용법이나 간단한 소개는 생략하도록 하겠다. 현재까지의 코드는 테스트 결과에 일관성을 가지고 있지 못하다. 테이블에 동일한 ID값을 가지고 있으면 그 값 때문에 입력할 수 없다는 결과가 도출 될 것이다. 이러한 테스트는 좋은 테스트라고 할 수가 없다. 여러번 반복하더라도 동일한 결과를 얻을 수 있는 게 좋은 테스트이기 때문이다. 따라서 실행을 마칠 때마다 테이블을 비워서 실행하기 이전 상태로 돌리는 것이다. 이러한 기능을 실현하기 위해서 UserDao에 테이블을 모두 비우는 deleteAll()메소드와 getCount()메소드를 추가한다.
//데이터 모두 삭제
public void deleteAll() throws SQLException {
Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement("delete from users");
ps.executeUpdate();
ps.close();
c.close();
}
//테이블 레코드 갯수 리턴
public int getCount() throws SQLException {
Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement("select count(*) from users");
ResultSet rs = ps.executeQuery();
rs.next();
int count = rs.getInt(1);
rs.close();
ps.close();
c.close();
return count;
}
두 메소드가 추가 되었으니 이 메소드가 제대로 작동하는지 알아보기 위한 테스트도 만들 필요가 있다. 하지만 단독으로 만들고 테스트 하기에 두 메소드는 애매한 구석이 있다. 애초에 둘을 테스트 하려면 add를 할 필요가 있기 때문이다. 이 둘을 테스트 하기 위해서 일일이 add를 하기보다는 기존의 addAndGet테스트를 확장하는 게 더 효율적이다.
@Test
public void addAndGet() throws SQLException {
...
dao.deleteAll();//테이블 비우기
assertThat(dao.getCount(), is(0));//0개인지 체크, 아니면 오류 메세지 출력
User user = new User("cho", "초롱불", "greenlight");
dao.add(user);
assertThat(dao.getCount(), is(1));//1개인지 체크, 아니면 오류 메세지 출력
System.out.println(user.getId() + "등록 성공");
User user2 = dao.get(user.getId());
assertThat(user2.getName(), is(user.getName()));
assertThat(user2.getPassword(), is(user.getPassword()));
}
이제 어떤 값을 넣든 비워진 테이블에 입력하는 것이기에 다른 결과 값이 나오지 않는다. 이렇듯 일관성 있는 테스트를 만드는 것이 중요하다.
2. 포괄적인 테스트 개선
위의 코드에서 getCount()는 0과 1 두가지만을 테스트 해보고 있다. 이 정도의 테스트는 확실한 검증을 했다고 하기 어려운 면이 있다. 따라서 좀 더 꼼꼼한 getCount()테스트를 만들어 보겠다.
테스트는 USER테이블의 데이터를 모두 지우고 getCount의 값이 0인 것을 먼저 확인하고 사용자 정보를 하나 씩 늘려가면서 그때마다 getCount의 결과가 증가하는지 확인해 보는 식으로 진행해 보겠다. 먼저 사용자의 파라미터 값을 입력 받을 User 오브젝트의 생성자를 만들어주자.
public User(String id, String name, String password) {
this.id = id;
this.name = name;
this.password = password;
}
public User(){
//자바빈 규약에 따르는 클래스에 생성자를 명시적으로 추가했을 때는 파라미터가 없는 디폴트 생성자도 정의해준다
}
그리고 시나리오대로 count테스트 메소드를 만들어준다.
@Test
public void count() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml", UserDao.class);
UserDao dao = context.getBean("userDao", UserDao.class);
User user1 = new User("cho1", "초롱불1", "greenligh1");
User user2 = new User("cho2", "초롱불2", "greenligh2");
User user3 = new User("cho3", "초롱불3", "greenligh3");
dao.deleteAll();
assertThat(dao.getCount(), is(0));
dao.add(user1);
assertThat(dao.getCount(), is(1));
dao.add(user2);
assertThat(dao.getCount(), is(2));
dao.add(user3);
assertThat(dao.getCount(), is(3));
}
실행 시 문제가 없다는 것을 확인할 수 있다.
다음으로 addAndGet에서 get()의 기능을 더 꼼꼼하게 체크할 수 있도록 테스트를 개선하도록 해보자. get이 정말 주어진 id 값의 사용자를 가져온 것인지 아닌지를 체크할 수 있도록 말이다.
@Test
public void addAndGet() throws SQLException {
...
UserDao dao = context.getBean("userDao", UserDao.class);
User user1 = new User("cho1", "초롱불1", "greenligh1");
User user2 = new User("cho2", "초롱불2", "greenligh2");
dao.deleteAll();//테이블 비우기
assertThat(dao.getCount(), is(0));//0개인지 체크, 아니면 오류 메세지 출력
dao.add(user1);
dao.add(user2);
assertThat(dao.getCount(), is(2));
User userget1 = dao.get(user1.getId());
assertThat(userget1.getName(), is(user1.getName());
assertThat(userget1.getPassword(), is(user1.getPassword()));
User userget2 = dao.get(user2.getId());
assertThat(userget2.getName(), is(user2.getName()));
assertThat(userget2.getPassword(), is(user2.getPassword()));
}
가져온 값 userget값과 입력한 user의 값이 같은지 체크하여 더욱 기대한 대로 동작하는지 확신할 수 있게 되었다.
다음으로 get()메소드를 요청했을 때 해당 id에 해당하는 값이 없는 경우에 어떤 결과가 나올 수 있을지에 대해서 생각해보자. 두 가지 경우의 수를 생각해 볼 수 있을 것이다. 하나는 null값이 출력되는 경우의 수와 예외 값이 출력되는 경우의 수이다. 예외를 출력하게 한다고 생각하고 get메소드를 수정하기 전에 한 번 테스트 코드를 먼저 만들어 보도록 하자.
@Test(expected=EmptyResultDataAccessException.class) //테스트 중에 발생할 것으로 기대하는 예외 클래스 지정
public void getUserFailure() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml", UserDao.class);
UserDao dao = context.getBean("userDao", UserDao.class);
dao.deleteAll();
assertThat(dao.getCount(), is(0));
dao.get("unknown_id");
}
모든 User데이터를 지우고 존재하지 않는 id로 get()메소드를 실행하는 것이 테스트 코드의 전부이다. 이 테스트를 성공 시키려면 어떻게 해야 할까? 이 물음이 중요하다. 개발을 이끌어나갈 수 있는 키가 될 수 있다. 데이터를 찾지 못할 때 해당하는 예외가 출력되면 테스트의 성공이다. 그것을 위해서 get메소드를 수정하면 아래와 같이 할 수 있다.
public User get(String id) throws SQLException {
Connection c =dataSource.getConnection();
PreparedStatement ps = c.prepareStatement(
"select * from users where id = ?"
);
ps.setString(1, id);
ResultSet rs = ps.executeQuery();
User user = null;
if(rs.next()) {
user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
}
rs.close();
ps.close();
c.close();
if(user == null) throw new EmptyResultDataAccessException(1);
return user;
}
이처럼 수정함으로써 성공적인 테스트가 가능하다.
3. 테스트 주도 개발
방금전 사례는 테스트가 주도하는 개발이란 어떤 것인지 느낄 수 있게 한다. 원하는 기능을 위한 테스트를 먼저 만들고 그 테스트가 성공하게끔 하는 코드를 만들어 가는 것이다. 우리는 테스트를 위해서 deleteAll메소드, count메소드를 만들었고 get메소드에서 예외 값을 출력하게끔 메소드를 수정했다. 테스트를 통하다보면 필요한 기능에 대해서 보다 구체적으로 접근이 가능하기에 테스트의 가치가 중요함을 ㅇ ㅏㄹ 수가 있다.
정리
1. 테스트의 결과는 일관성을 가질 수 있게끔 만들어져야 한다.
2. 테스트를 성공적으로 실행해나가는 과정에서 새로운 기능의 확장을 가능하게 하고 요구사항에 접근할 수 있기에 테스트 주도 개발이라고까지 할 수 있는 개발의 방법론을 발견할 수 있다.
소스 깃허브
https://github.com/cholongbul/Tobyspring
cholongbul/Tobyspring
토비의 스프링 연습. Contribute to cholongbul/Tobyspring development by creating an account on GitHub.
github.com
'스프링' 카테고리의 다른 글
토비의 스프링 - 3.1 다시보는 초난감 DAO // 3.2 변하는 것과 변하지 않는 것 (0) | 2021.05.31 |
---|---|
토비의 스프링 - 2.4 스프링 테스트 적용 (0) | 2021.05.30 |
토비의 스프링 - 2.1 UserDaoTest다시 보기//2.2UserDaoTest개선 (0) | 2021.05.27 |
토비의 스프링 - 1.8 XML을 이용한 설정 (0) | 2021.05.23 |
토비의 스프링 - 1.7 의존관계 주입(DI) (0) | 2021.05.23 |