본문 바로가기
  • 개발공부 및 일상적인 내용을 작성하는 블로그 입니다.
Spring basic

스프링 입문 : 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 회원 레포지토리 테스트 케이스

by 방구석 대학생 2021. 11. 3.

"인프런의 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의를 듣고 작성한 글 입니다."

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8

 

[무료] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링 웹 애플리케이션 개발 전반을 빠르게 학습할 수 있습니다., 스프링 학습 첫 길잡이! 개발 공부의 길을 잃지 않도록 도와드립니다. 📣 확인해주세

www.inflearn.com

 

테스트 케이스 작성을 통한 검사(코드를 코드로 검증하는 것) - 내가 만든 클래스 들이 내가 원하는 대로 동작하는지 검증할 수 있는 방법

개발한 기능을 실행해서 테스트 할 때 자바의 main 메소드를 통해서 실행하거나 웹 어플리케이션의 컨트롤러를 통해서 작성한 기능들이 정상적으로 잘 동작하는지 확인해 볼 수 있다.

하지만 이러한 방법은 준비하고 실행하는데 오래 걸릴뿐더러, 반복 실행하기 어렵고 여러 테스트를 한번에 실행하기 어렵다는 단점이 있다.

(마침 이번에 pro.gg 프로젝트를 수행하면서 작성한 각 기능들이 정상적으로 잘 동작하는지 확인 해 볼때, 테스트 코드를 작성하여 기능을 검증하는 방법을 잘 몰라서 일일히 웹 어플리케이션을 다시 재실행 시키고 동작 하나하나를 실행시켜 가며 검증하느라 꽤 피곤하긴 했었다.)

자바는 이러한 문제를 JUnit 이라는 프레임워크로 테스트를 실행해서 해결하는 기능을 제공해준다.

 

다음과 같이 코드를 작성해보자.(src/test/java 하위 폴더에 테스트 파일을 생성한다.)

- MemoryMemberRepositoryTest.java

import static org.assertj.core.api.Assertions.*; // static 경로 추가
// 굳이 public 예약어를 사용하지 않아도 된다, 굳이 다른데서 불러와서 사용할 필요가 없기 때문
class MemoryMemberRepositoryTest {

    MemoryMemberRepository repository = new MemoryMemberRepository();

    @Test
    public void save(){
        Member member = new Member();
        member.setName("spring");

        repository.save(member);
        Member result = repository.findById(member.getId()).get();// Optional 이 반환 자료형일 경우 get() 메소드를 사용해 줄 수 있다.
		// org.junit.jupiter.api
        Assertions.assertEquals(result, member); // 메소드 내부에서 선언해서 만든 member 변수와 그 변수를 저장한 이후 다시 호출해온 result 변수가 서로 같은지 확인하는 테스트 코드
        // Assertions.assertThat(member).isEqualTo(result); -> org.assertj.core.api.Assertions -> 위의 코드와 똑같은 기능을 수행한다.
    }

    @Test
    public void findByName(){
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member(); // shift + f6 단축키 : 중복되는 이름의 변수가 있을 경우 해당 변수가 사용된 곳 모두 한번에 변수의 이름을 바꿔줄 수 있다.
        member2.setName("spring2");
        repository.save(member2);

        //findByName 메소드가 잘 동작하는지 확인한다.
        Member result = repository.findByName("spring1").get();
        assertThat(result).isEqualTo(member1);
    }
}

테스트 케이스를 사용할 경우 장점은 여러가지로 쪼개어 놓은 테스트 케이스를 한번에 다 같이 돌려볼 수 있다는 것이다.

class MemoryMemberRepository 옆에 있는 실행 버튼을 클릭해보면, 각 메소드 바로 옆에 있는 실행 버튼을 통해 메소드 하나하나를 테스트 해보는 것이 아닌 클래스 내부에 작성되어 있는 모든 메소드에 대해 한번에 테스트를 진행해 볼 수 있다.

 

다음으로 아래와 같음 테스트 코드를 추가해보자.

- MemoryMemberRepositoryTest.java

@Test
public void findAll(){
    Member member1 = new Member();
    member1.setName("spring1");
    repository.save(member1);

    Member member2 = new Member();
    member2.setName("spring2");
    repository.save(member2);
    
    List<Member> result = repository.findAll();
    assertThat(result.size()).isEqualTo(2);
}

 

