2.1 테스트

스프링과 테스트

스프링이 개발자에게 제공하는 가장 중요한 가치는 객체지향테스트이다. 현대의 앱은 계속 변하고 복잡해져간다.

변화에 대응하는 첫번째 전략은 IoC와 DI

스프링의 핵심인 IoCDI는 오브젝트의 설계, 생성, 관계, 사용에 관한 기술이다. 객체지향 프로그래밍 언어의 근본과 가치를 개발자가 손쉽게 적용하고 사용할 수 있게 도와준다. 객체지향을 이용하여 복잡한 엔터프라이즈 앱을 효과적으로 개발하도록 도와준다.

변화에 대응하는 두번째 전략은 테스트

테스트는 만들어진 코드를 확신할 수 있게 해주고, 변화에 유연하게 대처할 수 있는 자신감을 준다. 또, 테스트는 스프링을 학습하는데 있어서 가장 효과적인 방법 중 하나이다. 스프링의 다양한 기술을 활용하는 방법을 이해하고 검증하는데 효과적으로 사용될 수 있다.

테스트의 유용성

테스트는 코드를 수정한 뒤에도 처음과 동일한 기능을 수행함을 보장해준다. 테스트란 결국 내가 예상하고 의도했던 대로 코드가 정확히 동작하는지 확인하여 만든 코드를 확신할 수 있게 해주는 작업이다. 또, 테스트의 결과가 원하는대로 나오지 않는 경우에는 코드나 설계에 결함이 있음을 알 수 있다.

이전에 작성한 UserDaoTest의 특징

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

        User user = new User();
        user.setId("12341234");
        user.setName("제이크22522");
        user.setPassword("jakejake");

        userDao.add(user);

        System.out.println(user.getId() + " register succeeded");

        User user2 = userDao.get(user.getId());
        System.out.println(user2.getName());
        System.out.println(user2.getPassword());

        System.out.println(user2.getId() + " query succeeded");
    }
}
  • main() 메소드를 이용했다.

  • 테스트 대상인 UserDao의 오브젝트를 가져와 메소드를 호출한다.

  • 테스트에 사용할 입력 값(User 오브젝트)을 직접 코드에서 만들어 넣어준다.

  • 테스트의 결과를 콘솔에 출력한다.

  • 콘솔에 성공 메세지가 출력되면 성공한 것이다.

가장 돋보이는 것은 main() 메소드를 사용했다는 것과 UserDao를 직접 호출해서 사용한다는 것이다.

웹을 통한 DAO 테스트의 문제

일반적으로 서버에서 작성한 로직의 테스트를 작성하지 않고, 웹 화면에서 직접 테스트하는 경우가 꽤 많다.

웹화면을 통해 값을 입력하고, 기능을 수행하고, 결과를 확인하는 방법은 가장 흔히 쓰이지만 단점이 너무 많다.

  • 모든 레이어의 기능을 다 만들고 나서야 테스트가 가능하다는 점

  • 어디에서 문제가 발생했는지 찾아내야 한다는 점

위와 같은 문제들이 발생하는 이유는 테스트를 수행하는 데 참여하는 클래스와 코드가 너무 많기 때문이다.

실패의 원인은 DB 연결 방법, DAO 코드, JDBC API, SQL 문법, 파라미터 바인딩 등 많은 관심사에서 일어날 수 있다. 또한, 정작 테스트 대상인 DAO에서 문제가 일어난 것이 아니라, 테스트하려고 만든 코드에서 문제가 발생했던 것일 수도 있고, 웹 계층에서 DAO를 바르게 호출하지 못했을 수도 있고, MVC 컨트롤러에서 원하는 컨트롤러가 실행되지 않았을 수도 있다. 또, 테스트를 성공했지만 뷰에서 마치 실패처럼 보이게 만들어서 오류가 난 것처럼 보이기만 할 수도 있다.

결국 모든 계층과 심지어 서버 설정 상태까지 테스트에 영향을 주기 때문에, 오류가 있을 때 빠르고 정확하게 대응하기가 힘들다.

작은 단위의 테스트

테스트하고자 하는 대상이 명확하다면, 그 대상에만 집중해서 테스트하는 것이 바람직하다. 테스트는 가능하면 작은 단위로 쪼개서 집중할 수 있어야 한다. 그래야 테스트에서 문제가 생겼어도 문제가 생긴 부분이 명확해진다.

