JUnit이란?
자바와 JVM기반 언어(ex: kotlin) 의 단위 테스트 프레임워크
책과 다르게 SpringBoot, JUnit5, IntelliJ, Gradle을 사용하여 JUnit을 다뤄보겠습니다.
JUnit 테스트 실행 방법
JUnit5를 활용해 테스트를 실행하는 방법에 대해 다룹니다.
더보기
간단한 연산을 하는 Caclulator 클래스
@Service
public class Calculator {
public int add (int a, int b) {
return a + b;
}
}
Caclulator클래스를 테스트 하기 위한 CacalculatorTest 클래스
@SpringBootTest
class CaclulatorTests {
@Autowired
Calculator calculator;
@Test
void addition() {
assertEquals(2, calculator.add(1,1));
}
}
테스트 결과의 일관성
테스트의 결과는 일관적이어야 한다는 내용에 대해 다룹니다.
더보기
테이블의 모든 데이터를 삭제하는 deleteAll() 메서드, 테이블의 데이터 갯수를 반환하는 getCount() 메서드가 있다.
deleteAll()메서드를 확인하려면 데이터가 모두 삭제됐는지 확인해야 하는데 해당 메서드를 사용하고 테이블이 빈 상태에서 다시 사용하면 동작의 확인이 어렵다는 단점이 있다. 그래서 지난시간에 만든 addAndGet() 메서드를 확장하는 형식으로 테스트 할 수 있겠다. 아래 코드를 통해 확인해보자.
@SpringBootTest
public class DBTest {
@Autowired
Dao dao;
@Test
void addAndGet() {
dao.deleteAll();
assertEquals(dao.getCount(), 0);
User user = new User();
user.setId("amugae");
user.Setname("아무개");
user.setPassword("springno1");
dao.add(user);
assertEquals(dao.getCount(), 1);
User user2 = dao.get(user.getId);
assertEquals(user.getName(), user1.getName());
assertEquals(user2.getPassword(), user.getPassword());
}
}
동일한 결과를 보장하는 테스트
테스트는 DB 데이터의 변경 및 외부환경, 테스트 순서 변경과 상관없이 코드의 변경이 없다면 동일한 결과를 보장해야 한다.
포괄적인 테스트
포괄적인 테스트를 하는 방법에 대해 다룹니다.
더보기
getCount() 메서드를 테스트에 적용했으나 비어있는 경우(0), add를 한 번 한 경우(1)에 대한 테스트 뿐이었다.
두 개 이상의 레코드를 add() 했을때의 getCount()의 실행결과는 다를 수 있다.
이렇게 단편적인 테스트보다는 여러각도에서 다양한 시도로 테스트를 해야한다.
getCount() 테스트
getCount()를 테스트하기 위한 새로운 메소드를 추가해보자.
@Test
void getCountTest() {
User user1 = new User("id", "이름", "비밀번호1");
User user2 = new User("idid", "이름이름", "비밀번호2");
User user3 = new User("ididid", "이름이름이름", "비밀번호3");
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);
}
addAndGet() 테스트 보완
id를 조건으로 사용자를 검색하는 기능을 가진 get()에 대한 테스트 보완하기
@Test
void addAndGetUpgrade() {
User user1 = new User("id", "이름", "비밀번호1");
User user2 = new User("idid", "이름이름", "비밀번호2");
dao.deleteAll();
assertEquals(dao.getCont(), 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());
}
get() 예외조건에 대한 테스트
get() 메소드에 id 값에 해당하는 사용자 정보가 없을 경우를 가정한 test
이 경우에는 null등의 특별한 값을 리턴하거나 예외를 던지는 것이 있겠다.
이에 대한 테스트를 작성해보자.
JUnit5에서의 예외조건 테스트
@Test
void getUserFailure1() {
dao.deleteAll();
assertEquals(dao.getCount(), 0);
Assertions.assertThrows(EmptyResultDataAccessException.class, () -> {
dao.get("unknown id");
});
}
@Test
void getUserFailure2() {
assertThatThrownBy(() -> dao.get("unknown id"))
.isInstanceOf(EmptyResultDataAccessException.class);
}
@Test
void getUserFailure3() {
try{
dao.get("unknown id");
}catch (EmptyResultDataAccessException e) {
Assertions.assertEquals("failed Calc", e.getMessage());
}
}
@Test
void getUserFailure4() {
Throwable exception = assertThrows(EmptyResultDataAccessException.class, () -> {
dao.get("unknown id");
});
assertEquals("failed calc", exception.getMessage());
}
테스트를 성공시키기 위한 코드의 수정
테스트를 성공시키기 위해 get() 메소드 코드를 수정해보자
id에 해당하는 데이터가 없다면 EmptyResultDataAccessException을 던지는 코드를 추가
개선된 get() 메소드 코드
public User get(String id) throws SQLException {
...
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();
return user;
}
포괄적인 테스트
다양한 상황을 가정하여 해당 메서드에 대한 확신을 가지자.
단순하고 간단한 테스트가 치명적인 실수를 피할 수 있게 해주기도 한다.
스프링의 창시자인 로드존슨 왈 "항상 네거티브 테스트를 먼저 만들라"고 하였다.
성공적인 테스트도 좋지만 놓치기 쉬운 부정적인 테스트를 우선시하여 테스트 하는 것이 좋다.
테스트가 이끄는 개발
기능을 만든 후 수정하는 것이 아닌 테스트로 검증된 코드를 기반으로 기능을 만드는 방식이 있다.
이러한 테스트 기반 개발방법에 대해 다룹니다.
더보기
기능설계를 위한 테스트
테스트 코드는 잘 작성된하나의 기능정의서와 같다.
기능설계, 구현, 테스트라는 일반적인 개발 흐름의 기능 설계에 해당하는 부분을 테스트 코드가 일부분 담당하고 있다.
getUserFailure() 테스트 코드에 나타난 기능
단계 | 내용 | 코드 | |
조건 | 어떤 조건을 가지고 | 가져올 사용자 정보가 존재하지 않는 경우 | dao.deleteAll(); assertEquals(dao.getCount(), 0); |
행위 | 무엇을 할 때 | 존재하지 않는 id로 get()을 실행하면 | get("unkown_id"); |
결과 | 어떤 결과가 나온다. | 특별한 예외가 던져진다. | Assertions.assertThrows(EmptyResultDataAccessException.class |
테스트 주도 개발 (TDD)
만들고자 하는 기능의 내용을 담고 있으면서 만들어진 코드를 검증도 해줄 수 있도록 테스트 코드를 먼저 만들고, 테스트를 성공하게 해주는 코드를 작성하는 방식의 개발방법이 있다. 이를 테스트 주도 개발(TDD, Test Driven Development)이라고 한다. 또는 테스트를 코드보다 먼저 작성한다고 해서 테스트 우선 개발(Test First Development)이라고도 한다. "실패한 테스트를 성공시키기위한 목적이 아닌 코드는 만들지 않는다" 는 것이 TDD의 기본원칙이다.
장점
- 테스트를 빼먹지 않고 꼼꼼하게 만들 수 있음.
- 테스트를 작성하는 시간과 애플리케이션 코드를 작성하는 시간의 간격이 짧아진다.
- 자연스럽게 단위 테스트 작성 가능
단점
- 생산성의 저하
테스트 코드 개선
지금까지 만든 세 개의 테스트 메소드를 개선해보자. (중복코드 제거 등)
더보기
@BeforeAll ( JUnit 4이전 @Before )
Test작성시 필요한 사전작업 등에 사용한다.
Example Code
public class DBTest {
@BeforeAll
public void setUp() {
doFirstSomeThing(); // ex db connection and enviorment setting
}
@Test
void getUserFailure1() {
...
}
@Test
void getUserFailure2() {
...
}
}
@AfterAll( Junit 4 이전 @Before )
JUnit이 하나의 테스트 클래스를 가져와 테스트를 수행하는 방식
- 테스트 클래스에서 @Test가 붙은 public이고 void형이며 파라미터가 없는 테스트 메소드를 모두 찾는다.
- 테스트 클래스의 오브젝트를 하나 만든다.
- @BeforeAll가 붙은 메소드가 있으면 실행한다.
- @Test가 붙은 메소드를 하나 호출하고 테스트 결과를 저장해둔다.
- @AfterAll가 붙은 메소드가 있으면 실행한다.
- 나머지 테스트 메소드에 대해 2~5번을 반복한다.
- 모든 테스트의 결과를 종합해서 돌려준다.
픽스처
테스트 수행시 필요한 정보나 오브젝트
일반적으로 픽스처는 여러 테스트에서 반복적으로 사용되기 때문에 @Before 메소드를 이용해 생성해두면 편리하다.
ex) UserDaoTest -> dao
public Class UserDaoTest {
private User user1;
private User user2;
private User user3;
@BeforeAll
public void setUp() {
this.user1 = new User("gyumee", "이름1", "springno1");
this.user2 = new User("leegw700", "이름2", "springno2");
this.user3 = new User("bumjin", "이름3", "springno3");
}
}
References By
https://donghyeon.dev/junit/2021/04/11/JUnit5-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C/
https://covenant.tistory.com/256
'BackEnd > Spring & Springboot Study' 카테고리의 다른 글
[토비의 스프링] 2.5 학습 테스트로 배우는 스프링 (0) | 2022.12.23 |
---|---|
[토비의 스프링] 2.4 스프링 테스트 적용 (0) | 2022.12.23 |
[토비의 스프링] 2.2 UserDaoTest 개선 (0) | 2022.12.18 |
[토비의 스프링] 2.1 UserDaoTest 다시 보기 (0) | 2022.12.12 |
[토비의 스프링] 1장 정리 [IoC와 DI] (0) | 2022.12.08 |