반응형

1. 개요

  - 토비의 스프링 4주차 스터디

  - 2장 테스트 (145p ~ 182p)

 


2. 테스트

 - 테스트란 개발자가 의도했던 대로 코드가 정확히 동작하는지를 확인하고, 코드에 대한 확신을 얻기 위한 작업이다. 보다 정확한 테스트를 위해서는 하나의 테스트에 여러 책임을 갖게 하지 않도록 관심사를 분리해야한다. 한꺼번에 많은 기능을 테스트하면 그 과정도 복잡해지고, 오류가 발생했을 때 원인을 찾기 힘들기 때문이다. 이렇게 한 가지 관심에 집중할 수 있게 작은 단위로 만드는 테스트를 단위 테스트(Unit Test)라고 한다.

 

2.1. UserDaoTest의 문제점

public class UserDaoTest {

    public static void main(String[] args) throws SQLException {
       
        ApplicationContext applicationContext = new GenericXmlApplicationContext("applicationContext.xml");
        UserDao userDao = applicationContext.getBean("userDao", UserDao.class);

        User user = new User();
        user.setId("test");
        user.setPassword("1234");
        user.setName("테스터");
        userDao.add(user);

        User findUser = userDao.get("test");
        
        System.out.println(findUser.getName);
        
    }
}

 

1) 수동 확인 작업의 번거로움

 - 콘솔에 표시되는 findUser.getName값이 의도한 값으로 출력됐는지를  개발자가 확인해야하는 수동적인 작업이 불가피하다. 만약 검증해야할 클래스가 많아진다면, 수동 확인 작업의 양도 증가할 것이다.

 

2) 실행 작업의 번거로움

 - 테스트할 클래스를 개발자가 일일이 실행해야 한다. 마찬가지로 검증해야할 클래스가 많아진다면, 실행 작업의 양도 증가할 것이다.

 

이 중 수동 확인 작업의 번거로움을 줄이기 위한 방법이 있다. 다음과 같이 콘솔에 DB 조회 결과를 뿌려주는게 아니라, 테스트에 대한 결과를 뿌려주는 방법이다.

public class UserDaoTest {

    public static void main(String[] args) throws SQLException {
        ApplicationContext applicationContext = new GenericXmlApplicationContext("applicationContext.xml");
        UserDao userDao = applicationContext.getBean("userDao", UserDao.class);

        User user = new User();
        user.setId("test");
        user.setPassword("1234");
        user.setName("테스터1");
        userDao.add(user);

        User findUser = userDao.get("test");
        if(!findUser.getName().equals(user.getName())){
            System.out.print("테스트 실패 (name)");
        }else if(!findUser.getPassword().equals(user.getPassword())){
            System.out.println("테스트 실패 (password)");
        }else{
            System.out.println("조회 테스트 성공");
        }

    }
}

 

하지만 좀 더 편리하게 테스트를 수행하고 결과를 확인하려면 단순 main 메서드로는 한계가 있다. 테스트 코드를 구현하는 일정한 패턴도 없고, 그 결과를 종합적으로 확인하기도 힘들다.

 그래서 일정한 패턴을 가진 테스트를 만들 수 있고, 많은 테스트를 간단히 실행시킬 수 있으며, 테스트 결과를 종합해 볼 수 있고, 실패 지점을 빨리 찾을 수 있는 자바 테스트 프레임워크인 JUnit이 등장했다.

 

2.2. JUnit 전환 

 예제에서는 JUnit4를 사용하였으나, 필자의 경우 JUnit5를 사용하였다. 문법만 다르고 테스트 코드를 작성하는 매커니즘은 동일하기 때문에 어렵지 않게 실습할 수 있었다. 중요한 내용들은 다시한번 정리하며 기록해보았다.

 

1) 테스트 코드는 순서를 보장하지 않는다.

 - 현재 addAndGet(), count(), getUserWithInvalidId() 순서이나, 실제 테스트 코드가 실행되는 순서는 실행때마다 뒤바뀔 수 있다. 때문에 테스트 메서드간 영향을 받지 않도록 구현해야 한다.

 

