본문 바로가기
컴퓨터공학/스프링

테스트 도구 Junit

by 유리병 2023. 12. 7.

목차


     

     

    junit을 잘 활용하면 브라우저에 접속하지 않아도 테스트를 할 수 있다. 

     

    Library 설정

    Junit Library추가

    	<dependency>
    		<groupId>junit</groupId>
    		<artifactId>junit</artifactId>
    		<version>4.12</version>
    		<scope>test</scope>
    	</dependency>

     

    spring-test Library추가

    	<dependency>
    	    <groupId>org.springframework</groupId>
    	    <artifactId>spring-test</artifactId>
    	    <!-- 버전은 spring버전과 맞추기 -->
    	    <version>5.2.25.RELEASE</version>
    	    <scope>test</scope>
    	</dependency>

     

    테스트 파일 생성 및 실행

    테스트 파일 생성

    src/test/java에 테스트파일을 생성한다

     

    클래스명으로 Test라는 이름을 쓰면 안된다
    Junit의 anootation명이 Test이기 때문에 에러가 난다!

     

     

    import org.junit.Test;
    
    public class tempTest {
    	@Test
    	public void test() {
    		System.out.println("실행하고자 하는 테스트메소드 작성");
    	}
    }

     

    테스트 메소드 실행

     

    여러개의 테스트 메소드 중 한 메소드만 실행하고 싶을 경우 해당 메소드의 이름을 더블클릭 후 위 방법으로 메소드를 실행하면 해당 메소드만 실행된다.

     

     

    테스트 결과 확인

     

     

    Assertion

    테스트의 성공과 실패를 검증한다.

    내가 예상한 값을 먼저 기입한 후, 결과가 내가 예상한 값과 다르게 나오면 테스트가 실패한 것으로 간주한다.

    • assertEquals(a, b)
    • assertTrue(a)
    • assertFalse(a)
    • assertNull(a)
    • assertNotNull(a)
    • 등 수 많은 asertXXX()메소드가 있으니 ctrl + space로 원하는 기능을 찾아서 사용하면 된다.

     

    public class tempTest {
    	@Test
    	public void test() {
    		int a = 5;
    		assertEquals(3, a);
    	}
    }

    위 코드를 수행할 경우, 실행은 잘 되지만 예상했던 값(3)이 a값(5)와 다르기 때문에 테스트 실패로 처리된다. 

     

     

     Junit의 실행 순서

    BeforeClass → Before → Test → After → AfterClass

     

    @Before @After

    Before Annotation이 달린 메소드는 다른 테스트 메소드들이 실행되기 전에 실행된다.

    After Annotation이 달린 메소드는 다른 테스트 메소드들이 실행된 후에 실행된다.

    public class tempTest {
    	@Before
    	public void before() {
    		System.out.println("before");
    	}
    	@After
    	public void after() {
    		System.out.println("after");
    	}
    
    	@Test
    	public void test() {
    		System.out.println("테스트");
    	}
    	@Test
    	public void test2() {
    		System.out.println("테스트2");
    	}
    }

    실행 결과

     

    @BeforeClass @AfterClass

    BeforeClass Annotation이 달린 메소드는 처음 한 번만 수행된다.

    AfterClass Annotation이 달린 메소드는 마지막 한 번만 수행된다.

    이 둘은 반드시 static으로 선언되어야 한다.

    package test;
    
    import org.junit.AfterClass;
    import org.junit.BeforeClass;
    import org.junit.Test;
    
    public class tempTest {
    	@BeforeClass
    	public static void before() {
    		System.out.println("처음 한번만");
    	}
    
    	@AfterClass
    	public static void after() {
    		System.out.println("마지막 한번만");
    	}
    
    	@Test
    	public void test() {
    		System.out.println("테스트");
    	}
    
    	@Test
    	public void test2() {
    		System.out.println("테스트2");
    	}
    }

     

     

     

    Web 없이 SQL 테스트하기

    일반적으로 SQL을 실행하는데 까지 필요한 과정

    VO만들기 → Mapper만들기 → Mapper Interface만들기 → Service만들기 → Controller만들기 → 웹페이지 만들기 → 웹에서 실행

     

    Junit을 사용해 SQL을 실행하는데 까지 필요한 과정

    VO만들기 → Mapper만들기 → Mapper Interface만들기 → Test파일 만들기 → 테스트 실행

     

    Junit을 사용하면 간단히 테스트를 수행할 수 있다.

     

     

    이때 아래 세 @Annotation을 꼭 작성해야 한다.

    • @RunWith
    • @ContextConfiguration
      • java : @ContextConfiguration(classes = 클래스경로)
      • xml : @ContextConfiguration(location = 클래스경로)
    • @WebAppConfiguration

     

    Mapper  Interface Test

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = { sinhanDS.first.project.config.MvcConfig.class })
    @WebAppConfiguration
    public class MapperInterfaceTest {
    	@Autowired
    	ProductMapper mapper;	//테스트 할 mapper 주입
    	
    	@Test
    	public void test() {
    		List<ProductVO> list = mapper.product_list();
    		for(int i = 0; i < list.size(); i++) {
    			System.out.println(list.get(i));
    		}
    	}
    }
    실행 SQL문 SELECT * FROM product

     

    Service Test

    	@Autowired
    	SellerService service; //테스트할 서비스 빈 주입
        
    	@Test
    	public void test2() {
    		System.out.println(service.getProduct(4));
    	}
    실행 SQL문 SELECT * FROM product WHERE no=4

     

     

    Controller Test

    JSP가 없어도 Controller를 테스트 할 수 있다.

    체크할 컨트롤러의 메소드

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = { sinhanDS.first.project.config.MvcConfig.class })
    @WebAppConfiguration
    public class tempTest {
    	/* 테스트 환경에서 실행할 때 주소를 입력하기 위해 사용 */
    	@Autowired
    	WebApplicationContext ctx;
        
        /* 테스트 환경에서 실행할 가상의 MVC */
    	MockMvc mock;
    
    	/* 테스트 함수를 수행하기 전에 @Before 함수로 mok을 생성해야 한다 */
    	@Before
    	public void before() {
    		mock = MockMvcBuilders.webAppContextSetup(ctx).build();
    	}
    	
        /* 테스트 해볼 메소드를 만든다. */
    	@Test
    	public void regist_product() throws Exception {
    		RequestBuilder req = MockMvcRequestBuilders.get("/seller/idCheck.do")
    				.param("id", "collagom")	//parameter가 필요할 경우 parameter 부여
    				.param("ohterParam", "insertHere");//여러 개의 parameter를 부여할 수 있다	
    		mock.perform(req);
    
    	}
    }

     

     

    통합테스트 수행하기

    준비 작업으로 Test1, Test2 생성

     

     

    통합 테스트 수행을 위한 Java파일 생성

    @RunWith(Suite.class)
    @SuiteClasses({Test1.class, Test2.class})
    public class IntergratedTest {
    
    }
    • 아래 Annotation을 작성
      • @RunWith(Suite.class)
      • @SuiteClasses({통합 테스트 수행을 위한 클래스들을 나열})

    위 자바파일을 Run as Junit Test로 작동시키면 Test1.java, Test2.java 파일 내의 모든 Test메소드를 실행한다. 

    실행 결과

     

     

    통합 테스트 수행 중 에러가 날 경우

    위와 같이 어느 부분에서 에러가 났는지 체크해준다. 

     

     

    Test할 때 Interceptor 통과하기

    Interceptor에 의한 Block

    Interceptor에 대한 글은 아래 글에 작성으니 참고해보자!

    https://cider-glass.tistory.com/19

     

    로그인을 해야만 /seller/index.do로 접속할 수 있도록 Interceptor를 설정해줬다.

    때문에 아래 코드를 실행하면 인터셉터에 의해 막힌다.

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = { sinhanDS.first.project.config.MvcConfig.class })
    @WebAppConfiguration
    public class InterceptorTest {
    	@Autowired
    	WebApplicationContext ctx;
    	MockMvc mock;
    
    	@Before
    	public void before() {
    		mock = MockMvcBuilders.webAppContextSetup(ctx).build();
    	}
    
    	@Test
    	public void interceptorTest() throws Exception {
    		RequestBuilder req = MockMvcRequestBuilders.get("/seller/index.do");
    		
    		mock.perform(req);
    
    	}
    }

    실행결과

     

     

    이 프로그램의 Interceptor는 다음과 같다.

    public class SellerLoginInterceptor  implements HandlerInterceptor {
    	@Override
    	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    		HttpSession sess = request.getSession();
    		SellerVO login = (SellerVO)sess.getAttribute("sellerLoginInfo");
    		if (login == null) {
    			System.out.println("인터셉터 컷");
    			return false;
    		}
    		System.out.println("인터셉터 통과");
    		return true;
    	}
    }

    이 프로그램의 Interceptor는 Session에서 로그인여부를 받아와서 검사하는 구조이기 때문에 Session에 로그인 정보를 저장해서 테스트를 수행할 필요가 있다.

     

    Test시 Session 추가하기

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = { sinhanDS.first.project.config.MvcConfig.class })
    @WebAppConfiguration
    public class InterceptorTest {
    	@Autowired
    	WebApplicationContext ctx;
    	MockMvc mock;
    
    	@Before
    	public void before() {
    		mock = MockMvcBuilders.webAppContextSetup(ctx).build();
    	}
    
    	@Test
    	public void interceptorTest() throws Exception {
    		RequestBuilder req = MockMvcRequestBuilders.get("/seller/index.do")
    				.sessionAttr("sellerLoginInfo", new SellerVO());
    			/* 위와 같이 sessionAttr로 Test시 사용할 Session을 설정할 수 있다!! */
            
    		mock.perform(req);
    
    	}
    }

    위 코드와 같이 .sessionAttr()메소드를 사용해서 Test시 사용할 Session을 설정해 줄 수 있다!

     

    실행결과

     

     

     

    Interceptor를 사용할 수 없는 ParameterType에 대하여

    이 부분에 대해서는 사용할 수 없는지 확실치 않다. 이후에 사용 가능한 방법을 찾을 경우 수정하겠다.

     

    @RequestParam MultipartFile 

    @PostMapping("/regist.do")
    public String regist(@RequestParam MultipartFile filename, HttpServletRequest request, ProductVO vo, ProductCategoryVO cvo, ProductOptionVO ovo) {
    	System.out.println("voTest: " + vo);
        if(filename != null) {
    		vo = service.upload_file(filename, vo);
    	}
    	service.regist(vo, cvo, ovo);
    		
    	return "redirect:/seller/index.do";
    }

    위 코드는 JSP로부터 받아온 파일을 업로드하는 코드이다. 

    이때 인자로 MultipartFile을 받아오는 것을 확인할 수 있다.

     

    위 코드를 아래 코드로 테스트하면 테스트가 동작하지 않는다.

     

    	@Test
    	public void regist_product() throws Exception {
    		RequestBuilder req = MockMvcRequestBuilders.post("/seller/product/regist.do")
    				.sessionAttr("sellerLoginInfo", new SellerVO())
    				.param("seller_no", "1").param("name", "테스트로만든상품")
    				.param("price", "10001").param("stock", "50")
    				.param("discount", "0");
    		
    		mock.perform(req);
    	}

    실행 결과처럼 인터셉터만 통과하고 voTest가 출력되지 않는다.

    이는 /seller/product/regist.do 요청을 통해 인터셉터로 그 요청이 들어가기는 했지만, 인터셉터를 통과한 이후에 /seller/product/regist.do가 맵핑된 메소드로는 도달하지 못했다는 의미이다.

     

    이때 /seller/product/regist.do 요청이 맵핑된 메소드를 아래와 같이 MultipartFile이 포함되지 않게 수정하면 테스트 코드가 잘 작동하는 것을 확인할 수 있다.

    	@PostMapping("/regist_forTest.do")
    	public String regist(HttpServletRequest request, ProductVO vo, ProductCategoryVO cvo, ProductOptionVO ovo) {
    		service.regist(vo, cvo, ovo);
    		
    		return "redirect:/seller/index.do";
    	}

     

     

    MultipartFile메소드를 Junit의 RequestBuilder를 넘겨주지 못했고, 이로 인해서 /seller/product/regist.do와 맵핑된 메소드와 인자의 타입이 달라서 해당 메소드를 실행하지 못한 것으로 보여진다.

     

    RequestBuilder에 MultipartFile을 인자로 줄 수 있는 방법을 찾으면 글을 수정해보자!