테스트 주도 개발(Test-Driven Development, TDD) - 3
테스트 주도 개발(Test-Driven Development, TDD)
에 대한 학습
이전 포스팅에서 컨트롤러를 테스트하기 위한 환경을 구축하고 실제로 동작하는지까지 확인해봤다.
이번에는 신장과 체중을 입력하면 BMI지수를 반환해주는 API를 만들어 볼 것이다.
입력은 숫자만 들어온다고 가정한다. <input type="number">
이를 테스트하기 위한 코드를 먼저 작성해보자.
// file: 'FirstControllerTest.class'
public class FirstControllerTest extends AbstractMockMvcTests {
@Test
public void init() throws Exception {
mvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(content().string("Hello! World!"));
}
@Test
public void getBmi() throws Exception {
DecimalFormat df = new DecimalFormat("#.##");
String height = "180";
String weight = "70";
// BMI = 체중(kg) / (신장(m) * 신장(m))
double bmi = Double.parseDouble(weight) / Math.pow(Double.parseDouble(height) / 100, 2);
String result = df.format(bmi);
mvc.perform(get("/get/bmi")
.param("height", height)
.param("weight", weight))
.andExpect(content().string(result));
}
}
DecimalFormat df = new DecimalFormat("#.##");
계산하여 나온 BMI지수는 double
이기 때문에 소수점 자리가 길게 늘어질 거라 예상이 된다.
깔끔하게 소수점 2번째 자리까지만 보고 싶기 때문에 DecimalFormat
을 생성한다.
String height = "180";
String weight = "70";
클라이언트가 입력할 신장과 체중 값을 미리 입력해보는 곳이다.
이곳의 값들을 조정해 Tase-Case
로 이용할 것이다.
// BMI = 체중(kg) / (신장(m) * 신장(m))
double bmi = Double.parseDouble(weight) / Math.pow(Double.parseDouble(height) / 100, 2);
String result = df.format(bmi);
테스트 코드를 자동화하기 위해 BMI의 계산식을 코드로 구현한다.
result 에는 계산되어 나온 BMI지수가 소수점 2번째 자리에서 잘려 String 리터럴로 저장될 것이다.
mvc.perform(get("/get/bmi")
.param("height", height)
.param("weight", weight))
.andExpect(content().string(result));
URL /get/bmi
에 신장과 체중을 매개변수로 전달하여 예상하는 BMI값과 일치하는지 검증할 것이다.
예를 들어 신장=180cm, 체중=70kg이라는 값을 넣는다면
BMI = 70 / (1.8*1.8) = 21.60493827160494
하지만 2번째 자리에서 잘라낼 것이므로 예상되는 BMI지수는 21.60이다.
모든 코드를 작성하였으니 테스트를 돌려보자.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.1)
2021-01-14 17:57:47.932 INFO 1600 --- [ Test worker] c.s.s.controller.FirstControllerTests : Starting FirstControllerTests using Java 11.0.8 on Changhoon-Han with PID 1600 (started by Han in D:\Project\springboot)
2021-01-14 17:57:47.935 INFO 1600 --- [ Test worker] c.s.s.controller.FirstControllerTests : No active profile set, falling back to default profiles: default
2021-01-14 17:57:48.865 INFO 1600 --- [ Test worker] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2021-01-14 17:57:49.188 INFO 1600 --- [ Test worker] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2021-01-14 17:57:49.188 INFO 1600 --- [ Test worker] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet ''
2021-01-14 17:57:49.189 INFO 1600 --- [ Test worker] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 1 ms
2021-01-14 17:57:49.208 INFO 1600 --- [ Test worker] c.s.s.controller.FirstControllerTests : Started FirstControllerTests in 1.47 seconds (JVM running for 2.493)
MockHttpServletRequest:
HTTP Method = GET
Request URI = /get/bmi
Parameters = {height=[180], weight=[70]}
Headers = []
Body = null
Session Attrs = {}
Handler:
Type = org.springframework.web.servlet.resource.ResourceHttpRequestHandler
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 404
Error message = null
Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Response content expected:<21.6> but was:<>
필요:21.6
실제 :
당연히 실패한다. 실패하게끔 코드를 작성했기 때문이다.
왜냐하면 테스트 대상인 FirstController
에는 처리 로직이 구현되어 있지 않기 때문이다.
그래서 21.6이라는 값이 나올 거라 예상했지만, 실제로 얻은 값은 ““이므로 테스트가 실패했다는 로그가 발생한다.
그럼 이제 실제 로직을 작성해보자.
// file: 'FirstController.class'
@RestController
public class FirstController {
@GetMapping("/")
public String init() {
return "Hello! World!";
}
@GetMapping("/get/bmi")
public String calcBmi(@RequestParam("height") double height, @RequestParam("weight") double weight) {
DecimalFormat df = new DecimalFormat("#.##");
// BMI = 체중(kg) / (신장(m) * 신장(m))
double bmi = weight / Math.pow(height / 100, 2);
return df.format(bmi);
}
}
테스트를 다시 돌려본다.
컨트롤러가 예상한 대로 잘 동작하고 있다.
다음 포스팅엔 리팩토링을 진행해볼 것이다.