상세 컨텐츠

본문 제목

[2.3] 개발자를 위한 테스팅 프레임워크 Junit

토비의스프링

by kwanghyup 2020. 10. 28. 00:11

본문

UserDaoTest에 조회 테스트기능을 추가한다.

User user2 = dao.get(user.getId());
System.out.println(user2.getName());
System.out.println(user2.getPassword());

System.out.println(user2.getId() + " 조회성공");

1. 테스트 검증

 

테스트를 통해 확인하고 싶은 것은

add()에 전달한 User오브젝트에 담긴 사용자 정보와

get()을 통해 DB에서 가져온 User오브젝트 정보가

서로 정확히 일치하는가 여부이다.

 

조회 부분을 다음과 같이 코드를 수정하자.

if(!user.getName().equals(user2.getName())) {
	System.out.println("테스트 실패 : name ");
} else if(!user.getPassword().equals(user2.getPassword())) {
	System.out.println("테스트 실패 : password");
} else {
	System.out.println("조회테스트 성공");
}

user.getName() : DB 삽입시 사용한 오브젝트

user2.getName() : DB에서 가져온 오브젝트

두 데이터가 일치하지 않으면 테스트는 실패해야 한다.

 

2. JUnitTest로 전환

 

pom.xml에 JUnit 의존 설정을 추가한다.

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

 

이제 UserDaoTest를 Junit테스트로 바꿔보자.

package springbook.user.dao;

import static org.junit.Assert.assertEquals;

import java.sql.SQLException;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

import springbook.user.domain.User;

public class 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("gyumee");
		user.setName("박성철");
		user.setPassword("springno1");
		
		dao.add(user);
		
		User user2 = dao.get(user.getId());
		
		assertEquals(user2.getName(), user.getName());
		assertEquals(user2.getPassword(), user.getPassword());
		
	}	
}

 

assertEquals(expexted, actual)

expexted : DB에서 조회되는 값

actual : add()메서드를 통해 전달한 값이다. 

 

이 테스트를 실행하면 성공한다.

 


3. 테스트의 일관성

 

지금까지는 테스트를 실행하기전에 

DB의 USERS테이블에 있는 데이터를 모두 삭제해야했다.

 

코드에 문제가 없어도 데이터가 있는 경우 

기본키가 중복되어 테스트에 실패하고만다.

 

이는 외부의 사정에 따라 테스트가 성공하기도 하고 

실패하기도 한다는 의미이다.

이는 좋은 테스트가 아니다.

코드에 변경사항이 없다면 테스트 결과는 항상 동일해야한다.

 

이를 해결하기 위한 방법은 

addAndGet()메소드를 수행하고 나면

테스트를 수행하기 이전 상태로 만들어주는것이다.

 

이를 위해 UserDao에 두 가지르 메소드를 추가하자.

가장 먼저할일은  USERS 테이블에 있는 

데이터를 모두 삭제 해줄 deleteAll()을 추가하는것이다.

public void deleteAll() throws SQLException {
	Connection c = dataSource.getConnection();
	
	String sql = "truncate users";
	PreparedStatement ps = c.prepareStatement(sql);
	ps.executeUpdate();
	
	ps.close();
	c.close();
}

 

두 번째 할 일은 USERS 테이블의 레코드 개수를

돌려주는 getCount()메소드를 추가하는 것이다.

public int getCount() throws SQLException {
	Connection c = dataSource.getConnection();
	
	String sql = "select count(*) from users";
	PreparedStatement ps = c.prepareStatement(sql);
	
	ResultSet rs = ps.executeQuery();
	rs.next();
	int count = rs.getInt(1);
	
	return count; 
}

 

 

deleteAll()과 getCount()메소드를 기존에 테스트에 적용시키자.

package springbook.user.dao;

import static org.junit.Assert.assertEquals;

import java.sql.SQLException;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

import springbook.user.domain.User;

public class UserDaoTest {
	
