본문 바로가기
spring/토비의 스프링

토비의 스프링 Vol. 1 정리 - 2장 테스트

by Empering 2020. 4. 5.
반응형

토비의 스프링 Vol.1 정리 - 2

 

2장 테스트


테스트의 유용성

테스트란 예상하고 의도했던 대로 코드가 정확히 동작하는 지를 확인해서, 만튼 코드를 확신할 수 있게 해주는 작업

테스트의 결과가 원하는 대로 나오지 않는 경우 코드나 설계에 결함이 있음을 알 수 있다.

이를 통해 코드의 결함을 제거해가는 작업을 거치고, 테스트가 성공하면 모든 결함이 제거 됐다는 확신을 얻을 수 있다.

 

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

흔히 DAO 를 테스트 하기위해, MVC 모든 계층을 포함한 기능을 만든 뒤 웹화면을 통해 테스트하게된다.

하지만 DAO 테스트로서 웹을 통한 테스트는 단점이 많이 존재한다.

  1. DAO 뿐만 아니라 서비스, 컨트롤러, 뷰 등 모든 계층의 기능을 구현해야한다. > 테스트 참여 클래스가 많음
  2. 테스트 참여 클래스가 많은 경우 오류가 발생한 원인 파악이 어렵게 된다. > 확인 대상이 많음
  3. DAO 테스트를 하고 싶었지만 다른 클래스들이 테스트에 영향을준다.
  4. 테스트 하기 번거롭고, 오류의 원인을 빠르고 정확하게 대응하기 힘들다.

작은 단위의 테스트

테스트 하려는 대상이 명확하다면 그 대상에만 집중하게 테스트하는 것이 바람직하다.

관심의 분리라는 원리가 테스트에도 적용된다.

DAO 테스트를 관심의 분리를 통해 작은 단위로 분리 한경우

  1. 다른 MVC 계층 클래스, 서비스 등이 필요 없다.
  2. 서버에 배포할 필요가 없다.
  3. 간단히 IDE에서 실행 및 테스트 가능하다.
  4. 오류가 발생한 경우 DAO에 관한 오류이므로 빠르게 원인을 찾을 수 있다.

이렇게 관심의 분리를 통해 작은 단위로 테스트 하는 것을 단위 테스트(unit test) 라고 한다.

단위란 특정한 크기와 범위가 있는 것은 아니고, 클래스 또는 메소드 하나를 단위라 볼 수 도 있다. 충분히 하나의 관심에 집중해서 효율적으로 테스트할 만한 범위를 단위라고 보면 된다.

 

자동수행 테스트 코드

웹을 통한 테스트를 하기위해서는 테스트 하는 사람이 많은 작업이 들어가야한다.

예를 들어 특정 URL을 접속하고, 입력폼에 맞게 데이터를 입력하고 실행해보는 식이다.

하지만 테스트 클래스를 작성해 두면, 메서드를 실행하는 것만으로 테스트 과정이 진행 된다.

자동으로 수행되는 테스트의 장점은 자주 반복할 수 있다는 것이다.

번거로운 작업이 없고 테스트를 빠르게 실행할 수 있기 때문에 언제든 코드를 수정한 다음 테스트 해볼 수 있다.

개발이 완료되고 특정 코드의 변경 영향이 전체 애플리케이션에 문제를 일으 키는지 확신이 없는경우, 만들어둔 테스트들을 실행해 다른 기능에 문제가 발생하는지 빨리 확인하고, 확신을 얻을 수 있다.

 

자동수행 테스트 코드의 문제점

- 수동 확인 작업의 번거로움

테스트 수행결과가 명확하지 않고, 콘솔의 값을 직접 개발자가 확인해서 비교해봐야한다.

테스트의 결과가 성공/실패로 리턴되어 별도의 비교작업이 없어야 한다.

 

- 실행 작업의 번거로움

자동으로 수행하는 메소드를 매번 실행하는 것 또한 번거로운 작업이다.

테스트 클래스가 많아 지는 경우 이를 종합하게 전체 기능을 모두 테스트 한 결과를 종합하는 것 또한 큰 작업이다.

 

main을 이용한 테스트와 jUnit 테스트코드 비교

<main을 이용한 테스트>

package com.empering.springdemo;

import com.empering.springdemo.user.dao.DaoFactory;
import com.empering.springdemo.user.dao.UserDao;
import com.empering.springdemo.user.domain.User;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.GenericApplicationContext;

import java.sql.SQLException;

public class UserDaoTest {
    public static void main(String[] args) throws SQLException {
        GenericApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);

        UserDao dao = context.getBean("userDao", UserDao.class);

        User user = new User("empering", "아쮸네하우스", "1234");
        dao.add(user);

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

        if (!user.getName().equals(user2.getName())) {
            System.out.println("테스트 실패 (name)");
        } else if (!user.getPassword().equals(user2.getPassword())) {
            System.out.println("테스트 실패 (password)");
        } else {
            System.out.println("테스트 성공");
        }
    }
}

<jUnit을 이용한 테스트>

package com.empering.springdemo;

import com.empering.springdemo.user.dao.DaoFactory;
import com.empering.springdemo.user.dao.UserDao;
import com.empering.springdemo.user.domain.User;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.GenericApplicationContext;

