• 스프링이 제공하는 가장 중요한 가치는 객체지향과 테스트다
    • IoC/DI를 이용해 객치지향 기술을 손쉽게 적용하도록 도와준다
  • 테스트
    • 코드의 확신과 변화의 유연함
    • 스프링 학습에 효과적
  • 웹을 이용한 DAO를 테스트
    • DAO뿐만 아니라 서비스 클래스, 컨트롤러, JSP 뷰 등 모든 레이어 기능을 다 만들고 나서야 테스트가 가능
    • 테스트 중 발생한 에러의 원인을 찾는 수고가 필요
    • 과정이 번거롭고 오류에 대한 대처가 쉽지 않다
  • UserDaoTest
      public class UserDaoTest {
          public static void main(String[] args) throws SQLException {
          Applicationcontext context = new GenericXmlApplicationContext("applicationcontext.xml");
    		
          UserDao dao = context.getBean("userDao", UserDao.class);
    		
          User user = new User();
          user.setld("user");
          user.setName("백기선");
          user.setPassword("married");
    		
          dao.add(user);
    		
          System.out .printin(user.getId() + " 등록 성공");
    		
          User user2 = dao.get(user.getId());
          System.out.printin(user2.getName());
          System.out.printin(user2.getPassword());
    		
          System.out.printin (user2.getId() + " 조회 성공");
          }
      }
    
  • 단위 테스트(Unit Test) : 작은 단위의 코드에 대해 테스트를 수행

  • 테스트에 DB가 사용되면 단위 테스트가 아닐까?
    • DB의 상태를 테스트가 관장하고 있다면 단위 테스트이다
  • 단위테스트가 필요한 이유
    • 개발자가 설계하고 만든 코드가 원래 의도한 대로 동작하는지를 개발자 스스로 발리 확인받기 위함
    • 단위가 작은 시점에 빠르게 테스트를 하여 오류를 수정하기 위함
  • 자동화된 테스트
    • 수작업을 거치는 방법이 아닌 코드로 만들어져 자동으로 수행되어야 한다
    • 자주 반복할 수 있다
  • UserDaoTest의 문제점
    • 수동 확인 작업의 번거로움
    • 실행 작업의 번거로움

UserDaoTest 개선

두 문제점을 개선해보자

  • 검증 자동화된 UserDaoTest
      //수정 전 
      System.out.printin(user2.getName());
      System.out.println(user2.getPassword());
      System.out.printin (user2.getId() + " 조회 성공");
    
      //수정 후
      if (!user.getName().equals(user2.getName())) {
          System.out .printin("테스트 실패 (name)");
      }
      else if (!user.getPassword().equals(user2.getPassword())) {
          System.out .printin("테스트 실패 (password)");
      }
      else {
          System.out.println ("조회 테스트 성공");
      }
    
  • JUnit : 독립된 단위테스트를 지원해주는 자바 테스팅 프레임워크
    • 테스트용 메소드의 조건
      • public으로 선언
      • @Test 어노테이션 사용
    • assertThat()
      • JUnit이 제공하는 메소드
      • 첫 번째 파라미터(actual)와 뒤에 오는 matcher와 비교
      • actual : 테스트 대상의 실제 값
      • matcher : 기대하는 값이나 조건
  • JUnit을 적용한 UserDaoTest
      import static org.hamcrest.CoreMatchers.is;
      import static org.junit.Assert.assertThat;
      ...
      public class UserDaoTest {
          @Test
          public static void addAndGet() throws SQLException {
          Applicationcontext context = new GenericXmlApplicationContext("applicationcontext.xml");
    		
          UserDao dao = context.getBean("userDao", UserDao.class);
    	
          User user = new User();
          user.setld("user");
          user.setName("백기선");
          user.setPassword("married");
    		
          dao.add(user);
    		
          User user2 = dao.get(user.getId());
    		
          assertThat(user2.getName(), is(user.getName()));
          assertThat(user2.getPassword(), is(user.getPassword()));
          }
      }
    
  • JUnit 테스트를 실행하는 메소드
      import org.junit.runner.JUnitCore
      ...
      public static void main(String[] args) {
          JUnitCore.main("springbook.user.dao.UserDaoTest");
      }
    
  • 단위 테스트는 일관성있는 결과가 보장돼야 한다
    • DB에 남아있는 데이터와 같은 외부 환경에 영향을 받지 말아야 함
    • 실행 순서를 바꿔도 동일한 결과를 보장해야 함