	@Test
	public void addAndGet() throws ClassNotFoundException, SQLException {
		ApplicationContext context =
				new GenericXmlApplicationContext("applicationContext.xml");
		
		UserDao dao = context.getBean("userDao", UserDao.class);
		
		// 테스트를 시작하기 전에 DB의 모든 데이터를 삭제한다.
		dao.deleteAll();
		assertEquals(dao.getCount(), 0);
		
		User user  = new User(); 
		user.setId("gyumee");
		user.setName("박성철");
		user.setPassword("springno1");
		
		dao.add(user);
		
		User user2 = dao.get(user.getId());
		
		assertEquals(user2.getName(), user.getName());
		assertEquals(user2.getPassword(), user.getPassword());
		
		// DB에 데이터가 삽입 되었으므로 dao.getCount()는 1을 리턴해야한다. 
		assertEquals(dao.getCount(), 1);
	}	
}

테스트를 여러번 실행하더라도 동일한 결과를 보장한다.

 

 

 

getCount()메소드에 대한 새로운 테스트 메소드를 만들어보자.

 

테스트 시나리오는 다음과 같다.

USERS 테이블의 데이터를 모두 지우고

 레코드 수가 0개임을 확인한다.

 3명의 사용자 정보를 하나씩 추가하면서

getCount()의 결과가 하나씩 증가하는지 확인한다.

 

사용자 정보 생성의 편의를 위해 User클래스에 생성자를 추가하자.

public User() {}
public User(String id, String name, String password) {
	this.id = id;
	this.name = name;
	this.password = password;
}

 

UserDaoTest에 다음의 메소드를 추가한다.

@Test
public void count() throws SQLException, ClassNotFoundException {
	ApplicationContext context =
			new GenericXmlApplicationContext("applicationContext.xml");
	
	UserDao dao = context.getBean("userDao", UserDao.class);
	User user1 = new User("gyumee", "박상철", "springno1");
	User user2 = new User("leegw700", "이길원", "springno2");
	User user3 = new User("bumjin", "박범진", "springno3");
	
	dao.deleteAll();
	assertEquals(dao.getCount(), 0);
	
	dao.add(user1);
	assertEquals(dao.getCount(), 1);
	
	dao.add(user2);
	assertEquals(dao.getCount(), 2);
	
	dao.add(user3);
	assertEquals(dao.getCount(), 3);
}

 

JUnit테스트는 테스트 메소드 실행의 순서를 보장하지 않는다.

또한 테스트 결과가 순서에 영향을 받는다면

잘못된 만든 테스트이다.

 

 

 

이번에는 addAndGet() 테스트를 보완하자.

@Test
public void addAndGet() throws ClassNotFoundException, SQLException {
	ApplicationContext context =
			new GenericXmlApplicationContext("applicationContext.xml");
	
	UserDao dao = context.getBean("userDao", UserDao.class);
	
	// 테스트를 시작하기 전에 DB의 모든 데이터를 삭제한다.
	dao.deleteAll();
	assertEquals(dao.getCount(), 0);
	
	User user1 = new User("guymee", "박성철", "springno1");
	User user2 = new User("leegw700", "이길원", "springno2");
	
	dao.add(user1);
	dao.add(user2);
	assertEquals(dao.getCount(), 2);
	
	User userget1 = dao.get(user1.getId());
	assertEquals(userget1.getName(), user1.getName());
	assertEquals(userget1.getPassword(), user1.getPassword());
	
	User userget2 = dao.get(user2.getId());
	assertEquals(userget2.getName(), user2.getName());
	assertEquals(userget2.getPassword(), user2.getPassword());
}

 

get() 예외조건에 대한 테스트 

 

get()메소드에 전달된 id값에 해당하는 사용자 정보가 없다면 어떻게 될까?

이런 경우 id에 해당하는 정보를 찾을 수 없다고 예외를 던져야한다. 

 

예외클래스가 하나 필요하다.

스프링은 EmptyResultDataAccessException예외를 제공한다.

get()메소드에서 쿼리의 실행 결과가 아무것도 없을 때 

이 예외를 던지게 하면된다.

 

이번 테스트의 경우

예외가 던져지면 테스트가 성공해야하고

예외가 던져지지 않고 정상적으로 작업을 마치면

테스트가 실패하도록 해야한다.

 

UserDaoTest 테스트 메소드를 다음과 같이 추가하자.