위와 같이 메소드를 작성해 두고 findAll() 메소드 하나만 단독으로 실행시키면 정상적으로 테스트가 잘 통과되는 것을 확인해 볼 수 있다.

그런데 여기서 전체 메소드를 다시 한번 다 같이 실행해보면 findByName 메소드 테스트에서 에러가 발생하는 것을 확인해 볼 수 있다.

 

로그를 살펴보면 findAll() 메소드가 먼저 테스트 되는 것을 확인할 수 있는데(강의에서도 그렇고 실습 때도 그렇고 에러가 발생한 메소드와 먼저 실행되버린 메소드가 동일했다.) 여기서 우리가 알 수 있는 사실은 전체 통합 테스트에서 각각 메소드들의 테스트 순서는 작성되어 있는 순서대로 보장이 되지 않는다는 것이다.

findAll() 메소드는 작성한 테스트 케이스 중에서도 가장 나중에 작성되었음에도 불구하고 전체 통합 테스트에서 가장 먼저 테스트가 수행된 것을 보면 알 수 있을 것이다.

즉, 모든 테스트는 순서와는 상관없이 전부 다 따로 동작을 수행하도록 설계해야 한다는 것이다.(작성한 순서에 의존적이게 테스트를 설계해서는 안된다.)

 

그런데 이번 실습의 경우 findAll() 메소드의 테스트가 가장 먼저 수행되면서 spring1, spring2 가 이미 저장이 되어 있는 상태가 되었는데, 이로 인해 findByName 메소드에서 해당 메소드 내부에서 변수를 선언하고 저장한 spring1 이 아닌 다른 spring1(findByName 이 아닌 findAll 에서 저장한 spring1) 데이터가 나와버린 것이다.

 

이를 해결하려면 어떻게 해야 하느냐, 테스트 하나가 끝나면 메모리에 저장되어 있을 데이터들을 clear 해주어야 한다.

테스트가 끝날때마다 메모리를 깔끔하게 지워주는 코드를 작성하자.

- MemoryMemberRepositoryTest.java

@AfterEach // 각종 테스트 케이스 메소드들의 수행이 끝날때 마다 이 메소드를 수행하라는 뜻의 어노테이션(call-back 메소드)
public void aftereach(){
    repository.clearStore();
}

- MemoryMemberRepository.java

public void clearStore(){
    store.clear();
}

이와 같이 메모리를 초기화 시켜주는 코드를 작성한 이후 다시 전체 통합 테스트를 수행해보면 이전과 달리 모든 테스트 들이 잘 통과되는 것을 확인해 볼 수 있다.

- 각각의 테스트들은 서로 의존관계 없이 독립적으로 존재하게끔 설계되어야 한다, 그러므로 하나의 테스트가 수행될 때마다 공용 데이터와 같은 것들은 깔끔하게 비워주는 것이 좋다.

 

현재까지 강의에서는 먼저 MemoryMemberRepository 를 개발한 다음 테스트를 작성했다.

그런데 이것을 반대로 뒤집어서 테스트 코드를 먼저 작성한 다음 MemoryMemberRepository 의 내용을 작성해 줄 수도 있다.

즉, 테스트 코드를 먼저 작성하여 내가 개발하고자 하는 기능들의 틀을 먼저 만들어 준 후 제대로된 작품을 만드는 방식으로 개발을 진행할 수도 있다는 뜻이다.

이를 두고 테스트 주도 개발(TDD) 이라고 한다. (테스트 코드를 먼저 만든 다음, 실제 구현 코드를 작성해서 기능들이 정상적으로 잘 수행이 되는지를 확인하는 것)

 

혼자서 개발을 한다고 가정했을때 라면 몰라도 현업에서 여러명의 다른 개발자들과 함께 협업하여 프로젝트가 진행되고, 프로젝트 소스 코드가 1만줄 단위로 넘어가기 시작하면 테스트 코드 없이 개발을 한다는 것 자체가 불가능해진다.

그렇기에 테스트 코드와 관련된것은 기회가 된다면 꼭 깊이 있게 공부를 해보는 게 좋을 것이다.