From 0e98fe1d9a404e676237c9574720a9b3ded371b3 Mon Sep 17 00:00:00 2001 From: hiiro <31722737+MoonJeWoong@users.noreply.github.com> Date: Thu, 14 Sep 2023 01:38:28 +0900 Subject: [PATCH] =?UTF-8?q?[MVC=20=EA=B5=AC=ED=98=84=ED=95=98=EA=B8=B0=20-?= =?UTF-8?q?=201=EB=8B=A8=EA=B3=84]=20=ED=9E=88=EC=9D=B4=EB=A1=9C(=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=EC=9B=85)=20=EB=AF=B8=EC=85=98=20=EC=A0=9C=EC=B6=9C?= =?UTF-8?q?=ED=95=A9=EB=8B=88=EB=8B=A4.=20(#390)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: reflection 학습 테스트 진행 완료 * docs: README 기능 요구사항 명세 작성 * feat: AnnotationHandlerMapping 클래스 initialize 기능 구현 * feat: AnnotationHandlerMapping 클래스 getHandler 기능 구현 * feat: HandlerExecution 클래스 handle 기능 구현 * test: user 테스트 추가 * test: servlet 학습 테스트 완료 --- README.md | 15 +++++ .../java/com/techcourse/domain/UserTest.java | 33 ++++++++++ .../mvc/tobe/AnnotationHandlerMapping.java | 42 ++++++++++-- .../servlet/mvc/tobe/HandlerExecution.java | 11 +++- .../com/example/CharacterEncodingFilter.java | 3 +- .../servlet/com/example/KoreanServlet.java | 2 +- .../com/example/LocalCounterServlet.java | 2 +- .../com/example/ServletApplication.java | 2 +- .../com/example/SharedCounterServlet.java | 2 +- .../servlet/com/example/TomcatStarter.java | 2 +- .../java/reflection/Junit3TestRunner.java | 8 +++ .../java/reflection/Junit4TestRunner.java | 7 ++ .../test/java/reflection/ReflectionTest.java | 66 +++++++++++-------- .../test/java/reflection/ReflectionsTest.java | 14 ++++ .../java/servlet/com/example/FilterTest.java | 3 +- .../java/servlet/com/example/ServletTest.java | 5 +- 16 files changed, 176 insertions(+), 41 deletions(-) create mode 100644 app/src/test/java/com/techcourse/domain/UserTest.java rename study/src/main/java/{ => reflection}/servlet/com/example/CharacterEncodingFilter.java (83%) rename study/src/main/java/{ => reflection}/servlet/com/example/KoreanServlet.java (96%) rename study/src/main/java/{ => reflection}/servlet/com/example/LocalCounterServlet.java (96%) rename study/src/main/java/{ => reflection}/servlet/com/example/ServletApplication.java (83%) rename study/src/main/java/{ => reflection}/servlet/com/example/SharedCounterServlet.java (98%) rename study/src/main/java/{ => reflection}/servlet/com/example/TomcatStarter.java (98%) diff --git a/README.md b/README.md index 35eff71457..4aebad64ba 100644 --- a/README.md +++ b/README.md @@ -1 +1,16 @@ # @MVC 구현하기 + +# 기능 요구사항 명세 +- [] 어노테이션 기반의 MVC 프레임워크를 구현한다. + - [] mvc 모듈과 app 모듈의 영역이 잘 구분되어야 한다. + - [] 새로운 컨트롤러가 생겨도 MVC 프레임워크 영역까지 영향이 미치면 안된다. + - [x] AnnotationHandlerMapping 구현 사항 + - [x] initialize 기능 구현 + - [x] basePackage 하위에 존재하는 @Controller 어노테이션 선언 클래스 탐색 + - [x] Controller 클래스 내 선언된 @requestMapping 어노테이션 선언 메서드 탐색 + - [x] 각 Method 객체를 조합으로 가지는 HandlerExecution 객체를 생성해서 저장 + - [x] getHandler 기능 구현 + - [x] HttpServletRequest 객체를 인자로 받아서 HandlerKey 객체 생성 + - [x] 생성된 HandlerKey 객체를 key로 가지는 value 값에 해당하는 handler를 반환 + - [x] HandlerExecution 구현 사항 + - [x] handler 객체에서 request, response 객체를 인자로 받아 적합한 비즈니스 로직을 수행하는 기능을 구현한다. diff --git a/app/src/test/java/com/techcourse/domain/UserTest.java b/app/src/test/java/com/techcourse/domain/UserTest.java new file mode 100644 index 0000000000..8946b6a68f --- /dev/null +++ b/app/src/test/java/com/techcourse/domain/UserTest.java @@ -0,0 +1,33 @@ +package com.techcourse.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +@SuppressWarnings("NonAsciiCharacters") +class UserTest { + + @Test + void 입력한_비밀번호가_사용자_정보와_일치하는지_확인할_수_있다() { + //given + User user = new User(1, "gugu", "password", "test@email.com"); + + //when + boolean result = user.checkPassword("password"); + + //then + assertThat(result).isTrue(); + } + + @Test + void 입력한_비밀번호가_사용자_정보와_불일치하는지_확인할_수_있다() { + //given + User user = new User(1, "gugu", "password", "test@email.com"); + + //when + boolean result = user.checkPassword("wrong password"); + + //then + assertThat(result).isFalse(); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java index a355218efa..0bb62bcc6b 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java @@ -1,11 +1,17 @@ package webmvc.org.springframework.web.servlet.mvc.tobe; +import context.org.springframework.stereotype.Controller; import jakarta.servlet.http.HttpServletRequest; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import java.lang.reflect.Method; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; +import org.reflections.Reflections; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import web.org.springframework.web.bind.annotation.RequestMapping; +import web.org.springframework.web.bind.annotation.RequestMethod; public class AnnotationHandlerMapping { @@ -20,10 +26,38 @@ public AnnotationHandlerMapping(final Object... basePackage) { } public void initialize() { + Set> controllers = getAnnotatedControllerClasses(); + for (Class controller : controllers) { + addAnnotatedHandlerExecution(controller); + } log.info("Initialized AnnotationHandlerMapping!"); } + private void addAnnotatedHandlerExecution(Class controller) { + for (Method method : controller.getMethods()) { + if (!method.isAnnotationPresent(RequestMapping.class)) { + continue; + } + RequestMapping requestMapping = method.getAnnotation(RequestMapping.class); + HandlerKey handlerKey = new HandlerKey(requestMapping.value(), + requestMapping.method()[0]); + HandlerExecution handlerExecution = new HandlerExecution(controller, method); + handlerExecutions.put(handlerKey, handlerExecution); + } + } + + private Set> getAnnotatedControllerClasses() { + Set> controllers = new HashSet<>(); + for (Object packagePath : basePackage) { + Reflections reflections = new Reflections((String) packagePath); + controllers.addAll(reflections.getTypesAnnotatedWith(Controller.class)); + } + return controllers; + } + public Object getHandler(final HttpServletRequest request) { - return null; + String requestURI = request.getRequestURI(); + RequestMethod requestMethod = RequestMethod.valueOf(request.getMethod().toUpperCase()); + return handlerExecutions.get(new HandlerKey(requestURI, requestMethod)); } } diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecution.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecution.java index 37c583fbdf..edb5beac54 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecution.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecution.java @@ -2,11 +2,20 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.lang.reflect.Method; import webmvc.org.springframework.web.servlet.ModelAndView; public class HandlerExecution { + private final Class clazz; + private final Method method; + + public HandlerExecution(Class clazz, Method method) { + this.clazz = clazz; + this.method = method; + } + public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception { - return null; + return (ModelAndView) method.invoke(clazz.getDeclaredConstructor().newInstance(), request, response); } } diff --git a/study/src/main/java/servlet/com/example/CharacterEncodingFilter.java b/study/src/main/java/reflection/servlet/com/example/CharacterEncodingFilter.java similarity index 83% rename from study/src/main/java/servlet/com/example/CharacterEncodingFilter.java rename to study/src/main/java/reflection/servlet/com/example/CharacterEncodingFilter.java index cf4d886974..b585528b0c 100644 --- a/study/src/main/java/servlet/com/example/CharacterEncodingFilter.java +++ b/study/src/main/java/reflection/servlet/com/example/CharacterEncodingFilter.java @@ -1,4 +1,4 @@ -package servlet.com.example; +package reflection.servlet.com.example; import jakarta.servlet.*; import jakarta.servlet.annotation.WebFilter; @@ -11,6 +11,7 @@ public class CharacterEncodingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.getServletContext().log("doFilter() 호출"); + response.setCharacterEncoding("UTF-8"); chain.doFilter(request, response); } } diff --git a/study/src/main/java/servlet/com/example/KoreanServlet.java b/study/src/main/java/reflection/servlet/com/example/KoreanServlet.java similarity index 96% rename from study/src/main/java/servlet/com/example/KoreanServlet.java rename to study/src/main/java/reflection/servlet/com/example/KoreanServlet.java index 0805046362..d6000be9ef 100644 --- a/study/src/main/java/servlet/com/example/KoreanServlet.java +++ b/study/src/main/java/reflection/servlet/com/example/KoreanServlet.java @@ -1,4 +1,4 @@ -package servlet.com.example; +package reflection.servlet.com.example; import jakarta.servlet.ServletConfig; import jakarta.servlet.ServletException; diff --git a/study/src/main/java/servlet/com/example/LocalCounterServlet.java b/study/src/main/java/reflection/servlet/com/example/LocalCounterServlet.java similarity index 96% rename from study/src/main/java/servlet/com/example/LocalCounterServlet.java rename to study/src/main/java/reflection/servlet/com/example/LocalCounterServlet.java index a12e27574d..4a73154c55 100644 --- a/study/src/main/java/servlet/com/example/LocalCounterServlet.java +++ b/study/src/main/java/reflection/servlet/com/example/LocalCounterServlet.java @@ -1,4 +1,4 @@ -package servlet.com.example; +package reflection.servlet.com.example; import jakarta.servlet.ServletConfig; import jakarta.servlet.ServletException; diff --git a/study/src/main/java/servlet/com/example/ServletApplication.java b/study/src/main/java/reflection/servlet/com/example/ServletApplication.java similarity index 83% rename from study/src/main/java/servlet/com/example/ServletApplication.java rename to study/src/main/java/reflection/servlet/com/example/ServletApplication.java index 495a6b3011..02b6a9b339 100644 --- a/study/src/main/java/servlet/com/example/ServletApplication.java +++ b/study/src/main/java/reflection/servlet/com/example/ServletApplication.java @@ -1,4 +1,4 @@ -package servlet.com.example; +package reflection.servlet.com.example; public class ServletApplication { diff --git a/study/src/main/java/servlet/com/example/SharedCounterServlet.java b/study/src/main/java/reflection/servlet/com/example/SharedCounterServlet.java similarity index 98% rename from study/src/main/java/servlet/com/example/SharedCounterServlet.java rename to study/src/main/java/reflection/servlet/com/example/SharedCounterServlet.java index 4c29958d52..0f33fc0f61 100644 --- a/study/src/main/java/servlet/com/example/SharedCounterServlet.java +++ b/study/src/main/java/reflection/servlet/com/example/SharedCounterServlet.java @@ -1,4 +1,4 @@ -package servlet.com.example; +package reflection.servlet.com.example; import jakarta.servlet.ServletConfig; import jakarta.servlet.ServletException; diff --git a/study/src/main/java/servlet/com/example/TomcatStarter.java b/study/src/main/java/reflection/servlet/com/example/TomcatStarter.java similarity index 98% rename from study/src/main/java/servlet/com/example/TomcatStarter.java rename to study/src/main/java/reflection/servlet/com/example/TomcatStarter.java index b91214e9c3..24a216cc2d 100644 --- a/study/src/main/java/servlet/com/example/TomcatStarter.java +++ b/study/src/main/java/reflection/servlet/com/example/TomcatStarter.java @@ -1,4 +1,4 @@ -package servlet.com.example; +package reflection.servlet.com.example; import org.apache.catalina.Context; import org.apache.catalina.LifecycleException; diff --git a/study/src/test/java/reflection/Junit3TestRunner.java b/study/src/test/java/reflection/Junit3TestRunner.java index b4e465240c..453e004888 100644 --- a/study/src/test/java/reflection/Junit3TestRunner.java +++ b/study/src/test/java/reflection/Junit3TestRunner.java @@ -1,5 +1,6 @@ package reflection; +import java.lang.reflect.Method; import org.junit.jupiter.api.Test; class Junit3TestRunner { @@ -9,5 +10,12 @@ void run() throws Exception { Class clazz = Junit3Test.class; // TODO Junit3Test에서 test로 시작하는 메소드 실행 + Method[] tests = clazz.getDeclaredMethods(); + + for (Method method : tests) { + if (method.getName().startsWith("test")) { + method.invoke(clazz.getDeclaredConstructor().newInstance()); + } + } } } diff --git a/study/src/test/java/reflection/Junit4TestRunner.java b/study/src/test/java/reflection/Junit4TestRunner.java index 8a6916bc24..64db8ce56c 100644 --- a/study/src/test/java/reflection/Junit4TestRunner.java +++ b/study/src/test/java/reflection/Junit4TestRunner.java @@ -1,5 +1,6 @@ package reflection; +import java.lang.reflect.Method; import org.junit.jupiter.api.Test; class Junit4TestRunner { @@ -9,5 +10,11 @@ void run() throws Exception { Class clazz = Junit4Test.class; // TODO Junit4Test에서 @MyTest 애노테이션이 있는 메소드 실행 + Method[] declaredMethods = clazz.getDeclaredMethods(); + for (Method method : declaredMethods) { + if (method.isAnnotationPresent(MyTest.class)) { + method.invoke(clazz.getDeclaredConstructor().newInstance()); + } + } } } diff --git a/study/src/test/java/reflection/ReflectionTest.java b/study/src/test/java/reflection/ReflectionTest.java index 370f0932b9..6dd254b26e 100644 --- a/study/src/test/java/reflection/ReflectionTest.java +++ b/study/src/test/java/reflection/ReflectionTest.java @@ -1,15 +1,17 @@ package reflection; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import static org.assertj.core.api.Assertions.assertThat; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Date; import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; class ReflectionTest { @@ -19,25 +21,27 @@ class ReflectionTest { void givenObject_whenGetsClassName_thenCorrect() { final Class clazz = Question.class; - assertThat(clazz.getSimpleName()).isEqualTo(""); - assertThat(clazz.getName()).isEqualTo(""); - assertThat(clazz.getCanonicalName()).isEqualTo(""); + assertThat(clazz.getSimpleName()).isEqualTo("Question"); + assertThat(clazz.getName()).isEqualTo("reflection.Question"); + assertThat(clazz.getCanonicalName()).isEqualTo("reflection.Question"); } @Test void givenClassName_whenCreatesObject_thenCorrect() throws ClassNotFoundException { final Class clazz = Class.forName("reflection.Question"); - assertThat(clazz.getSimpleName()).isEqualTo(""); - assertThat(clazz.getName()).isEqualTo(""); - assertThat(clazz.getCanonicalName()).isEqualTo(""); + assertThat(clazz.getSimpleName()).isEqualTo("Question"); + assertThat(clazz.getName()).isEqualTo("reflection.Question"); + assertThat(clazz.getCanonicalName()).isEqualTo("reflection.Question"); } @Test void givenObject_whenGetsFieldNamesAtRuntime_thenCorrect() { final Object student = new Student(); - final Field[] fields = null; - final List actualFieldNames = null; + final Field[] fields = student.getClass().getDeclaredFields(); + final List actualFieldNames = Arrays.stream(fields) + .map(Field::getName) + .collect(Collectors.toList()); assertThat(actualFieldNames).contains("name", "age"); } @@ -45,8 +49,10 @@ void givenObject_whenGetsFieldNamesAtRuntime_thenCorrect() { @Test void givenClass_whenGetsMethods_thenCorrect() { final Class animalClass = Student.class; - final Method[] methods = null; - final List actualMethods = null; + final Method[] methods = animalClass.getDeclaredMethods(); + final List actualMethods = Arrays.stream(methods) + .map(method -> method.getName()) + .collect(Collectors.toList()); assertThat(actualMethods) .hasSize(3) @@ -56,7 +62,7 @@ void givenClass_whenGetsMethods_thenCorrect() { @Test void givenClass_whenGetsAllConstructors_thenCorrect() { final Class questionClass = Question.class; - final Constructor[] constructors = null; + final Constructor[] constructors = questionClass.getDeclaredConstructors(); assertThat(constructors).hasSize(2); } @@ -65,11 +71,16 @@ void givenClass_whenGetsAllConstructors_thenCorrect() { void givenClass_whenInstantiatesObjectsAtRuntime_thenCorrect() throws Exception { final Class questionClass = Question.class; - final Constructor firstConstructor = null; - final Constructor secondConstructor = null; + final Constructor firstConstructor = questionClass.getDeclaredConstructor(String.class, + String.class, String.class); + final Constructor secondConstructor = questionClass.getDeclaredConstructor(long.class, + String.class, String.class, String.class, Date.class, int.class); - final Question firstQuestion = null; - final Question secondQuestion = null; + final Question firstQuestion = (Question) firstConstructor.newInstance("gugu", "제목1", + "내용1"); + final Question secondQuestion = (Question) secondConstructor.newInstance(1L, "gugu", "제목2", + "내용2", + new Date(), 0); assertThat(firstQuestion.getWriter()).isEqualTo("gugu"); assertThat(firstQuestion.getTitle()).isEqualTo("제목1"); @@ -82,7 +93,7 @@ void givenClass_whenInstantiatesObjectsAtRuntime_thenCorrect() throws Exception @Test void givenClass_whenGetsPublicFields_thenCorrect() { final Class questionClass = Question.class; - final Field[] fields = null; + final Field[] fields = questionClass.getFields(); assertThat(fields).hasSize(0); } @@ -90,7 +101,7 @@ void givenClass_whenGetsPublicFields_thenCorrect() { @Test void givenClass_whenGetsDeclaredFields_thenCorrect() { final Class questionClass = Question.class; - final Field[] fields = null; + final Field[] fields = questionClass.getDeclaredFields(); assertThat(fields).hasSize(6); assertThat(fields[0].getName()).isEqualTo("questionId"); @@ -99,7 +110,7 @@ void givenClass_whenGetsDeclaredFields_thenCorrect() { @Test void givenClass_whenGetsFieldsByName_thenCorrect() throws Exception { final Class questionClass = Question.class; - final Field field = null; + final Field field = questionClass.getDeclaredField("questionId"); assertThat(field.getName()).isEqualTo("questionId"); } @@ -107,7 +118,7 @@ void givenClass_whenGetsFieldsByName_thenCorrect() throws Exception { @Test void givenClassField_whenGetsType_thenCorrect() throws Exception { final Field field = Question.class.getDeclaredField("questionId"); - final Class fieldClass = null; + final Class fieldClass = field.getType(); assertThat(fieldClass.getSimpleName()).isEqualTo("long"); } @@ -115,15 +126,16 @@ void givenClassField_whenGetsType_thenCorrect() throws Exception { @Test void givenClassField_whenSetsAndGetsValue_thenCorrect() throws Exception { final Class studentClass = Student.class; - final Student student = null; - final Field field = null; + final Student student = (Student) studentClass.getConstructor().newInstance(); + final Field field = student.getClass().getDeclaredField("age"); + field.trySetAccessible(); // todo field에 접근 할 수 있도록 만든다. assertThat(field.getInt(student)).isZero(); assertThat(student.getAge()).isZero(); - field.set(null, null); + field.set(student, 99); assertThat(field.getInt(student)).isEqualTo(99); assertThat(student.getAge()).isEqualTo(99); diff --git a/study/src/test/java/reflection/ReflectionsTest.java b/study/src/test/java/reflection/ReflectionsTest.java index 5040c2ffa2..ecb009122d 100644 --- a/study/src/test/java/reflection/ReflectionsTest.java +++ b/study/src/test/java/reflection/ReflectionsTest.java @@ -1,9 +1,14 @@ package reflection; +import java.util.HashSet; +import java.util.Set; import org.junit.jupiter.api.Test; import org.reflections.Reflections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import reflection.annotation.Controller; +import reflection.annotation.Repository; +import reflection.annotation.Service; class ReflectionsTest { @@ -14,5 +19,14 @@ void showAnnotationClass() throws Exception { Reflections reflections = new Reflections("reflection.examples"); // TODO 클래스 레벨에 @Controller, @Service, @Repository 애노테이션이 설정되어 모든 클래스 찾아 로그로 출력한다. + Set> clazzSet = new HashSet<>(); + clazzSet.addAll(reflections.getTypesAnnotatedWith(Controller.class)); + clazzSet.addAll(reflections.getTypesAnnotatedWith(Service.class)); + clazzSet.addAll(reflections.getTypesAnnotatedWith(Repository.class)); + + for (Class clazz : clazzSet) { + String typeName = clazz.getTypeName(); + log.info(typeName); + } } } diff --git a/study/src/test/java/servlet/com/example/FilterTest.java b/study/src/test/java/servlet/com/example/FilterTest.java index 53d6236dff..978000b176 100644 --- a/study/src/test/java/servlet/com/example/FilterTest.java +++ b/study/src/test/java/servlet/com/example/FilterTest.java @@ -1,10 +1,11 @@ package servlet.com.example; import org.junit.jupiter.api.Test; +import reflection.servlet.com.example.TomcatStarter; import support.HttpUtils; import static org.assertj.core.api.Assertions.assertThat; -import static servlet.com.example.KoreanServlet.인코딩; +import static reflection.servlet.com.example.KoreanServlet.인코딩; class FilterTest { diff --git a/study/src/test/java/servlet/com/example/ServletTest.java b/study/src/test/java/servlet/com/example/ServletTest.java index 75fbb10dd5..cc574b3b6d 100644 --- a/study/src/test/java/servlet/com/example/ServletTest.java +++ b/study/src/test/java/servlet/com/example/ServletTest.java @@ -1,6 +1,7 @@ package servlet.com.example; import org.junit.jupiter.api.Test; +import reflection.servlet.com.example.TomcatStarter; import support.HttpUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -28,7 +29,7 @@ void testSharedCounter() { // expected를 0이 아닌 올바른 값으로 바꿔보자. // 예상한 결과가 나왔는가? 왜 이런 결과가 나왔을까? - assertThat(Integer.parseInt(response.body())).isEqualTo(0); + assertThat(Integer.parseInt(response.body())).isEqualTo(3); } @Test @@ -50,6 +51,6 @@ void testLocalCounter() { // expected를 0이 아닌 올바른 값으로 바꿔보자. // 예상한 결과가 나왔는가? 왜 이런 결과가 나왔을까? - assertThat(Integer.parseInt(response.body())).isEqualTo(0); + assertThat(Integer.parseInt(response.body())).isEqualTo(1); } }