// EmptyResultDataAccessExceptio예외가 발생해야 테스트에 성공한다.
@Test(expected = EmptyResultDataAccessException.class)
public void getUserFailure() throws SQLException, ClassNotFoundException {
	ApplicationContext context =
			new GenericXmlApplicationContext("applicationContext.xml");
	
	UserDao dao = context.getBean("userDao", UserDao.class);
	dao.deleteAll();
	assertEquals(dao.getCount(), 0);
	
	dao.get("unkown_id"); // 이 메서드 실행 중에 예외가 발생해야 한다.  
}

 

실행을 해보면 테스트에 실패한다.

 

get()메소드에서 rs.next()를 실행할때

가져올 로우가 없기때문에 SQLException이 발생한다.

 

 

테스트를 성공시키기 위한 코드의 수정 

 

rs.next()값이 존재할 때만 User오브젝트를 생성해 값을 넣어주고

그렇지 않으면 EmptyResultDataException 예외를 던진다.

 

UserDao의 get()메소드를 다음과 같이 수정하자.

public User get(String id) throws ClassNotFoundException, SQLException {
	
	Connection c = dataSource.getConnection(); 
	
	String sql = "select * from users where id = ?";
	PreparedStatement ps = c.prepareStatement(sql);
	ps.setString(1,id);
	
	ResultSet rs = ps.executeQuery();
	
	User user = null; // 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;
}	

이제 UserDaoTest를 실행하면 테스트에 성공한다.

 


4. 테스트 코드 개선

 

UserDaoTest 코드를 살펴보면 반복되는 부분이 있다.

바로 UserDao빈을 가져오는 부분이다.

 

JUnit제공하는 기능을 제공하여 이를 개선 해보자.

 

UserDaoTest를 다음과 같이 수정한다.

package springbook.user.dao;

import static org.junit.Assert.assertEquals;

import java.sql.SQLException;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.dao.EmptyResultDataAccessException;

import springbook.user.domain.User;

public class UserDaoTest {
	
	private UserDao dao; // 각각의 테스트 메소드에서 사용하기 때문에 필드변수로 선
	
	@Before
	public void setUp() { // 중복되는 부분 추출 
		ApplicationContext context =
				new GenericXmlApplicationContext("applicationContext.xml");
		dao = context.getBean("userDao", UserDao.class);
	}
	
	@Test
	public void addAndGet() throws ClassNotFoundException, SQLException {
		// UserDao빈을 가져오는 코드를 삭제했다.

		dao.deleteAll();
		assertEquals(dao.getCount(), 0);
		
		User user1 = new User("guymee", "박성철", "springno1");
		User user2 = new User("leegw700", "이길원", "springno2");
		
		dao.add(user1);
		dao.add(user2);
		assertEquals(dao.getCount(), 2);
		
		User userget1 = dao.get(user1.getId());
		assertEquals(userget1.getName(), user1.getName());
		assertEquals(userget1.getPassword(), user1.getPassword());
		
		User userget2 = dao.get(user2.getId());
		assertEquals(userget2.getName(), user2.getName());
		assertEquals(userget2.getPassword(), user2.getPassword());
	}
	
	@Test
	public void count() throws SQLException, ClassNotFoundException {
		// UserDao빈을 가져오는 코드를 삭제했다.
		
		User user1 = new User("gyumee", "박상철", "springno1");
		User user2 = new User("leegw700", "이길원", "springno2");
		User user3 = new User("bumjin", "박범진", "springno3");
		
		dao.deleteAll();
		assertEquals(dao.getCount(), 0);
		
		dao.add(user1);
		assertEquals(dao.getCount(), 1);
		
		dao.add(user2);
		assertEquals(dao.getCount(), 2);
		
		dao.add(user3);
		assertEquals(dao.getCount(), 3);
		
	}
	
	
	@Test(expected = EmptyResultDataAccessException.class)
	public void getUserFailure() throws SQLException, ClassNotFoundException {
		// UserDao빈을 가져오는 코드를 삭제했다.
		
		dao.deleteAll();
		assertEquals(dao.getCount(), 0);
		
		dao.get("unkown_id"); // 이 메서드 실행 중에 예외가 발생해야 한다.  
	}	
}

 

JUnit은 @Tset가 붙은 메소드가 실행하기 전 후에 

