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

테스트 도구 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을 인자로 줄 수 있는 방법을 찾으면 글을 수정해보자!