길벗 코딩자율학습단 13일차

donghan378·2025년 1월 26일

13일차

~13.2 테스트 코드 작성하기(종이책 p.363~387)

13장 테스트 코드 작성하기

13.1 테스트란

테스트(test)란 프로그램의 품질을 검증하는 것으로, 의도대로 프로그램이 잘 동작하는지 확인하는 과정이다.

테스트 도구를 이용해 반복적인 검증 절차를 자동화할 수 있다.

테스트 도구를 활용해 코드를 검증한다는 것은 테스트 코드(test code)를 작성해 실행한다는 말로, 테스트 코드는 보통 다음 3단계로 작성한다.

  1. 예상 데이터 작성하기
  2. 실제 데이터 획득하기
  3. 예상 데이터와 실제 데이터 비교해 검증하기

작성한 코드가 테스트를 통과하면 지속적인 리팩터링으로 코드를 개선하고, 테스트를 통과하지 못하면 잘못된 부분을 찾아 고치는 디버깅(debugging)을 해야한다.

테스트 코드는 다양한 경우를 대비해 작성하는데, 이를 테스트 케이스(test case)라고 한다. 성공할 경우뿐만 아니라 실패할 경우도 고려해야 한다.

테스트 주도 개발(TDD, Test Driven Development)이란 일단 테스트 코드를 만든 후 이를 통과하는 최소한의 코드부터 시작해 점진적으로 코드를 개선 및 확장해 나가는 개발 방식이다.

13.2 테스트 코드 작성하기

12장에서 만든 ArticleService를 검증하는 테스트 코드를 작성해 본다.

  1. servcie/ArticleService를 연다. 테스트 하고 싶은 메서드(index())에서 마우스 오른쪽 버튼을 누르고 Generate → Test를 선택한다.
  2. Create Test창이 열리면 Testing library를 JUnit5로 선택하고 Member에서 원하는 메서드(index():List<Article>)를 선택해 OK 버튼을 누른다. (이 책에는 JUnit5로 테스트를 진행해본다.)

이러면 index() 메서드의 테스트 코드가 자동으로 생성되는데, 테스트 코드는 main 디렉터리와 같은 디렉터리에 생성되며 내부 구조(경로)가 데칼코마니처럼 만들어진다.


자바 코드:  src/main/java/com.example.firstproejct/service/ArticleService
테스트 코드: src/test/java/com.example.firstproject/service/ArticleServiceTest

이제 ArticleServiceTest 코드를 살펴보자.

가장 먼제 눈에 들어오는 것은 @Test 이다. @Test 어노테이션은 해당 메서드가 테스트를 위한 코드라고 선언하는 것이다. Test 패키지를 임포트하고, 앞으로 사용할 가능성이 있는 패키지도 미리 임포트해 놓는다.

테스트 코드를 스프링 부트와 연동하기 위해 다음과 같이 설정한다.

  1. ArticleServcieTest 클래스 위에 @SpringBootTest를 붙인다. 이렇게 하면 해당 클래스를 스프링 부트와 연동해 통합 테스트를 수행하겠다고 선언하는 것이며, 스프링 부트가 관리하는 다양한 객체를 주입받을 수 있다.
  2. articleService 객체를 선언한다. 외부 객체를 주입해야 하므로 @Autowired를 붙인다.

