package com.example.springcalculator.component;
public interface ICalculator {
int sum(int x, int y);
int minus(int x, int y);
void init();
}
Calculator 클래스 작성
@RequiredArgsConstructor: final로 선언된 변수의 생성자를 만들어 준다. 따라서 기존 생성자를 지워준다.
package com.example.springcalculator.component;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
// final로 선언된 변수의 생성자를 만들어 준다.
@RequiredArgsConstructor
public class Calculator {
private final ICalculator iCalculator;
// public Calculator(ICalculator iCalculator){
// this.iCalculator = iCalculator;
// }
public int sum(int x, int y){
// 서버로부터 환율을 받아온다.
iCalculator.init();
return this.iCalculator.sum(x,y);
}
public int minus(int x, int y){
// 서버로부터 환율을 받아온다.
iCalculator.init();
return iCalculator.minus(x, y);
}
}
DollarCalculator 클래스 작성
@RequiredArgsConstructor: final로 선언된 변수의 생성자를 만들어 준다. 따라서 기존 생성자를 지워준다.
package com.example.springcalculator.component;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
// final로 선언된 변수의 생성자를 만들어 준다.
@RequiredArgsConstructor
public class DollarCalculator implements ICalculator {
private int price = 1;
private final MarketApi marketApi;
// 현재 환율 가져오기 (생성자를 이용하여 클래스 호출시 자동)
// public DollarCalculator(MarketApi marketApi){
// this.marketApi = marketApi;
// }
// 현재 환율을 금액에 대입 시킨다.
@Override
public void init(){
this.price = marketApi.connect();
}
@Override
public int sum(int x, int y) {
x *= price;
y *= price;
return x + y;
}
@Override
public int minus(int x, int y) {
x *= price;
y *= price;
return x - y;
}
}
MarketApi 클래스 작성 (환율 값 가져오는 클래스)
package com.example.springcalculator.component;
import org.springframework.stereotype.Component;
@Component
public class MarketApi {
public int connect(){
return 1100;
}
}
POST 방식의 controller 테스트를 진행하기 위한 DTO 생성
Req 클래스 생성 (파라미터를 받을 DTO)
package com.example.springcalculator.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Req {
private int x;
private int y;
}
Res 클래스 작성 (연산 결과와 상태값을 저장할 DTO)
package com.example.springcalculator.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Res {
private int result;
private Body response;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Body{
private String resultCode = "OK";
}
}
controller 작성 (POST, GET 처리)
package com.example.springcalculator.controller;
import com.example.springcalculator.component.Calculator;
import com.example.springcalculator.dto.Req;
import com.example.springcalculator.dto.Res;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class CalculatorApiController {
private final Calculator calculator;
@GetMapping("/sum")
public int sum(@RequestParam int x, @RequestParam int y){
return calculator.sum(x, y);
}
@PostMapping("/minus")
public Res minus(@RequestBody Req req){
int result = calculator.minus(req.getX(), req.getY());
Res res = new Res();
res.setResult(result);
// resultCode":"OK" 데이터 추가
res.setResponse(new Res.Body());
return res;
}
}
JUnit Test 진행할 code 작성
기본적인 Test Code
package com.example.springcalculator;
import com.example.springcalculator.component.Calculator;
import com.example.springcalculator.component.DollarCalculator;
import com.example.springcalculator.component.MarketApi;
import com.example.springcalculator.dto.Req;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.test.web.servlet.MockMvc;
@SpringBootTest
public class DallarCalculatorTest {
// 스프링에서는 Bean 처리되기 때문에
@MockBean
private MarketApi marketApi;
@Autowired
private Calculator calculator;
@Test
public void dollarCalculatorTest(){
Mockito.when(marketApi.connect()).thenReturn(3000);
int sum = calculator.sum(10, 10);
int minus = calculator.minus(10, 10);
Assertions.assertEquals(60000, sum);
Assertions.assertEquals(0, minus);
}
}
Controller를 이용하여 MVC 테스트를 진행할 코드 작성
package com.example.springcalculator.controller;
import com.example.springcalculator.component.Calculator;
import com.example.springcalculator.component.DollarCalculator;
import com.example.springcalculator.component.MarketApi;
import com.example.springcalculator.dto.Req;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
// web을 test하기 때문에 선언
// SpringBootTest을 이용하여 진행해도 되지만 SpringBootTest는 bean에 등록된 모든 자원을 이용하고
// WebMvcTest는 해당 클래스만 이용하기 때문에 자원을 아낄 수 있다.
@WebMvcTest(CalculatorApiController.class)
// MVC와 관련된 Bean을 올린다
@AutoConfigureWebMvc
// 테스트를 진행할 controller에서 사용하기 때문에 해당 클래스를 주입해준다.
@Import({Calculator.class, DollarCalculator.class})
public class CalculatorApiControllerTest {
@MockBean
private MarketApi marketApi;
@Autowired
private MockMvc mockMvc;
@BeforeEach
public void init(){
Mockito.when(marketApi.connect()).thenReturn(3000);
}
// get 방식 테스트
@Test
public void sumTest() throws Exception {
// http://localhost:8080/api/sum
// 해당 URL로 접속
mockMvc.perform(
MockMvcRequestBuilders.get("http://localhost:8080/api/sum")
// 파라미터 값 추가
.queryParam("x", "10")
.queryParam("y", "10")
// 기대하는 값 입력
).andExpect(
// 200 ok
MockMvcResultMatchers.status().isOk()
).andExpect(
// 합: 60000
MockMvcResultMatchers.content().string("60000")
// 실행
).andDo(MockMvcResultHandlers.print());
}
// POST 방식 테스트
@Test
public void minusTest() throws Exception {
Req req = new Req();
req.setX(10);
req.setY(10);
// req를 json 형태로 변환
String json = new ObjectMapper().writeValueAsString(req);
System.out.println(json);
// 해당 URL로 접속
mockMvc.perform(
MockMvcRequestBuilders.post("http://localhost:8080/api/minus")
.contentType(MediaType.APPLICATION_JSON)
.content(json)
// 기대하는 값 입력
).andExpect(
MockMvcResultMatchers.status().isOk()
).andExpect(
// json의 result 항목이 0으로 나오는지 확인
MockMvcResultMatchers.jsonPath("$.result").value("0")
).andExpect(
// json의 response.resultCode 항목이 OK으로 나오는지 확인
MockMvcResultMatchers.jsonPath("$.response.resultCode").value("OK")
// 실행
).andDo(MockMvcResultHandlers.print());
}
}
테스트 주도 개발에서 사용하지만, 코드의 유지 보수 및 운영 환경에서의 에러를 미리 방지 하기 위해서 단위 별로 검증하는 테스트 프레임워크
단위 테스트
작성한 코드가 기대하는 대로 동작을 하는지 검증 하는 절차
JUnit
Java기반의 단위 테스트를 위한 프레임워크
Annotation 기반으로 테스트를 지원하며, Assert를 통하여, (예상, 실제)를 통해 검증
실습 환경: Gradle 자바 프로젝트로 진행
해당 dependency, test가 존재하는지 확인
테스트를 진행하기 위한 계산 코드 작성
ICalculator 인터페이스 생성
public interface ICalculator {
int sum(int x, int y);
int minus(int x, int y);
}
원화 계산을 하기위한 KrwCalculator 클래스 생성
public class KrwCalculator implements ICalculator{
private int price = 1;
@Override
public int sum(int x, int y) {
x *= price;
y *= price;
return x + y;
}
@Override
public int minus(int x, int y) {
x *= price;
y *= price;
return x - y;
}
}
달러 계산을 하기위한 DollarCalculator 클래스 생성
public class DollarCalculator implements ICalculator{
private int price = 1;
private MarketApi marketApi;
// 현재 환율 가져오기 (생성자를 이용하여 클래스 호출시 자동)
public DollarCalculator(MarketApi marketApi){
this.marketApi = marketApi;
}
// 현재 환율을 금액에 대입 시킨다.
public void init(){
this.price = marketApi.connect();
}
@Override
public int sum(int x, int y) {
x *= price;
y *= price;
return x + y;
}
@Override
public int minus(int x, int y) {
x *= price;
y *= price;
return x - y;
}
}
환율 값을 가져올 MarketApi 클래스 생성
public class MarketApi {
public int connect(){
return 1100;
}
}
원화 클래스와 달러 클래스를 주입받기 위한 Calculator 클래스 선언
public class Calculator {
private ICalculator iCalculator;
public Calculator(ICalculator iCalculator){
this.iCalculator = iCalculator;
}
public int sum(int x, int y){
return this.iCalculator.sum(x,y);
}
public int minus(int x, int y){
return iCalculator.minus(x, y);
}
}
테스트 코드 작성 (어노테이션 이용)
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
// Mock 어노테이션을 사용하기 위해 선언
@ExtendWith(MockitoExtension.class)
public class DollarCalculatorTest {
//@Test
public void testHello(){
System.out.println("Hello");
}
// 테스트를 진행하려는 클래스 지정
@Mock
public MarketApi marketApi;
// 원하는 클래스에 메소드가 동작할때 사용자 임의로 데이터를 설정할 수 있다.
@BeforeEach
public void init(){
Mockito.lenient().when(marketApi.connect()).thenReturn(3000);
}
@Test
public void dollarTest(){
MarketApi marketApi = new MarketApi();
DollarCalculator dollarCalculator = new DollarCalculator(marketApi);
dollarCalculator.init();
Calculator calculator = new Calculator(dollarCalculator);
System.out.println(calculator.sum(10, 10));
// Assertions.assertEquals("예상하는 데이터", "실제 동작하는 데이터")
Assertions.assertEquals(22000, calculator.sum(10,10));
Assertions.assertEquals(0, calculator.minus(10,10));
}
// Mock 어노테이션을 이용한 테스트 진행 함수
@Test
public void MockTest(){
DollarCalculator dollarCalculator = new DollarCalculator(marketApi);
dollarCalculator.init();
Calculator calculator = new Calculator(dollarCalculator);
Calculator calculatorK = new Calculator(new KrwCalculator());
System.out.println(calculator.sum(10, 10));
// Assertions.assertEquals("예상하는 데이터", "실제 동작하는 데이터")
Assertions.assertEquals(60000, calculator.sum(10,10));
Assertions.assertEquals(0, calculator.minus(10,10));
Assertions.assertEquals(20, calculatorK.sum(10,10));
Assertions.assertEquals(0, calculatorK.minus(10,10));
}
}