과정을 즐기자

테스트 본문

Spring

테스트

320Hwany 2023. 3. 26. 14:00

이 글은 토비의 스프링을 읽고 정리한 글입니다.  

 

스프링이 개발자에게 제공하는 가장 중요한 가치는 객체지향과 테스트입니다.

이번 글에서는 테스트에 대해서 정리해보려고 합니다.  

먼저 테스트를 작성하는 이유를 킹뽀대님 블로그에서 발췌해서 가져왔습니다.  

 

'테스트는 우리가 작성한 코드가 주어진 요구사항을 해결할 수 있는지 회귀를 통하여 검증할 수 있도록 돕고, 주어진 요구사항을 테스트하므로 작성된 테스트가 문서로서 작동할 수 있으며, 의미 있는 단위의 테스트를 고민함으로써 좋은 디자인을 만드는 데에도 도움을 줍니다. 또한 테스트로 보호된 코드는 나와 동료들에게 코드의 변경을 거침없이 시도할 수 있도록 안정감과 자신감을 줍니다.'

 

그렇다면 좋은 테스트코드란 무엇일까요?

 

1. 테스트는 자동화되어야 하고 빠르게 실행할 수 있어야 합니다.

2. 테스트 결과는 일관성이 있어야 합니다. 코드의 변경 없이 환경이나 테스트 실행 순서에 따라서

    결과가 달라지면 안됩니다.

3. 코드 작성과 테스트 수행의 간격이 짧을수록 효과적입니다.

4. 반복적으로 테스트할 수 있어야합니다

5. 테스트하기 쉬운 코드가 좋은 테스트 코드입니다.

 

테스트 주도 개발

TDD(Test Driven Development)는 아예 테스트를 먼저 만들고 그 테스트가 성공하도록 하는

코드만 만드는 식으로 진행하기 때문에 요구사항을 빼먹지 않고 꼼꼼하게 만들어낼 수 있습니다.

TDD에서는 테스트를 작성하고 이를 성공시키는 코드를 만드는 작업의 주기를 가능한 한

짧게 가져가도록 권장한다.  

 

개발자의 머릿속에서는 '이런 조건하에서 이런 작업을 하면 이런 결과가 나올것이다' 라는 식으로

기능을 먼저 정리하게 될 것입니다. 이러한 과정을 머릿속에서 복잡하게 진행하던 작업을

실제 코드로 끄집어 내놓으면 이게 바로 TDD가 됩니다.

 

JUnit과 스프링 테스트 적용

JUnit은 하나의 클래스 안에 여러 개의 테스트 메소드가 들어가는 것을 허용합니다. 

@Test가 붙어 있고 리턴 값이 void 형이고 파라미터가 없다는 조건을 지켜야 합니다.  

 

JUnit은 각 테스트 메소드를 실행할 때마다 테스트 클래스의 오브젝트를 새로 만듭니다.  

JUnit 개발자가 각 테스트가 서로 영향을 주지 않고 독립적으로 실행됨을 확실히 보장해주기

위해 매번 새로운 오브젝트를 만들게 했습니다. 

테스트는 가능한 한 독립적으로 매번 새로운 오브젝트를 만들어서 사용하는 것이 원칙입니다. 

하지만 애플리케이션 컨텍스트처럼 생성에 많은 시간과 자원이 소모되는 경우에는 

테스트 전체가 공유하는 오브젝트를 만들기도 합니다.

 

애플리케이션 컨텍스트가 관리하는 빈은 싱글톤으로 만들어졌습니다. 싱글톤으로 관리되는 빈이

인스턴스 변수를 가져서 공유하지 않는 이상 (동시성 문제 발생)

어떤 메소드를 호출한다고 해서 빈의 상태가 변경되지 않습니다. 

따라서 애플리케이션 컨텍스트는 한번만 만들고 여러 테스트가 공유해서 사용할 수 있습니다.

 

Autowired

일반적으로는 주입을 위해서는 생성자를 이용했습니다.

필드 주입 방식은 final로 선언할 수도 없고 순환참조 문제가 발생하는 등 여러 문제가 있어 안티패턴입니다.  

하지만 테스트 코드에서는 필드 주입을 사용하는 경우가 많은데 객체를 생성하는 일을 하지 않아서 

편리하게 사용할 수 있기 때문입니다.

 

 

테스트 코드 작성

 

UserDaoTest

@SpringBootTest(classes = DaoFactory.class)
class UserDaoTest {