각각 @Before와 @After가 붙은 메소드를 자동실행한다.

 

JUnit은 각 테스트 메소드를 실행할 때마다 테스트 클래스의 

오브젝트를 새로만든 다.

한번 만들어진 테스트 클래스의 오브젝트는

하나의 테스트 메소드를 사용하고 나면 버려진다. 

 

다음과 같이 클래스를 만들어 테스트 결과를 확인 해보자.

package springbook.mytest;

import static org.junit.Assert.*;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

import springbook.user.dao.UserDao;

public class JUnitStudySample {

	private UserDao dao;
	
	@Before
	public void setUp() {
		System.out.println("@Before 실행");
		ApplicationContext ctx = new GenericXmlApplicationContext("applicationContext.xml");
		dao = ctx.getBean("userDao",UserDao.class);
	}
	
	@After
	public void tearDown() {
		System.out.println("@After 실행");
		
	}
	
	@Test
	public void test01() {
		System.out.println("test01 실행 : " + dao);
	}
	
	@Test
	public void test02() {
		System.out.println("test02 실행 : " + dao);
	}
	
	@Test
	public void test03() {
		System.out.println("test03 실행 : " + dao);
	}
	
}

결과를 확인하면 매번 다른 UserDao 객체가 생성됨과 

@Before와 @After메소드가 

각각 테스트 메소드 실행 전후에 실행됨을 알 수 있다.

 

 

픽스처

 

테스트를 수행하는 데 필요한 정보나 오브젝트를 픽스처라 한다.

 

UserDaoTest에서 dao는 픽스처로 볼 수 있다.

User오브젝트 또한 픽스처이다.

픽스처는 각각의 테스트 메소드에서 

반복 사용되기 때문에 @Before 메소드에 두면 편리하다.

 

이제 User오브젝트를 @Before 메소드에서 초기화 하자

package springbook.user.dao;

import static org.junit.Assert.assertEquals;

import java.sql.SQLException;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.dao.EmptyResultDataAccessException;

import springbook.user.domain.User;

public class UserDaoTest {
	
	private UserDao dao; 
	
	// 각각의 메소드에서 사용하기 위해서 필드 변수로 선언했다.
	private User user1;
	private User user2;
	private User user3;
	
	@Before
	public void setUp() {  
		ApplicationContext context =
				new GenericXmlApplicationContext("applicationContext.xml");
		dao = context.getBean("userDao", UserDao.class);
		
		user1 = new User("gyumee", "박상철", "springno1");
		user2 = new User("leegw700", "이길원", "springno2");
		user3 = new User("bumjin", "박범진", "springno3");
	}
	
	@Test
	public void addAndGet() throws ClassNotFoundException, SQLException {
		// 기존에 있던 User 오브젝트 생성 코드를 제거했다.
		
		dao.deleteAll();
		assertEquals(dao.getCount(), 0);
		

		dao.add(user1);
		dao.add(user2);
		assertEquals(dao.getCount(), 2);
		
		User userget1 = dao.get(user1.getId());
		assertEquals(userget1.getName(), user1.getName());
		assertEquals(userget1.getPassword(), user1.getPassword());
		
		User userget2 = dao.get(user2.getId());
		assertEquals(userget2.getName(), user2.getName());
		assertEquals(userget2.getPassword(), user2.getPassword());
	}
	
	@Test
	public void count() throws SQLException, ClassNotFoundException {
		// 기존에 있던 User 오브젝트 생성 코드를 제거했다.		
		
		dao.deleteAll();
		assertEquals(dao.getCount(), 0);
		
		dao.add(user1);
		assertEquals(dao.getCount(), 1);
		
		dao.add(user2);
		assertEquals(dao.getCount(), 2);
		
		dao.add(user3);
		assertEquals(dao.getCount(), 3);
		
	}
	
	@Test(expected = EmptyResultDataAccessException.class)
	public void getUserFailure() throws SQLException, ClassNotFoundException {
		
		dao.deleteAll();
		assertEquals(dao.getCount(), 0);
		
		dao.get("unkown_id");   
	}	
}

테스트를 실행하면 성공한다.

관련글 더보기

댓글 영역