index() 테스트하기

  1. 예상 데이터 작성하기

    • 예상 데이터는 main/resources/data.sql 파일을 열어 확인한다.
    • 확인한 각 데이터를 Article 객체 a, b, c에 저장한다. 이때 id는 Long 타입이므로 접미사 L을 붙인다.
    • 3개의 객체를 Arrays.asList() 메서드를 사용해 ArrayList로 합치고 List<Article> 타입의 expected에 저장한다.
    
    Article a = new Article(1L, “가가가가”, “1111”);
    Article b = new Article(2L, “나나나나”, “2222”);
    Article c = new Article(3L, “다다다다”, “3333”);
    List<Article> expected = new ArrayList<Article>(Arrays.asList(a, b, c));
    

    ※ Arrays.asList() 메서드는 입력된 배열 또는 2개 이상의 동일한 타입 데이터를 정적 리스트로 만들어 반환한다. 정적 리스트는 고정 크기이므로 add()나 remove() 메서드를 사용할 수 없다.

  2. 실제 데이터 획득하기

    • articleService.index() 메서드를 호출해 그 결과를 List<Article> 타입의 articles에 받아 온다.
    
    List<Article> articles = articleService.index();
    
  3. 예상 데이터와 실제 데이터 비교해 검증하기

    • assertEquals(x, y) 메서드를 이용해 비교한다. 이 메서드는 JUnit에서 제공하고, 예상 데이터(x)와 실제 데이터(y)를 비교해 일치하면 테스트를 통과시킨다.
    
    assertEquals(expected.toString(), articles.toString());
    

index() 메서드의 시작 행 바로 왼쪽에 있는 실행 버튼을 클릭하고 Run ‘ArticleServiceTest.i…’를 선택한다. 잠시 후 실행창에 로그가 나오고 왼쪽을 보면 테스트에 통과한 것을 확인할 수 있다.

만약 테스트에 실패한다면 테스트에 통과하지 못했다고 뜬다.

show() 테스트하기

  1. show() 메서드에 마우스 오른쪽 버튼으로 Generate → Test로 테스트 코드를 생성한다.

  2. 성공하는 경우와 실패하는 경우로 나눠서 테스트하기 위해 show() 테스트 코드를 바로 아래에 복사 붙여넣기 해서 하나는 show_성공(), 다른 하나는 show_실패()로 수정한다.

  3. 게시글 조회에 성공하는 상황은 다양하므로, 존재하는 id를 입력해 성공하는 경우에 대해서 테스트해본다.

    1. show_성공() 메서드 이름을 show_성공_존재하는_id_입력()으로 수정한다.
    2. 예상 데이터는 사용자가 id가 1인 게시물의 조회를 요청했다고 가정하고 작성한다.
    3. 실제 데이터를 얻기 위해 articleService.show(id) 메서드를 호출해 article 개체에 저장한다.
    4. assertEquals() 메서드를 이용해 비교한다.
    
    void show_성공_존재하는_id_입력() {
    	// 1. 예상 데이터
    	Long id = 1L;
    	Article expected = new Article(id, “가가가가”, “1111”);
    	// 2. 실제 데이터
    	Article article = articleService.show(id);
    	// 3. 비교 및 검증
    	assertEquals(expected.toString(), article.toString());
    }
    

    Run으로 실행해보면 테스트에 잘 통과하는 모습을 볼 수 있다.

  4. 실패하는 경우도 다양한데, 존재하지 않는 id를 입력해 실패하는 경우를 테스트해본다.

    1. show_실패() 메서드의 이름을 show_실패_존재하지_않는_id_입력() 으로 수정한다.
    2. 예상 데이터는 존재하지 않는 id인 -1을 조회한다고 가정해 작성한다. 이 경우 DB에서 조회되는 내용이 없어 null을 반환할 것이므로 expected 객체에 null을 저장한다.
    3. 실제 데이터를 얻기 위해 articleService.show(id) 메서드를 호출해서 얻은 결과를 article 객체에 저장한다.
    4. assertEquals() 메서드를 이용해 비교한다. 실제 데이터와 예상 데이터의 값 null은 toString() 메서드를 호출할 수 없으므로 expected와 article을 사용한다.
    
    void show_실패_존재하지_않는_id_입력() {
    	// 1. 예상 데이터
    	Long id = -1L;
    	Article expected = null;
    	// 2. 실제 데이터
    	Article article = articleService.show(id);
    	// 3. 비교 및 검증
    	assertEquals(expected, article);
    }
    

    테스트를 해보면 잘 통과하는 것을 확인할 수 있다.