UserDaoTest의 경우는 문제가 생겼을 때 DB 연결 방법, SQL 입력정도에서 문제가 생겼을 것이라고 추측이 가능하다. 웹에서 버튼을 누를 필요도 없고, 그러한 테스트에 비하면 테스트했을 때, 상대적으로 어디서 문제가 생겼는지 명확하다.

이렇게 작은 단위의 코드에 대해 테스트를 수행하는 것을 단위 테스트(unit test) 라고 한다. 여기서 말하는 단위라는 것은 명확히 범위와 크기가 정해진 건 아니고, 하나의 관심에 집중해서 효율적으로 테스트할만한 범위의 단위라고 보면 된다.

일반적으로 단위는 작을수록 좋다.

단위 테스트와 DB의 관계

사용할 DB의 상태를 테스트가 관장하고 있다면 이는 단위 테스트이다. 다만, DB의 상태가 매번 달라지고 테스트를 위해 DB를 특정 상태로 만들어줄 수 없다면, UserDaoTest는 단위 테스트로서의 가치가 없어진다. 통제할 수 없는 외부의 리소스에 의존하는 테스트는 단위 테스트가 아니라고 본다.

단위 테스트만으로 충분한가?

초기 등록부터 로그인, 각종 기능을 모두 사용한 뒤에 로그아웃하는 전 과정을 하나로 묶어서 테스트할 필요성도 물론 있다. 각 단위 기능은 잘 동작하는데 묶어놓으면 안 되는 경우가 종종 발생하기 때문이다.

그러나, 이러한 긴 테스트만 있다면 에러를 만나거나 에러는 아니지만 원치 않는 동작이 발생했을 때, 그 원인을 찾기 매우 힘들다. 디버깅을 돌려가며 각 단계별로 DB와 메모리 값을 확인해야 하는 수고를 할 수도 있다.

단위 테스트는 개발자 테스트

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

물론, 전문 테스터나 고객이 직접 단위 테스트를 할 수도 있지만, 개발자 입장에서 만든것이므로 이를 개발자 테스트라고 부를 수도 있다.

단위 테스트가 잘 작성되었다면 나중에 고객에 의해 테스트된 뒤에 오류가 발견되더라도 개발자는 그 부분이 어딘지 상대적으로 쉽게 가늠해볼 수 있다.

자동수행 테스트 코드

웹 화면을 이용하는 경우, 테스트하는 테스터가 매번 입력 값을 손으로 입력해주고 버튼을 눌러보아야 한다. 이 경우 입력 값을 실수할 수도 있고, 테스트하는데 시간도 많이 소비된다.

이러한 단점을 해결하기 위해 테스트는 자동으로 수행되도록 코드로 만들어지는 것이 중요하다. 또한 테스트 코드는 애플리케이션을 구성하는 클래스 안에 포함되는 것이 아니라, 별도로 테스트용 클래스를 만들어서 테스트 코드를 넣는 편이 좋다.

테스트 코드에 대한 분리가 확실하면, 추후에 몇번이고 테스트를 다시해도 부담이 없다.

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

테스트는 최대한 작은 단위로 작성하며 작은 테스트를 수행하고 성공하면서 지속적 확신을 얻으며 개발하는 것이 좋다. 이러한 과정을 거쳐 전체적으로 코드를 개선하는 작업에 속도가 붙고 더 쉬워질 수 있다.

테스트를 이용하면 새로운 기능도 기대한 대로 동작하는지 확인할 수 있으며, 기존에 만들어둔 기능들이 새로운 기능을 추가하느라 수정한 코드에 영향을 받지 않고 여전히 잘 동작하는지도 확인할 수 있다.

UserDaoTest의 문제점

UserDaoTestUI단계까지 동원되는 수동 테스트에 비해 수행하기도 쉽고, 책임도 명확한 클래스였지만, 만족스럽지 못한 부분도 있었다.

수동 확인 작업의 번거로움

결과를 단순히 콘솔창에 출력하여 사람이 직접 눈으로 확인해야 했다. 코드로 테스트 결과가 맞는지 자동으로 확인해주었다면 더욱 좋은 테스트가 됐을 것이다.

실행 작업의 번거로움

DAO가 수백개가 된다면 모든 DaoTest클래스의 main() 메소드를 일일이 실행시켜보는데는 상당한 수고가 필요할 것이다. 좀 더 편리하고 체계적으로 실행하고 그 결과를 확인하는 방법은 없을까?

Last updated