테스트 주도 개발(Test-Driven Development, TDD) - 3

테스트 주도 개발(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);
    }
}

 

테스트를 다시 돌려본다.

 

 

컨트롤러가 예상한 대로 잘 동작하고 있다.

다음 포스팅엔 리팩토링을 진행해볼 것이다.

 


© 2022. All rights reserved.