create() 테스트하기

  1. create() 메서드에 Generate → Test를 통해서 테스트 메서드를 만든다.

  2. 복사 붙여넣기로 하나 더 만들고 실패와 성공의 경우로 나눈다.

  3. 다양한 성공의 경우 중, title과 content만 있는 dto를 입력한 경우로 테스트해 본다.

    1. create_성공() 메서드 이름을 create_성공_title과_content만_있는_dto_입력()으로 수정
    2. 예상 데이터는 사용자가 새 게시물을 생성한 상황을 가정해 작성한다. id는 DB에서 자동으로 생성하므로 써 줄 필요가 없다. 그리고 expected에 예상 데이터의 id, title, content를 저장하는데 id는 자동으로 생성될 값인 4L로 써준다.
    3. 실제 데이터를 얻기 위해 articleService.create(dto) 메서드를 호출해 얻은 결과를 article 객체에 저장한다.
    4. assertEquals() 메서드를 이용해 비교한다.
    
    void create_성공_title과_content만_있는_dto_입력() {
    	// 1. 예상 데이터
    	String title = “라라라라”;
    	String content = “4444”;
    	ArticleForm dto = new ArticleForm(null, title, content);
    	Article expected = new Article(4L, title, content);
    	// 2. 실제 데이터
    	Article article = articleService.create(dto);
    	// 3. 비교 및 검증
    	assertEquals(expected.toString(), article.toString());
    }
    

    테스트를 돌려 보면 정상적으로 잘 통과한다.

  4. 게시글 생성에 실패하는 다양한 경우 중, id가 포함된 dto가 입력된 경우를 테스트 케이스로 작성한다.

    1. create_실패() 메서드의 이름을 create_실패_id가_포함된_dto_입력() 으로 수정한다.
    2. 예상 데이터는 새 게시물을 생성할 때 써줄 필요가 없는 id를 입력한 상황이라고 가정한다.
    3. assertEquals() 메서드를 이용한다. toString() 메서드를 호출할 수 없으므로 expected와 article을 사용한다.
    
    void create_실패_id가_포함된_dto_입력() {
    	// 1. 예상 데이터
    	Long id = 4L;
    	String title = “라라라라”;
    	String content = “4444”;
    	ArticleForm dto = new ArticleForm(id, title, content);
    	Article expected = null;
    	// 2. 실제 데이터
    	Article article = articleService.create(dto);
    	// 3. 비교 및 검증
    	assertEquals(expected, article);
    }
    

    테스트를 돌려 보면 잘 통과하는 것을 확인할 수 있다.

여러 테스트 케이스 한 번에 실행하기

ArticleServiceTest 클래스 자체를 실행하면 여러 테스트 케이스를 한 번에 돌릴 수 있다.

앞에서 하나씩 테스트를 했을 때느 모두 통과했지만, 이렇게 여러 테스트 케이스를 한 번에 돌리면 에러가 발생한다.

index() 메서드에서 실패하게 되는데, 예상 값에는 3개의 데이터가 들어가 있고 실제 값에는 4개의 데이터가 들어가 있다. 이러한 현상은 롤백하지 않아서 생긴 현상이다. index() 메서드 실행전 create_성공_title과_content만_있는_dto_입력() 메서드에서 데이터를 하나 추가했기 때문이다.

create_성공_title과_content만_있는_dto_입력() 메서드 위에 @Transactional 어노테이션을 추가하면 테스트가 끝난 후 변경된 데이터를 처음으로 되돌릴 수 있다.

데이터를 조회(Read)하는 테스트를 제외하고 데이터를 생성(Create), 수정(Update), 삭제(Delete)하는 테스트를 할 때는 반드시 해당 테스트를 트랜잭션으로 묶어 테스트가 종료한 후 원래대로 돌아갈 수 있도록 롤백 처리해 줘야 한다.

0개의 댓글