    @Autowired
    private UserDao dao;
    private User user1;
    private User user2;
    private User user3;

    @BeforeEach
    public void setUp() {
        this.user1 = new User("hwany1", "정유환1", "1234");
        this.user2 = new User("hwany2", "정유환2", "1234");
        this.user3 = new User("hwany3", "정유환3", "1234");
    }

    @Test
    void addAndGet() throws SQLException {
        dao.deleteAll();
        assertThat(dao.getCount()).isEqualTo(0);

        dao.add(user1);
        dao.add(user2);
        assertThat(dao.getCount()).isEqualTo(2);

        User userGet1 = dao.get(user1.getId());
        assertThat(userGet1.getName()).isEqualTo(user1.getName());
        assertThat(userGet1.getPassword()).isEqualTo(user1.getPassword());

        User userGet2 = dao.get(user2.getId());
        assertThat(userGet2.getName()).isEqualTo(user2.getName());
        assertThat(userGet2.getPassword()).isEqualTo(user2.getPassword());
    }

    @Test
    void getUserFailure() throws SQLException {
        dao.deleteAll();
        assertThat(dao.getCount()).isEqualTo(0);

        assertThrows(EmptyResultDataAccessException.class,
                () -> dao.get("unknown_id"));
    }

    @Test
    void count() throws SQLException {
        dao.deleteAll();
        assertThat(dao.getCount()).isEqualTo(0);

        dao.add(user1);
        assertThat(dao.getCount()).isEqualTo(1);

        dao.add(user2);
        assertThat(dao.getCount()).isEqualTo(2);

        dao.add(user3);
        assertThat(dao.getCount()).isEqualTo(3);
    }
}

 

dao, user1, user2, user3 모두 인스턴스 변수로 선언했지만 dao는 @Autowired를 붙여줬는데

스프링 컨테이너가 관리하는 오브젝트라는 점을 이용하기 위해서 입니다. 

각 메소드 실행마다 이 객체들은 같은 객체일까요?

 

addAndGet

 

UserDaoTest = com.tobyspring.user.UserDaoTest@3b1dc579

dao = com.tobyspring.user.UserDao@5ec5ea63

user1 = com.tobyspring.user.User@5a49af50

user2 = com.tobyspring.user.User@3b1dc579
user3 = com.tobyspring.user.User@793d163b

 

Count

 

UserDaoTest = com.tobyspring.user.UserDaoTest@25d0cb3a

dao = com.tobyspring.user.UserDao@5ec5ea63

user1 = com.tobyspring.user.User@767599a7
user2 = com.tobyspring.user.User@5f5effb0
user3 = com.tobyspring.user.User@25d0cb3a

 

getUserFailure

 

UserDaoTest = com.tobyspring.user.UserDaoTest@5ec5ea63

dao = com.tobyspring.user.UserDao@5ec5ea63

user1 = com.tobyspring.user.User@4190bc8a
user2 = com.tobyspring.user.User@47d023b7
user3 = com.tobyspring.user.User@5c83ae01

 

위에서 설명한 것처럼 스프링 컨테이너가 관리하는 빈은 싱글톤이므로 같은 객체이고

테스트 클래스를 포함한 다른 오브젝트들은 모두 테스트 메소드 실행마다 새로 생성된다는 것을 알 수 있습니다.

 

하지만 항상 스프링 컨테이너 없이 테스트할 수 있는 방법을 가장 우선적으로 고려해야 합니다.   

예를 들어 domain 계층에 있는 비즈니스 로직을 테스트한다고 할 때 스프링 컨테이너를 

띄우지 않고 오브젝트를 그냥 생성하여 테스트할 수 있습니다.  

 

출처 : 토비의 스프링 3.1 Vol.1 스프링의 이해와 원리

 

 

테스트를 작성하는 방법

이 글은 .NET Core 및.NET 표준을 사용하는 단위 테스트 모범 사례라는 글에 영감을 받았습니다. 글에서 제시하는 맥락에 어느정도 동의하며 이 중 자바 관점으로의 전환이 필요한 내용과 자바 개발

blog.kingbbode.com

 

'Spring' 카테고리의 다른 글

예외  (0) 2023.04.03
템플릿  (0) 2023.04.02
오브젝트와 의존관계  (0) 2023.03.25
요청과 응답에서 엔티티를 직접 사용하면 안되는 이유  (0) 2023.02.23
자체 로그인, 소셜 로그인 같이 사용하기  (0) 2023.02.11