import java.sql.SQLException;

import static org.assertj.core.api.Assertions.assertThat;

public class UserDaoJUnitTest {
    @Test
    @DisplayName("사용자 등록, 조회테스트")
    public void addAndGet() throws SQLException {
        GenericApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);

        UserDao dao = context.getBean("userDao", UserDao.class);

        User user = new User("empering", "아쮸네하우스", "1234");
        dao.add(user);

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

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

 

포괄적인 테스트

간단한 기능은 굳이 다양한 테스트를 하지 않고 코드만 살펴봐도 문제가 생기지 않을 것라 자신 할 수도있다.

하지만 포괄적인 테스트를 만들어 두는 편이 훨씬 안전하고 유용하다.

특히 평소에는 정상적으로 잘 작동하는 것처럼 보이지만, 특정 상황이 발생하면 잘못 동작하는 코드를 만든 경우

테스트도 안해봤다면 나중에 문제가 발생했을 때 원인을 찾기 힘들어서 고생할지도 모른다.

종종 단순하고 간단한 테스트가 치명적인 실수를 필할 수 있게 해주기도 한다.

 

개발자가 테스트를 직접 만들 때 자주하는 실수 - 성공하는 테스트만 골라서 만드는 경우

개발자는 머릿속으로 정상적으로 작동하는 케이스를 생각하며 코드를 만드는 경우가 일반적이다.

그래서 테스트를 작성할 때도 문제가 될만한 상황이나, 입력 값 등은 피해서 코드를 만드는 습성이 있다.

자동테스트 뿐만 아니라 UI 수동테스트에서도 종종 발생한다. > "내 PC에서는 잘 되는데..."

 

테스트를 작성 할 때 부정적인 케이스를 먼저 만드는 습관을 들이는 게 좋다.

 

테스트가 이끄는 개발

테스트할 코드도 안 만들어놓고 테스트 코드부터 만드는 것은 좀 이상하다고 생각이 되지만,

이런 순서를 따라 새발을 진행하는 구체적인 개발 전략이 실제로 존재한다.

그리고 많은 전문적인 개발자가 이런 개발 방법을 적극적으로 사용하고 있다.

 

기능설계를 위한 테스트

...

 

테스트 주도 개발 (TDD)

만들고자 하는 기능의 내용을 담고 있으면서, 만들어진 코드를 검증도 해줄 수 있도록 테스트 코드를 먼저 만들고, 테스트를 성공하게 해주는 코드를 작성하는 방식의 개발 방법

개발자가 테스트를 만들어가며 개발하는 방법이 주는 장점을 극대화한 방법이다.

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

 

TDD는 테스트를 먼저 만들고 그 테스트가 성공하도록 하는 코드만 만드는 식으로 진행하기 때문에

  1. 테스트를 빼먹지 않고 꼼꼼하게 만들어낼 수 있다.
  2. 테스트를 작성하는 시간과 애플리케이션 코드를 작성하는 시간의 간격이 짧아진다.
  3. 코드를 작성하면서 바로바로 테스트를 실행해볼 수 있다.
  4. 코드에대한 피드백을 매우 빠르게 받을 수 있게 된다.
  5. 매번 테스트가 성공하는 것을 보면서 작성한 코드에 대한 확신을 가질 수 있다.

TDD에서는 테스트를 작성하고, 성공시키는 코드를 만드는 작업주기를 가능한 짧게 가져가도록 권장한다.

 

사실 모든 개발자는 이미 테스트가 개발을 이끌어가는 방식으로 개발을 하고 있다.

새로운 기능을 만들때, 개발자 머릿속에서는 "조건-행위-결과"의 형태로 기능을 먼저 정리 할 것이다.

그리고 코드를 작성하는 동안에도 시뮬레이션을 계속 하게 된다.

이렇게 머릿속에서 진행되는 테스트는 제약이 심하고, 오류가 많고, 나중에 다시 반복하기 힘들다는 점이 있다.

그래서 머릿속에서 진행하는 작업을 실제 코드로 끄집어 내놓으면 TDD가 된다.

 

스프링 테스트 적용

 

<스프링 컨텍스트를 적용한 테스트>

package com.empering.springdemo;

import com.empering.springdemo.user.dao.DaoFactory;
import com.empering.springdemo.user.dao.UserDao;
import com.empering.springdemo.user.domain.User;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.sql.SQLException;

import static org.assertj.core.api.Assertions.assertThat;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = DaoFactory.class)
public class UserDaoJUnitTest {
    @Autowired
    UserDao dao;

    @Test
    @DisplayName("사용자 등록, 조회테스트")
    public void addAndGet() throws SQLException {
        User user = new User("empering", "아쮸네하우스", "1234");
        dao.add(user);

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

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

 

토비의 스프링 3.1 Vol. 1: 스프링의 이해와 원리, 에이콘출판 토비의 스프링 3.1 Vol. 2: 스프링의 기술과, 에이콘출판 토비의 스프링 3.1 세트:스프링의 이해와 원리 + 스프링의 기술과, 에이콘출판

 

토비의 스프링 3.1 세트:스프링의 이해와 원리 + 스프링의 기술과

COUPANG

www.coupang.com

“파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음"

반응형

댓글