2) 부정적인 테스트 케이스도 만들어야 한다.

 -  코드에서 발생할 수 있는 다양한 상황입력 값을 고려하는 테스트를 만들기 위해서는 부정적인 테스트 케이스(네거티브 테스트)도 만들어야 한다. 정상적인 상황보다 예외적인 상황에서 버그가 발생할 확률이 높기 때문이다.

 

유저를 등록하고 조회하는 addAndGet(), 카운트를 조회하는 count(), 유효하지 않는 아이디를 조회했을 때 예외를 떨구는 네거티브 케이스인 getUserWithInvalidId() 를 작성하였다.

 이 책에서는 테스트의 개념 설명이 주 목적이기에 Mock이나 Stubbing과 같은 개념이 사용되지 않아 관심사를 더 분리하진 않은 것 같다.

class UserDaoTest {

    private UserDao userDao;
    private User user1;
    private User user2;
    private User user3;

    @BeforeEach
    void setUp(){
        ApplicationContext applicationContext = new GenericXmlApplicationContext("applicationContext.xml");
        userDao = applicationContext.getBean("userDao", UserDao.class);
        user1 = new User("test1","1234","테스터1");
        user2 = new User("test2","12345","테스터2");
        user3 = new User("test3","123456","테스터3");
    }
    @Test
    public void addAndGet() throws SQLException{

        userDao.deleteAll();
        assertThat(userDao.getCount()).isEqualTo(0);

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

        User findUser1 = userDao.get("test1");
        assertThat(findUser1.getName()).isEqualTo(user1.getName());
        assertThat(findUser1.getPassword()).isEqualTo(user1.getPassword());

        User findUser2 = userDao.get("test2");
        assertThat(findUser2.getName()).isEqualTo(user2.getName());
        assertThat(findUser2.getPassword()).isEqualTo(user2.getPassword());
    }

    @Test
    public void count() throws SQLException{

        userDao.deleteAll();
        assertThat(userDao.getCount()).isEqualTo(0);

        userDao.add(user1);
        userDao.add(user2);
        userDao.add(user3);

        assertThat(userDao.getCount()).isEqualTo(3);
    }

    @Test
    public void getUserWithInvalidId() throws SQLException {

        userDao.deleteAll();
        assertThat(userDao.getCount()).isEqualTo(0);

        assertThatThrownBy(() -> userDao.get("test1")).isInstanceOf(EmptyResultDataAccessException.class);
    }
}

 

 


3. TDD

 - TDD는 Test Driven Development의 약자로 테스트 주도개발을 말한다. 쉽게 말하면 테스트 코드를 먼저 개발하고, 어플리케이션 코드를 작성하는 것이다.

 

3.1. TDD의 장점

1) 자연스러운 객체 지향 설계.

  - 테스트 코드 작성 시 관심사를 분리하여 작은 단위로 모듈화시켜야 하는데, 이 과정에서 다른 기능과의 결합도를 낮추고, 한 기능에만 집중하도록 응집도를 높이는 코드를 만들기 위해 노력하게 된다. 즉, 자연스러운 객체 지향적인 설계를 유도한다.

 

2) 유지보수 용이.

 - 유지보수 도중 코드 수정이 발생할 경우 필연적으로 테스트를 해야하는데, 이를 테스트 코드 실행만으로 확인 가능하다. 또한, 코드를 수정했을 때 파생될 수 있는 다른 부분의 에러나 사이드 이펙트을 바로 체크할 수 있어 유지보수에 용이하다.

 

3) 테스트 문서의 기능.

 - 테스트 코드의 결과만으로도 테스트 문서를 대체할 수 있으며, 기존 통합 테스트보다 더 신뢰성 있는 정보가 될 수 있다.

 

3.2. TDD의 단점

1) 생산성 저하

 - 어플리케이션 코드와 테스트코드를 모두 작성해야 한다면, 소요되는 시간도 많아진다. 이는 곧 생산성 저하로 이어진다. (현실적으로 테스트 코드를 고려한 개발 기간도 주어지지 않는다.)

 


4. 회고

 올해 초 TDD 멘토링 프로그램에 참여하였기에 전반적인 내용들을 쉽게 이해할 수 있었다. 그때 배웠던 내용들을 생각하며 한번 더 정리하게 된 좋은 시간이었다.

반응형

+ Recent posts