컴퓨터/spring

[toby's spring] 2장. 테스트

김크리 2021. 10. 24. 15:50

테스트

 스프링은 IoC/DI를 이용하여 객체지향 프로그래밍 언어의 근본과 가치를 개발자가 손쉽게 개발하고 사용할 수 있게 도와주는 기술이다. 동시에 스프링은 복잡한 엔터프라이즈 애플리에키션을 효과적으로 개발하기 위한 기술이기도 하다. 이러한 복잡한 애플리케이션을 개발하는 데 필요한 도구 하나는 객체지향 기술이다. 그리고 다른 하나의 도구로는 테스트이다.

웹을 통한 테스트 방법의 문제점

 보통 DAO 를 테스트할때 서비스 계층, MVC 프레젠테이션 계층까지 모두 포함 하여 입출력 코드로만들다. 이후 서버에 웹 애플리케이션을 배치한 뒤 등록을 하는 식의 테스트를 진행한다. 이를 위해서 만들어야 하는 것들이 너무 많다. 또한 문제가 발생한 위치를 찾는 수고도 필요하다. 그렇다면 테스트를 어떻게 만들면 이런 문제를 피할 수 있고, 효율적으로 테스트를 활용할 수 있을지 생각해보자.

작은 단위의 테스트(단위 테스트)

 단위 테스트는 개발자가 설계하고 만든 코드가 원래 의도한 대로 동작하는지 개발자 스스로 빨리 확인받기 위해서이다. 이때 확인의 대상과 조건이 간단하고 명확할수록 좋다.

자동수행 테스트 코드

 자동 수행 테스트 코드의 장점은 자주 반복할 수 있다는 점이다. 기능에대한 자동 수행 테스트 코드가 있다면 수정 후 빠르게 전체 테스트를 수행해서 수정 때문에 다른 기능에 문제가 발생하지 않는지 확인할 수 있다.

지속적인 개선과 점진적인 개발을 위한 테스트

JUnit

 JUnit은 자바의 표준 테스팅 프레임 워크라고 불릴 만큼 폭 넓게 사용되고 있다. 스프링 테스트 모듈도 JUnit을 사용하고있다. deleteAll, getCount 기능을 추가하고 테스트를 진행해보자.

...
    public void deleteAll() throws SQLException{
        Connection c = dataSource.getConnection();
        PreparedStatement ps = c.prepareStatement(
                "delete from user");
        ps.executeUpdate();
        ps.close();
        c.close();
    }

    public int getCount() throws SQLException {
        Connection c = dataSource.getConnection();
        PreparedStatement ps = c.prepareStatement("select count(*) from user");
        ResultSet rs = ps.executeQuery();
        rs.next();
        int count = rs.getInt(1);

        rs.close();
        ps.close();
        c.close();
        return count;
    }
 }
package com.example.demo.user.dao;

import com.example.demo.user.domain.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.internal.bytebuddy.matcher.ElementMatchers.is;

import java.sql.SQLException;

public class UserDaoTest {

    @Test
    public void addAndGet() throws SQLException {
        UserDao userDao = new DaoFactory().userDao();
        User user = new User();
        user.setId("sjkim1");
        user.setName("김수정1");
        user.setPassword("123");

        userDao.add(user);

        User user2 = userDao.get(user.getId());

        assertThat(user.getName()).isEqualTo(user2.getName());
        assertThat(user.getPassword()).isEqualTo(user2.getPassword());
    }
}

위와 같이 테스트를 진행하면 데이터베이스에 실제 데이터가 삽입된다. 단위테스트는 동일한 테스트 결과가 언제든 나오도록 해야한다. 그러니 데이터를 이전과 동일하도록 하여 문제가 안되도록 진행하는 것이 필요하다. 

테스트 주도 개발(Test Driven Development)

 만들고자 하는 기능의 내용을 담고 있으면서 만들어진 코드를 검증도 해줄 수 있도록 테스트 코드를 먼저 만들고, 테스트를 성공하게 해주는 코드를 작성하는 방식의 개발 방법을 테스트 주도 개발이라고 한다. 테스트 코드보다 먼저 작성한다고 하여 테스트 우선개발(Test First Development) 라고도 한다.

"실패한 테스트를 성공시키기 위한 목적이 아닌 코드는 만들지 않는다" 라는 것이 TDD의 기본 원칙이다.

픽스쳐(Fixture)

 테스트를 수행하는 데 필요한 정보나 오브젝트를 픽스처라고 한다. 일반적으로 픽스처는 여러 테스트에서 반복적으로 사용되기 때문에 @BeforeEach 등의 메소드를 이용해 생성해두면 편리하다.

DI와 테스트

 UserDao와 DB 커넥션 생성 클래스 사이에 datasource라는 인터페이스를 두었다. 그래서 Dao는 자신이 사용하는 오브젝트의 클래스가 무엇인지 알 필요가 없다. DI를 통해 외부에서 오브젝트를 주입받기 때문에 오브젝트 생성에 대한 부담을 지지 않아도 된다. 인터페이스를 두고 DI를 적용하는 이유에 대해 생각해보자

  • 소프트웨어 개발에서는 절대로 바뀌지 않는 것은없다. 클래스 대신 인터페이스를 사용하고, DI를 통해 주입받게 하는것은 추수 필요에 따라 수정에 들어가는 시간과 비용 부담을 줄여준다.
  • 클래스 구현 방식은 바뀌지 않더라도 인터페이스를 두고 DI를 적용하게 해두면 다른 차원의 서비스 기능을 도입할 수 있다.
  • 마지막으로, 효율적인 테스트를 위해서라도 DI를 적용해야한다. DI는 테스트가 작은 단위의 대상에 대해 독립적으로 만들어지고 실행되게 하는데 중요한 역할을 한다.