Test 작성하기
Test란 프로그램의 품질 검증을 위한 것으로 우리의 의도대로 프로그램이 동작하는지 확인하는 것이다.
웹 브라우저를 통하여 확인하는 과정을 최소화 시킬 수 있다.
예상 시나리오 작성
실제 코드 결과 와 비교 및 비교
성공시 리팩토링 실패시 디버깅을 통해 코드를 고친다.
TDD란 Test Driven Development의 약자로 ‘테스트 주도 개발’이라고 한다.
테스트를 반복적으로 이용한 소프트웨어 방법론으로 작은 단위의 테스트 케이스를 작성하고 이를 통과하는 코드를 실제 코드에 추가하는 단계를 반복하여 구현한다.
보통의 개발 방식은 '요구사항 분석 -> 개발 -> 테스트 -> 배포'의 형태의 개발주기를 갖는다.
하지만 이러한 방식에는 소프트웨어 개발을 느리게 하는 잠재적 위험이 존재한다.
위험 이유
1. 소비자의 요구사항이 처음부터 명확하지 않을 수 있다.
- 고객의 요구사항 또는 디자인의 오류등 많은 외부 또는 내부 조건에 의해 재설계되어 완벽한 설계로 나아가기 때문에
- 처음부터 완벽한 설계는 없다.
- 자체 버그 검출 능력 저하 또는 소스코드의 품질이 저하될 수 있다.
- 작은 부분의 수정에도 모든 부분을 테스트 해야 하므로 전체적인 버그를 검출하기 어려워진다. 또한 버그가 어디서 발생할지 모르기 때문에 잘못된 코드도 고치지 않으려 하는 현상이 나타난다.(품질 저하)
- 자체 테스트 비용이 증가할 수 있다.
- 작은 수정에도 모든 기능을 다시 테스트 해야 하는 문제가 발생하여 자체 테스트 비용이 증가된다.
결론적으로 이러한 코드들은 재사용이 어렵고 관리가 어려워 유지보수가 어려워진다.
TDD와 일반적인 개발 방식의 가장 큰 차이점은 테스트 코드를 작성한 뒤에 실제 코드를 작성한다는 것이다.
테스트 코드를 작성하는 도중 발생하는 예외 사항(버그 및 수정사항)은 테스트 케이스에 추가하고 설계를 개선한다.
이후 테스트가 통과된 코드만을 코드 개발 단계에서 실제 코드로 작성한다.
이러한 반복적인 단계가 진행되면서 자연스럽게 코드의 버그가 줄어들고 소스코드는 간결해진다.
생산성 저하
소프트웨어 개발 할 때는 다양한 성공과 실패가 나올 수 있다. 게시판으로 예를 들면, 존재하지 않는 id의 게시글 생성이나 삭제, id와 title만 수정한 게시글 등이 있다. 그래서 각 성공과 실패에 따른 테스트 코드를 작성해야한다.
또한 테스트 코드를 작성 시 해당 메서드의 예상되는 객체값과 실제 반환되어야 하는 객체값에 대한 생성과 각 객체의 값이 같은지를 나타내어 테스트가 정상작동하는지 확인해야 한다.
@SpringBootTest // 해당 클래스는 스프링부트와 연동되어 테스팅 된다.
class ArticleServiceTest {
@Autowired
ArticleService articleService;
@Test // 해당 메서드가 테스트를 위한 코드라는 것을 선언하는 어노테이션
void index() { // 성공과 실패의 경우를 한가지 테스트에서 실행
// 예상 시나리오
Article a = new Article(1L, "1111", "aaaa");
Article b = new Article(2L, "2222", "bbbb");
Article c = new Article(3L, "3333", "cccc");
List<Article> expected = new ArrayList<Article>(Arrays.asList(a, b, c));
// 실제 결과
List<Article> articles = articleService.index();
// 비교 (예상 시나리오와 실제 결과가 같은지 비교한다)
assertEquals(expected.toString(), articles.toString());
}
@SpringBootTest
@Test
이 메서드에서는 id가 1, 2, 3인 게시글을 예상해서 만든다. (예상 객체 값)
이후 articleService의 메서드의 반환 값을 생성 한다.
마지막으로 각 객체들의 값이 같은지 assertEquals메서드를 활용해 비교한다.
다른 테스트 코드들도 보자.
@Test
void show_success__존재하는_id_입력() { // 성공하는 경우는 많기 때문에 여러가지로 나눌 수 있다.( 이 테스트는 id 가 존재했을 때 출력이 잘되는지 확인하는 테스트)
//예상
Long id = 1L;
Article expected = new Article(id, "1111", "aaaa");
// 실제
Article article = articleService.show(id);
// 비교
assertEquals(expected.toString(), article.toString());
}
@Test
void show_fail__존재하지_않는_id_입력(){ // 이 테스트는 id가 존재 하지 않을 때 null값이 잘 나오는지 확인하는 테스트
//예상
Long id = -1L;
Article expected = null; // 없는 id 일 경우 orElse함수를 통해 null값을 반환하기로 했었기 때문에 null을 넣음
// 실제
Article article = articleService.show(id);
// 비교
assertEquals(expected, article); // null값은 toString 메서드 사용 X
}
@Test
@Transactional
void save__성공__title과_content만_있는_dto_입력() {
//예상
String title = "4444";
String content = "aaaa";
ArticleForm dto = new ArticleForm(null, title, content);
Article expected = new Article(4L, title, content);
// 실제
Article article = articleService.save(dto);
// 비교
assertEquals(expected.toString(), article.toString());
}
@Test
@Transactional
void save__실패__id가_포함된_dto_입력() { // 데이터 작성할때 다양한 실패가 있다. ex) id가 포함된 dto, 다른 변수가 생성된 dto등 -> 그 중 하나를 만드는 것
//예상
Long id = 5L;
String title = "4444";
String content = "dddd";
ArticleForm dto = new ArticleForm(4L, title, content);
Article expected = null;
// 실제
Article article = articleService.save(dto); // id가 존재 할 때 null값 반환 (save메서드 보면 알수 있음)
// 비교
assertEquals(expected, article);
}
이 테스트들을 한번에 돌리게 되면 index() 메서드의 오류가 생긴다. (id 4의 데이터가 생성되어서 -> save성공 및 실패 메서드에서 데이터가 추가되었기 때문에)
이 오류는 save메서드 테스트를 트랜잭션(롤백)을 하지 않아서 이다.
--> save 성공 및 실패 메서드에 트랜잭션 어노테이션을 작성하여 오류가 나지 않게 한다.
(트랜잭션 어노테이션은 성공시에도 롤백을 한다.)
공부한 테스트 코드는 더 있지만, 비슷한 코드들이 대부분이기 때문에 여기까지만 확인할 것이다.
References (참고 자료)
https://www.inflearn.com/course/%EA%B0%9C%EB%85%90%EC%8B%A4%EC%8A%B5-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-
https://hanamon.kr/tdd%EB%9E%80-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A3%BC%EB%8F%84-%EA%B0%9C%EB%B0%9C/