테스트가 이끄는 개발

  • 테스트 주도 개발(TDD, Test Driven Develop)
    • “실패한 테스트를 성공시키기 위한 목적이 아닌 코드는 만들지 않는다”
    • 테스트가 만들고자 하는 기능의 내용을 담고있음
    • 당연히 만들어진 코드를 검증할 수도 있음
    • 테스트 개발 -> 테스트를 위한 기능 구현
    • 코드를 만들고 테스트를 실행하는 그 사이의 간격이 짧다
    • 오히려 개발속도가 빠르다
  • JUnit이 제공하는 기능을 통한 테스트 코드 개선
    • @Before : @Test 메소드가 실행되기 전에 먼저 실행되어야 하는 메소드를 정의한다
    • @After : @Test 메소드가 실행된 후에 실행되어야 하는 메소드를 정의한다
  • @Before 활용
      import static org.hamcrest.CoreMatchers.is;
      import static org.junit.Assert.assertThat;
      ...
      public class UserDaoTest {
          @Before
          public void setUp() {
              Applicationcontext context = new GenericXmlApplicationContext("applicationcontext.xml");
          this.dao = context.getBean("userDao", UserDao.class);
          }
    
          @Test
          public static void addAndGet() throws SQLException {
          User user = new User();
          user.setld("user");
          user.setName("백기선");
          user.setPassword("married");
    		
          dao.add(user);
    		
          User user2 = dao.get(user.getId());
    		
          assertThat(user2.getName(), is(user.getName()));
          assertThat(user2.getPassword(), is(user.getPassword()));
          }
      }
    
  • JUnit은 테스트 메소드를 실행할 때마다 테스트 클래스의 오브젝트를 새로 만든다
  • 픽스처(Fixture) : 테스트를 수행하는 데 필요한 정보나 오브젝트

  • @Before를 사용한 ApplicationContext 생성방식
    • Bean이 많아지고 복잡해지면 시간이 오래 걸릴 수 있다
      • ApplicationContext가 만들어질 때 모든 싱글톤 빈 오브젝트를 초기화한다
      • 어떤 빈은 독자적으로 많은 리소스를 할당하거나 독립적인 스레드를 띄우기도 한다
    • 한 번만 만들고 공유해서 사용하자
  • Junit을 이용하는 테스트 컨텍스트 프레임워크
    • @RunWith : 프레임워크의 테스트 실행 방법을 확장할 때 사용
    • @ContextConfiguration : 자동으로 만들어줄 애플리케이션 컨텍스트의 설정파일 위치를 지정
    • @Autowired : 스프링 컨텍스트에서 같은 타입의 빈을 주입해준다
  • 스프링 테스트 컨텍스트
      @RunWith(SpringJUnit4ClassRunner.class)
          @ContextConfiguration(locations="/applicationContext.xml") 
      public class UserDaoTest { 
          @Autowired 
          private ApplicationContext context;
          ...
    		
          @Before public void setUp() { 
              this.dao = this.context.getBean("userDao", UserDao.class);
          }
    
  • 다른 클래스와의 공유
      @RunWith(SpringJUnit4ClassRunner.class)
          @ContextConfiguration(locations="/applicationContext.xml") 
      public class UserDaoTest { .. }
    
      // 서로 공유
    
      @RunWith(SpringJUnit4ClassRunner.class)
              @ContextConfiguration(locations="/applicationContext.xml") 
          public class GroupDaoTest { .. }
    

DI와 테스트

인터페이스 구현 클래스를 절대로 바꾸지 않는다고 해도 DI를 사용해야할까?

  • 소프트웨어 개발에서 절대로 바뀌지 않는 것은 없다
  • 클래스의 구현 방식은 바뀌지 않는다고 하더라도 인터페이스를 두고 DI를 적용하게 해두면 다른 차원의 서비스 기능을 도입할 수 있다
  • 효율적인 테스트를 손쉽게 만들기 위해

테스트에서의 DI 활용

  1. 수동 DI를 통한 의존관계 변경
    • 수동DI를 통한 테스트용 DataSource 사용
       @DirtiesContext 
       public class UserDaoTest {
         @Autowired
         UserDao dao;
         @Before
         public void setUp() { 
             ...
             DataSource dataSource = new SingleConnectionDataSource("jdbc:mysql://localhost/testdb", "spring", "book", true);
             dao.setDataSource(dataSource);
       }
      

      문제점 :  스프링 테스트 컨텍스트 프레임워크가 적용되어 모든 테스트가 하나의 애플리케이션 컨텍스트를 공유한다 해결 : @Dirtiescontext : 해당 클래스의 테스트에서 애플리케이션 컨텍스트의 상태를 변경한다는 것을 스프링의 테스트 컨텍스트 프레임워크에게 알려줌

  2. 테스트 전용 설정파일 생성
    • @ContextConfiguration 수정
       @RunWith(SpringJUnit4ClassRunner.class)
       @ContextConfiguration(locations="/test-applicationContext.xml")
       public class UserDaoTest { ... }
      
  3. 컨테이너 없는 DI 테스트
    • @ContextConfiguration 수정 ```java public class UserDaoTest { UserDao dao; …

    @Before public void setUp() { … dao = new UserDao(); DataSource dataSource = new SingleConnectionDataSource(…); dao.setDataSource(dataSource); } ```
    - 오브젝트를 직접 생성하는 번거로움은 있지만 애플리케이션 컨텍스트를 아예 사용하지 않으니 더 단순해지고 이해하기 편해짐

  • 학습 테스트(Learning Test) : 자신이 만들지 않은 기능에 대한 테스트를 작성하며 API나 프렘이워크의 사용법을 학습
    • 다양한 조건에 따른 기능을 손쉽게 확인해 볼 수 있다
    • 학습 테스트 코드를 개발 중에 참고할 수 있다
    • 프레임워크나 제품을 업그레이드 할 때 호환성 검증을 도와준다
    • 테스트 작성에 대한 좋은 훈련이 된다
    • 새로운 기술을 공부하는 과정이 즐거워진다
  • 버그 테스트(Bug Test) : 코드에 오류가 있을 때 그 오류를 가장 잘 드러내줄 수 있는 테스트
    • 버그가 발생하는 코드를 작성하고 그것을 해결하도록 코드를 수정
    • 테스트의 완성도를 높여준다
    • 버그의 내용을 명확하게 분석하게 해준다
    • 기술적인 문제를 해결하는 데 도움이 된다

카테고리:

업데이트:

댓글남기기