- 코드숨 1주차 과제 Java로 ToDo REST API만들기
- 8월 둘째 주 회고 코드숨 2주차 과제 Spring Web 으로 ToDo REST API 만들기
- 코드숨 3주차 과제 Spring Mock MVC , 유닛 테스트 작성하기
- 코드숨 4주차 과제 TDD로 상품 도메인 구현하기
- 코드숨 5주차 과제 TDD로 사용자 도메인 구현하기 + 유효성 검사
- 코드숨 6주차 과제 JWT를 활용한 인증 구현하기
- 코드숨 7주차 과제 Spring Security 적용하기
- 코드숨 8주차 과제 Spring REST Docs 적용, Docker로 빌드 및 배포
- 코드숨 끝
- SDK 를 사용하여 자바 버전 관리
- 자바 버전 관리하는게 항상 어렵게 느껴졌는데 SDK를 통해 편하게 느껴졌다
- 처음으로 gradle을 사용하여 프로젝트 시작
./gradlew run
./gradlew test
HttpServer
API를 사용하여 로컬 서버 테스트HttpServer
API를 처음 접해보았다- 임베디드 HTTP 서버를 구축하는 데 사용할 수있는 간단한 고급 Http 서버 API
- 오라클 문서 HttpServer
- 번역 문서 HttpServer
- HttpServer를 사용하는 걸 보고 처음 접하는 API라서 당황했다...
- 생에 첫 코드리뷰를 종립님에게 받아 감격스럽다..
- 교육에서 제공하는 참고 링크들은 꼭 진득하게 읽어보자
- 참고 링크
- Jackson ObjectMapper를 사용하여 ToDo REST API 진행
- 리소스 경로 받아 안전하게 접근하는 방법, 비즈니스 로직 분리 방법 , 예외 전달 방법을 고민해보았다
- Baeldung - Jackson ObjectMapper
- ObjectMapper를 재사용해도 될까?
- URL문자열을 잘라 resource , pathVariable을 저장하는
Path
클래스를 추가Path
클래스에서는 pathVariable에 접근할 때 해당 필드가 비어있다면 예외를 던지게 만들었다- [Java] Exception vs RuntimeException을 참고하여
getPathVariable()
을 호출할 때는 강제로 예외를 잡아 처리하게 만들었다
- 동시성 이슈와 효율성 관련하여
LinkedList
→ConcurrentHashMap
으로 변경 - taskId는 동시성을 위해
AtomicLong
으로 관리 - RFC 7231
- RFC 란?
- 요구 사항 수준을 나타내기 위해 RFC에서 사용하는 키워드
- RFC를 지나가면서 얼핏 봤던 것 같은데 뭔지 확실히 알게 되었다
- Logger.getLogger({name})
- 해당
{name}
으로 로거가 있다면 해당 로거를 반환한다 ConcurrentHashMap namedLoggers
로 관리되고 있다
- 해당
- 객체의 Setter를 제거하게 되면 객체가 생성된 후 값을 넣어줘야할 때는 어떻게 해야할지 고민
- What is the difference between
atomic
/volatile
/synchronized
?
💡
- 외부에 객체의 상태를 변경할 방법을 제공하는 것이기 때문에 객체의 상태가 언제 어디서 어떻게 변할지 예측할 수 없다.
- 복잡한 비즈니스 로직을 제공하는 서비스나 협업하는 상황에서는 setter를 제공하는 객체의 상태 변화에 대한 추적이 더욱 어려워진다.
getXXX
로 클래스의 필드를 가져오는것이 아니라 클래스에게 비교할 정보를 주고 결과만 반환받게 수정- ex)
Path.resourceEquals({비교 문자열})
- ex)
HttpMethod
enum 추가equal
메서드를 추가하여 1번과 같이 비교할 문자열을 enum에서 받아 결과를 반환하는 메서드를 추가
Path
클래스에 원본 문자열을 저장하는 필드 추가- 리뷰 내용 Path 클래스는 path를 감싸서 특정한 서비스를 제공하는 녀석입니다. 원본을 갖고 있다면 디버깅할 때 유용할 거에요.
JSON
↔︎Task
작업- Reflection을 사용하여
Task
의 필드에 접근 - 정규표현식을 사용하여
JSON
을 나눠보았다
- Reflection을 사용하여
- 외부에서 정보를 꺼내쓰기 보다는 객체 내부에서 판단이 가능하다면 내부에서 처리하게 하자
- AutoCloseable 클래스를 알게되었다
- [Java] try-with-resources
- 종립님이 남겨주신 리뷰
HttpMethod
,HttpResponse
enum에 JavaDoc 주석작성Task
를 JSON문자열로 변경할 때 변경해주는 메서드는 어디에 작성하면 좋을까?Task
model 내부?? ,TaskConverter
컨버터안에??- Task를 위한
Converter
라는 클래스가 따로 존재하니 일단TaskConveter
에 작성
- 응답 헤더 Content-Type 설정
- 객체 → JSON을 직접 반환하니 한글이 깨졌다
- SDK를 사용한 자바 버전 관리
- 스프링이 아닌 자바만을 사용하여 REST API를 개발
- 모델의 Setter를 제거하고 불변 클래스로 만들기
- Exception과 RuntimeException 차이
volatile
키워드- enum
- RFC (Request For Comments)
- JavaDoc 작성
- 확실한 문서를 레퍼런스로 남길 것
- 그냥 개발이 아니라 레퍼런스 문서를 토대로 실천하는 활동이다
- 의사결정을 글로 작성하는 연습
- Jacskon을 사용할 때는 모델의 기본 생성자가 필요하다
- 객체지향적 고민
- 객체에게 데이터를 요구하지 말고 작업을 요청하라
- 테스트 코드 작성
@DisplayName
에 대한 표현은 분명하게 표현하도록 하자- 테스트 코드를 읽을 상대의 가독성을 고려하자
- Return Early Pattern
- 언제부터 인지 필드에 직접 접근하는 것은 절대 안된다!라고 생각했다
final
로 선언돼 있다면getter
를 만들지 않고 직접 접근하는것도 괜찮다
과제를 진행하면서 처음부터 좋은 코드를 작성하려 노력했던 것 같다
실제로 좋은 코드도 아니었다
- 점진적인 개선
일단
만든다일단
되게한다- 리팩토링 한다
- 위의 과정을 반복한다
과제를 진행하며 위의 과정을 거치면서 점진적인 개선에 대해 조금 깨달은 것 같다
처음부터 너무 많은 것을 생각할 것이 아니라 위의 과정을 생각하자
1주차 과제가 끝이 났지만 내가 작성한 코드가 마음에 드는 것은 아니다
고쳐야할 부분도 많지만 항상 점진적인 개선을 의식하자
---- 리뷰 내용 중 ----
그냥 기계를 굴러가게 하기만 하는 것이 아닙니다.
레퍼런스 문서를 토대로 실천하는 활동이기도 하다는 점을 곱씹어보세요.
공식 문서의 힘을 끌어다 내 코드에 연결할 수 있다고 생각해 보세요.
그냥 기계를 굴러가게만 하는 것이 아니라는 것을 기억하자
-
- 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다
- OPTIONS
- Preflight request
- HTTP 헤더
- Vary
- 2주차 과제를 진행하면서 만나게 됐다
- API Server Port =
8080
- Web Server Port =
3000
3000
번에서8080
번에 접근하니 CORS 발생
- API Server Port =
- Enabling Cross Origin Requests for a RESTful Web Service
- Spring
@CrossOrigin
- Spring
-
Spring devtools 2.3.5.RELEASE
-
리뷰에서 DTO에 대한 얘기가 나와 조사 해보았다
- DTO 정리 (
+ POJO , JavaBean
)
- DTO 정리 (
-
- Controller에서
@ResponseBody
로TaskDTO
를 JSON → 객체로 변환하여 사용하였다 - 하지만
TaskDTO
를 불변 클래스로 변경해보자는 리뷰를 받았다 - AbstractJackson2HttpMessageConverter에서 ObjectMapper를 사용하여 변환 할 클래스의 기본 생성자를 사용하는 것을 알아 불가능한 상황인 줄 알았다
- 그래서
Converter
에 대해 조사하길 원하시는 줄 알았다
- 그래서
- 하지만 , Jackson ObjectMapper에서 기본 생성자 없이 Deserialization 하기를 보면 가능하다고 나와있다
github
Jackson Annotations@JSONCreator
@JSONProperty({name})
github
Jackson data-bindgithub
Jackson docsmedium
Customize your Java-JSON serialization using Jackson Annotations
- Controller에서
-
DTO → Domain으로 변환해주는 Mapper는 어느 계층에 있어야할까?
- Converter를 사용하여 컨트롤러에서
DTO
객체를 받았다면 컨트롤러의 역할은 끝났다고 생각하여 , 서비스에서 주입받았다 - DDD - which layer DTO should be implemented
- Converter를 사용하여 컨트롤러에서
-
Abstract Factory Pattern추가
-
DDD의 Factory 조사
-
IdGenerator
수정Long
을 반환하는generate
메서드 범용적으로 수정<T>
-
TaskServiceInterface
를CRUDInterface<In , Out>
으로 수정
public class TaskService implements CRUDInterface<TaskDTO , Task>
- service에서 해당 인터페이스를 구현할 때 타입을 지정해주게 수정
DTO
나model
이 수정되거나 추가되면 TaskDTO , Task를 감쌀 수 있는 인터페이스나 상속이 추가 되어야 겠지만 일단 수정해 보았다- List Interface가 Collection을 확장하고 있어서
new ArrayList
로 공간을 새로 만들지 않고 반환 타입을Collection<V>
로 수정
public List<Task> getAllTask(){
return new ArrayList<>(tasks.values());
}
// public interface List<E> extends Collection<E>
public Collection<Task> selectAll(){
return tasks.values();
}
- ExceptionResolver 복습 및 적용
- 2주차 과제에서 상황에 맞는 응답 코드를 내려주어야 하는데 지금은 null 체크를 하며 처리해주었다
- 스프링을 사용하여 처리하기 위해 복습
- 스프링은 예외가 나면 ExceptionResolver를 뒤져서 해결할 수 있는 예외인지 확인한다
- 해결이 가능하면 직접 해결하고 , 해결하지 못 하면 WAS까지 올라가서 WAS에서 다시
/error
로 내려온다 - 과제에
@ExceptionHandler
적용- 해당 예외 클래스의 자식 클래스까지 잡을 수 있다
- 다른 컨트롤러에 영향을 주지 않는다
- Http 네트워크 스터디
- 6장. HTTP 헤더
- TDD 읽기
- 점진적인 개선이 강조된다
- 불변 클래스에 대한 이야기 (
value object pattern
) - 팩토리 메서드가 사용된다
- 패턴 정리 (팩토리 메소드 , 추상 팩토리)
- CORS
- Spring
@CrossOrigin
- POJO , JavaBean , DTO
- 공식 문서를 보며 Optional을 간단하게 사용해 보았다
- 단순 null체크는 Optinal을 굳이 사용하진 말자
- Jackson Annotation
docs Spring
ResponseEntityResponseEntity.{status}.build()
ResponseEntity.of()
- 이번 2주차 과제에서는
interface
를 적극적으로 사용하면서 구현과 명세를 나누어 보았다- 범용적인 인터페이스 (
CRUDInterface
)를 만들어 볼려고 시도하였지만 마음에 들지 않는다
- 범용적인 인터페이스 (
- ExceptionResolver 복습 및 적용
- 그림으로 배우는 HTTP 네트워크 1장 ~ 6장
- 프록시 , 게이트웨이의 차이
- 프록시의 종류
- HTTP 헤더 종류
- TDD 1장 ~ 10장
- 점진적인 개선은 TDD 책에서도 계속 강조 되었다
- Factory를 정리하면서 DDD에 대해 알게되었고 도메인 주도 개발 시작하기책을 구매하였다
- 구글링해서 조사하기 보다는 책을 한번 읽어보고 싶었다
- Jackson Annotation에 대해 이번에 처음 알게 되었는데 굉장히 신기했다
- 기본 생성자 없이 생성자를 직접 지정해주고 키 필드 이름까지 지정이 가능했다..
- 네트워크에 대한 개념이 많이 부족하다
- 캐시 관련 설정은 굉장히 중요하다!!!
- HTTP 헤더를 배워도 크게 와닿지 않는다
- 과제를 진행하면서 자바가 부족하다는 걸 많이 느낀다
- 가능하다면 DTO , model 정보들은
final
을 붙이도록 노력하자 - 내가 사용하는 메소드의 내부는 자세히 확인하자
// 수정 전
return new ArrayList<>(tasks.values());
// 수정 후
public Collection<Task> selectAll(){
return tasks.values();
}
- 점진적인 개선 중요하다... 계속 강조된다
- 피드백을 받아도 사람마다 느끼는 것이 다 다르다
- 공부방 회고에서 비행기 조종사 피드백 이야기 ( + 통찰력)
- 인터페이스의 모든 메소드에는 주석이 있어야 한다!!!
- 이번 주 리뷰의 큰 맥락은 (스스로 느끼기엔) 계층별 클래스의 목적과 책임이라고 생각한다
- 이 클래스는 어디서 주입받아 사용하는게 맞을까 ?? 어디까지 영향을 줘야할까 ??
- 스스로 결정하며 개발하면서 맞는지 안맞는지는 확실히 잘 모른다
- 중요한건 내가 어떤 생각으로 왜 이렇게 작성했는지 생각을 하게 되는 것 같다
- 코드숨 3주차 과제 스프링 테스트 작성하기 시작
Junit5
user guide- JVM 환경에서 테스트를 실행할 수 있도록 테스트 엔진을 제공하고, 우리가 테스트를 작성하고 실행할 수 있도록 지원하는 도구
- AssertJ
- 표현력이 풍부한 단정문을 사용하면 테스트를 더 읽기 쉬워서 테스트 코드를 더 유지보수하기 좋다
daleseo
AssertJ 소개github docs
AssertJ
- Fixture란 테스트에 사용되는 데이터의 모음
- Spring Test
Spring Boot docs
TestingSpring docs
Testing the Web Layer@SpringBootTest
vs@WebMvcTest
vs@AutoConfigureMockMvc
🚩@WebMvcTest
관련 문제@WebMvcTest
를 사용하여 MockMvc를 주입받아 사용해 보았다- 이때, 문제는 Controller만 만들어준다. Controller는 Service를 주입받아 사용하지만
@WebMvcTest
는@Service
를 로딩해주지 않는다
- Mockito
- Mock(
진짜 객체 처럼 동작하지만 프로그래머가 직접 컨트롤 할 수 있는 객체
)을 지원하는 프레임워크 - Mock 객체를 만들고 관리하고 검증 할 수 있는 방법 제공(가짜 객체를 만들어준다고 생각)
baeldung
Mockito Tutorial 👍번역본
mockito docs
- Mock(
johngrib
JUnit5로 계층 구조의 테스트 코드 작성하기참고하여 계층형 테스트 코드 작성TaskControllerNestedTest
TaskControllerWebTest
Spring docs 2.3.5
ReferenceSpring docs
Auto-configured JSON TestsSpring docs
Auto-configured Spring MVC Testsjojoldu
SpringBoot @MockBean, @SpyBean 소개- 3주차 과제를 진행하면서 mockito를 사용하게 되었다
- 실제로는 스프링이 감싼 mockito이다
- mockito를 사용하는 이유를 보자
Mockito는 개발자가 동작을 직접 제어할 수 있는 가짜(Mock) 객체를 지원하는 테스트 프레임워크이다.
일반적으로 Spring으로 웹 애플리케이션을 개발하면, 여러 객체들 간의 의존성이 생긴다.
이러한 의존성은 단위 테스트를 작성을 어렵게 하는데, 이를 해결하기 위해 가짜 객체를 주입시켜주는 Mockito 라이브러리를 활용할 수 있다.
Mockito를 활용하면 가짜 객체에 원하는 결과를 Stub하여 단위 테스트를 진행할 수 있다.
**물론 Mock을 하지 않아도 된다면 하지 않는 것이 가장 좋다.**
[출처 MangKyu's Diary](https://mangkyu.tistory.com/145)
------------------------------------------------------------------------
Mock은 껍데기만 있는 객체를 얘기합니다.
인터페이스의 추상메소드가 메소드 바디는 없고 파라미터 타입과 리턴타입만 선언된 것처럼, Mock Bean은 기존에 사용되던 Bean의 껍데기만 가져오고 내부의 구현 부분은 모두 사용자에게 위임한 형태입니다.
즉, 해당 Bean의 어떤 메소드가 어떤 값이 입력 되면 어떤 값이 리턴 되어야 한다는 내용 모두 개발자 필요에 의해서 조작이 가능합니다.
[출처 jojoldu](https://jojoldu.tistory.com/226)
- 위의 글과 같이 가짜 객체를 주입하여 , stub클래스들만 만들어서 진행한다고 한다
- 그럼 지금은
Controller
,Service
,Advice
나 구현이 다 된 상태이니 굳이 mockitor를 사용해야할까??라는 의문이 들었다 - 그러나
MockMvc
를 사용하여 등록되는Controller ← Service
,Advice 등록
과 같은 주입을 어떻게 해줘야할지 몰라서 고생했지만 cozzin님의 코드를 보았다
@BeforeEach
void setup() {
final TaskRepository repository = new TaskRepository();
repositoryCleaner = new TaskRepositoryCleaner(repository);
controller = new TaskController(new TaskService(repository));
mockMvc = MockMvcBuilders
.standaloneSetup(controller)
.setControllerAdvice(new TaskErrorAdvice())
.build();
}
- 네트워크
- CGI(Common Gateway Interface)의 문제점을 해결하는 기술이 Java의
서블릿
이다
- CGI(Common Gateway Interface)의 문제점을 해결하는 기술이 Java의
- Apple의 #gotofail SSL 보안 버그는 쉽게 예방할 수 있었습니다.
if
문 내부가 한 줄이라도 중괄호는 무조건 작성하자
D-C-I
계층형 테스트 코드 작성 시- Context는 실험을 준비하는 곳
- @Test는 실험을 실행하는 곳
- 준비 영역과 실행 영역을 나누자
Spring docs
MockMvcBuilders@MockBean
을 사용하여 Service를 가짜 객체로 사용하였다
@MockBean
private TaskService service;
@MockBean
private TaskErrorAdvice advice;
@BeforeEach
void setUp() {
controller = new TaskController(service);
mockMvc = MockMvcBuilders
.standaloneSetup(controller)
.setControllerAdvice(advice)
.build();
}
List<Task> setTask(){
List<Task> tasks = new ArrayList<>();
Task task1 = new Task(1L , "TEST1");
Task task2 = new Task(2L , "TEST2");
tasks.add(task1);
tasks.add(task2);
given(service.getTasks()).willReturn(tasks);
given(service.getTask(1L)).willReturn(task1);
given(service.getTask(2L)).willReturn(task2);
return tasks;
}
- 다른
@Test
의@BeforeEach
에서 setTask() 메서드를 호출한다 - 하지만 service는 최초 생성되고나서 계속 유지되기 때문에 각
@Test
메소드 별로 격리가 되지 않는다- 다른 테스트에서 service에 데이터를 넣었다면 다른 테스트에서도 보인다
- How to clean up mocks in spring tests when using Mockito ⭐️ 해결~~
import static org.mockito.Mockito.reset;
@BeforeEach
void setUp() {
reset(service);
controller = new TaskController(service);
mockMvc = MockMvcBuilders
.standaloneSetup(controller)
.setControllerAdvice(advice)
.build();
}
- 🚀 ⭐️ mock (
given
) 관련 리뷰 - 스코프가 있는 테스트 코드 스타일의 논리적인 인과를 잘 마련하자 리뷰
- 테스트 코드
Context
스코프 수정- 중복 제거 보다는 논리적인 인과에 집중하였다
- Service 유닛 테스트 코드 작성
- Model 유닛 테스트 코드 작성
codechacha
Java - Runnable과 Callable의 차이점 이해하기tedblob
Map vs MultiValueMap differences
- Junit5 , AssertJ , Mockito
- MockMvc , @WebMvcTest
- @MockBean을 reset하는 방법
- MockMvcBuilders
- verify
baeldung
mockito argument matcherD-C-I
계층 구조의 테스트 코드 작성- XSS , CSRF
- 추가로 JWT를 읽어보았다
- "도메인 주도 개발 시작하기" 읽는 중
- mockito를 사용하게 될 상황을 고민해봤다
- 이번 주는 간단한 처리이기도 하고 이미 구현이 다 돼있는 상황이라서 "mock을 사용해야 할까?" 고민했다
- 그래서 Spring DI , mock 둘 다 작성해봤다
- 객체의 행위를 외부에서 지정해주니 Stub으로 껍데기만 짜고 테스트하기 편했다 (API를 몰라서 고생했지만)
- mock관련 리뷰를 통해
given
을 언제 써야할지 대략 감은 오지만 복잡한 로직의 테스트 코드를 작성해보지 않아 와닿지 않는다 - 네트워크 스터디에서 CSRF 방어하는 방법을 들었다 (OAuth 2.0)
- 클라이언트 → 자원에 접근할 서버
- 자원에 접근할 서버 → 클라이언트에게 난수를 응답하고 난수를 세션에 저장
- 클라이언트 → 자원 소유 서버 (+ 난수)
- 자원 소유 서버 → 클라이언트에게 난수 + 코드를 응답
- 클라이언트 → 자원에 접근할 서버 난수 + 코드를 전달 (코드가 서로 일치하지 않는다면 거부됨)
- 자원에 접근할 서버 → 자원 소유 서버에게 난수 + 코드를 검증받고 접근 토큰을 받는다
- JWT를 왜 사용하는지 알게 되었고
Refresh Token
의 역할을 알게 되었다
호출
↔︎역할
- 테스트 코드를 작성할 때 호출 부분과 역할 부분을 분리해서 생각하고 작성하자
- 리뷰의 내용이기도 하고 TDD책 에서도 이렇게 작성했다
- 이렇게 호출해야지 라고 먼저 작성하고 후에 역할을 작성한다
- 테스트 코드 작성 시
- 논리적인 인과에 집중하자
- 읽는 사람을 배려하자
- 단어 선택이나 문맥을 고려하자
- TDD를 통해 4주차 과제 진행
- 유닛 테스트
- 통합 테스트 X
@DataJpaTest
- Spring Data Jpa는 영속성 컨텍스트 flush , merge를 언제 진행할까??
- 보기에는 불필요한 널 체크
Objects.requireNonNull()
를 왜 사용하는지 - 유닛 테스트와 통합 테스트의 개념
- 불필요한 테스트 코드는 작성하지 말기
- DAO vs Repository Pattern
@Trnsactional(readOnly = true)
이 readOnly 는 꼭 지켜야 하는 명세가 아니다Robert C. Martin
The Clean Code Blog
- Use Cases : 비즈니스 로직과 연관 , 이 어플리케이션이 어떻게 작동하고 어떻게 사용한다
- 의존성의 방향
- Service →
Repository
← impl Repository
- Service →
- 도메인 주도 설계에서 Service라는 개념은 도메인 레이어나 DB 레이어나 여러 곳에서 사용된다
- Service(도메인 객체를 사용하는 곳)는 여러 곳에서 사용되기 때문에 상위 패키지를 application으로 정한다
- Domain의 Entity와 RDB의 Entity는 다르다
- DAO는 데이터 지속성의 추상화입니다. 그러나 저장소는 개체 모음의 추상화입니다.
- DAO는 스토리지 시스템에 더 가까운 하위 수준 개념입니다. 그러나 Repository는 더 높은 수준의 개념으로 Domain 객체에 더 가깝습니다.
- DAO는 데이터 매핑/접근 계층으로 작동하여 못생긴 쿼리를 숨깁니다. 그러나 리포지토리는 도메인과 데이터 액세스 레이어 사이의 레이어로, 데이터를 대조하고 도메인 객체를 준비하는 복잡성을 숨깁니다.
- DAO는 저장소를 사용하여 구현할 수 없습니다. 그러나 저장소는 기본 저장소에 액세스하기 위해 DAO를 사용할 수 있습니다.
- 커스텀 예외를 추가했었는데 해당 예외가 무슨 의미인지 질문을 주셨었다
- 주석을 달지 않아 무슨 생각으로 작성했었는지 잘 모르겠다..
- 주석 좀 달자
- TDD에 대한 방향을 못 잡았다
- **Service.save()**메서드 테스트 코드 작성 중에
null
객체에 대한 테스트 코드를 작성하고 있었다 null
을 잡아서 예외를 Controller까지 던져서 잡아야할까?? 잡는다면 Advice에 추가해야할까??- 미로에 갇힌 느낌이였다. 진행하면서
"뭐를 해야하지?"
라는 생각에 많이 잠겼다
- **Service.save()**메서드 테스트 코드 작성 중에
- 그래서 Service와 Controller 테스트 코드를 같이 짤려고 하는데 Controller는 어디까지 신경써야할까??
- Controller의 메서드만?
- Request , Response , Advice ?
- 저걸 다 합치면 통합 테스트가 아닌가?
- 일단 Service를 모킹하고 컨트롤러의 메소드에만 집중하자
Q
Contoller에서 **mock(Service)**를 해서 테스트 코드를 작성 하면서 Service를 개발하면 이 개발한 코드는 어떻게 입증하나요?
A
각각의 유닛 테스트 (Controller , Service ...)들을자기의 역할에만 집중해서 테스트 코드를 짜며 개발한다
그 후 통합 테스트 (모킹하지 않고)를 진행하면 된다
- 부족하다고 느낌
관심사의 분리에 대한 개념
도메인에 대한 개념
- 학년의 도메인에 0학년 , 7학년 정보가 들어올 수 없는 것처럼
불필요한 테스트 코드 작성
- 테스트 코드는 명세를 작성하는 것
- 불필요한 테스트 코드를 작성하면 이걸 읽는 사람들이 해당 테스트를 염려한다
TDD 작성 시 탑 다운 , 바텀 업
- 컨트롤러 테스트 코드를 작성하기 전에 서비스 테스트 코드를 작성했는데 서비스가 어떻게 쓰여야할지 확실히 정할 수 없다
- 계층을 넘어다니면서 테스트코드를 작성하자
- 2주차 때 실수했던 것을 또 실수 했다
- 확인하지 않고 형변환을 쓰거나
new
로 만들어버리지 말자 - IDE의 자동완성을 곧이 곧대로 쓰지말자
- 확인하지 않고 형변환을 쓰거나
- TDD가 무슨 개념인지 확실히 몰랐지만 이번 과제를 진행하면서 조금 감은 오는 것 같다
- 테스트 대상에 집중하자
docs oracle
Using Bean Validation Constraints- JSON 직렬화 , 역직렬화에서 특정 필드 무시하기
- 자원 생성 , 수정 각 상황에 맞는 DTO 추가
- JavaDoc을 작성하는 방법
- 자바 빌트인 클래스들을 참조
String.class
johngrib
JavaDoc 작성하기- 목표는 특정 코드 덩어리의 대략적인 역할을 3초 안에 파악할 수 있도록 도와주는 것이다.
- 메소드가 무엇을 입력받아서 무엇을 리턴하는지를 반드시 설명한다.
- 구현과 주석이 커플링이 생기지 않도록 한다.
- 주석 상속 규칙
- 자바 빌트인 클래스들을 참조
@AllArgsConstructor
,@RequiredArgsConstructor
주의 정리- 모킹을 제거하고 Controller 통합 테스트
- Serivce 목적에 맞게 분리 리뷰
QueryService
CommandService
johngrib
SOLID
코드숨 과제를 통해 배우고 정리했던 내용 덕분에 토프링 읽기모임에서 나왔던 내용들이 금방 이해됐다.
JavaDoc , 주석의 필요성을 느꼇다
주석은 구현에 의존되게 작성하지 말자
테스트 코드를 작성할 때 모킹을 쓰지 않는 환경에 적응하자
미리 테스트를 만들어 두는 경우에는 까먹지 않게 일부러 테스트를 실패하게 하자
간편해서 막 쓰는 어노테이션들을 세심하게 살펴보자
생성자 어노테이션을 막 작성한것이 그 예다
한 번에 알아볼 수 있도록 주석을 작성하려 노력하자
JLS를 애용하자
johngrib
테스트 코드와 반증가능성에 대한 메모- 과제를 진행하면서 이해 안가는 상황이 있다.. 🚩
- Jpa Repository가 Repository Interface와 CrudRepository Interface를 확장하고 있다
- Service클래스가 Repository Interface를 주입받고 사용하고 있다
- Spring Data JPA에서 제공하는 interface를 확장하면 제공되는 쿼리를 쓸 수 있다는 것만 알았는데..
- 그러면 Jpa Repository를 주입받아서 사용해야 하지 않나..?
- 어떻게 Repository Interface 쓸 수 있는 것일까?
- 거스름돈 같은 문제는 그리디로 풀었었는데 동전 2같은 문제는 그리디로 풀 수 없다고 한다. 왜?
- 화폐의 가치가 배수가 아니라서 그렇다
- 그리디로 풀면 동전 개수 최소값을 구할 수 없다
- DP배열을 초기화하는 아이디어가 부족했다
- 화페의 인덱스에
1
을 표시만 하려고 했지 배수로 그 금액을 만들 수 있는 화폐의 개수는 카운트 할려고 하지 않았다...
Arrays.fill(dp , MAX);
dp[0] = 0;
for(int i = 1 ; i <= N ; i++){
int coin = Integer.parseInt(br.readLine());
for(int j = coin ; j <= K ; j++){
dp[j] = Math.min(dp[j] , dp[j - coin] + 1);
}
}
- JJWT 라이브러리를 사용하여 인코딩, 디코딩을 통해 사용자 인증하기
- 로그인 실패 시
- UserNotFoundException
- WrongPasswordException
- 인터셉터를 추가하여
product
경로GET
메서드를 제외하고 JWT 인증을 적용 시켰다 @Value("${jwt.secret}") String secret
- application.yml에 있는 속성 정보 사용하기
- Request에서 헤더 Authorization 토큰 값만 꺼내기
- 빈 검증 실패 시 에러 메세지 커스텀하기
- 위의 방법도 있고 Advice에 예외
ConstraintViolationException.class
를 정의해서 잡는 방법도 있다
- 위의 방법도 있고 Advice에 예외
- JwtUtil 단위 테스트에서는 디코딩이 잘 되는데 왜 Product 통합 테스트에서는 예외가 발생할까..?
- 테스트 코드에서 사용한 Secret 과 서버에서 사용한 Secret키가 달랐다....
- JWT의 Header , Payload , Signature 에 대해 확실하게 알게 되었다
- 이번 주 과제 마지막 리뷰에서
특정 도메인 서비스를 위한 한 가지의 기능만 가진 인터페이스
얘기를 해주셨다- 함수형 인터페이스 얘기도 해주셨는데 자바 8을 겉핥기 식으로 읽어서 다음 과제에서도 이어서 얘기하고 싶다
- Java-JWT
mozilla
Authorization- JWT RFC7519
velopert
[JWT] JSON Web Token 소개 및 구조Awdsd
JWT 저장소에 대한 고민(feat. XSS, CSRF)baeldung
SRF With Stateless REST API- 세션 기반 인증과 토큰 기반 인증 (feat. 인증과 인가)
- 토근 기반 인증에서 bearer는 무엇일까?
- JWT (JSON Web Token) 간단히 훑어보기
- JSON Web Token이 가진 한계점들
- Token Expired Issue : 순수하게 JWT만 사용하여 인증을 처리하게 된다면 다중 로그인 상황을 구분할 수가 없다
- When JWT is stolen.. : JWT가 유출되면 위험하다
baeldung
Custom Error Message Handling for REST API- REST-assured
Spring Docs
MockMvcResultMatchersjsonPath()
handler()
Github
JsonPath- Spring Security에서 GrantedAuthority vs Role
Wikipedia
BcryptMDN
CSRF ,Wikipedia
CSRF- 신뢰할 수 있는 사용자를 가장하여 웹사이트에 원치 않는 명령을 보내는 공격
-
6주차 과제에서 인증을 인터셉터를 사용해서 했는데 이번엔
스프링 시큐리티
를 적용했다 -
정리를 통해 어떤 프레임워크인지 조금 감이 왔다
- 해당 과제에서는 메서드 주석 , 내장 표현식을 사용하였다.
- SecurityContextHolder를 더 이해해야 할 것 같다.
6주차 과제에서 인증을 인터셉터를 사용해서 했는데 이번엔
스프링 시큐리티
에서 해보자
-
스프링 시큐리티 의존성을 추가하면 웹 접속 시 자동으로 로그인 화면이 생성된다
- 우리는 필요 없으니 제거하자
-
스프링 시큐리티를 직접 구현하기는 힘드니 제공하고 있는 추상화 클래스(
WebSecurityConfigurerAdapter
)를 사용하자
- JWT 인증 필터를 구현하여 스프링 시큐리티에 등록하자
- 필터를 처음부터 구현하지 않고
BasicAuthenticationFilter
를 상속받아 사용했다BasicAuthenticationFilter
: HTTP 요청의 Authorization 헤더를 처리하여 SecurityContextHolder에 처리한 결과를 담습니다.AuthenticationManager
: AuthenticationManager는 Spring Security의 인증 필터를 정의할 수 있는 API입니다.
- 필터는 서블릿 단에서 처리되기 때문에 스프링 어드바이스에서 잡을 수 없다.
- 아래와 같이
authenticationFilter
에서 토큰을 분석하는데 해당 필터에서 InvalidTokenException이 발생할 수 있기 때문에 해당 예외를 잡아주는 필터가 더 필요하다. authenticationErrorFilter
라는 전처리기 필터를 추가해서 잡아주자.
- 아래와 같이
SecurityJavaConfig.class
@Override
public void configure(HttpSecurity http) throws Exception {
Filter authenticationFilter = new JwtAuthenticationFilter(authenticationManager(), authenticationService);
Filter authenticationErrorFilter = new AuthenticationErrorFilter();
http.csrf().disable()
.addFilter(authenticationFilter)
// authenticationFilter를 필터로 등록
// authenticationFilter의 전처리 필터 authenticationErrorFilter를 등록
.addFilterBefore(
authenticationErrorFilter,
JwtAuthenticationFilter.class
);
}
- 필터에서 토큰을 분석하고 아래와 같이 분석된 정보를 컨트롤러에서도 사용할 수 있게 담아주자
- 컨트롤러에서는 Authentication 타입을 받아주면 사용할 수 있다
JwtAuthenticationFilter.class
...
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication({Authentication});
...
- Authentication 타입의 객체 만들기
Spring Docs
Authentication 인터페이스를 구현하지 말고Spring Docs
AbstractAuthenticationToken 추상화 클래스를 상속받자
- @EnableGlobalMethodSecurity
- 어노테이션 기반의 보안을 적용할 수 있도록 해주는 어노테이션이다.
prePostEnabled
속성을true
로 설정하면 우리의 메서드에 PreAuthorize 어노테이션을 사용하여 Role기반의 제약을 사용할 수 있다.Spring Reference
Spring SecuritySpring Reference
Access Control using @PreAuthorize and @PostAuthorize
- 위의 설정을 따로 해주면 403 Forbidden 상태가 반환된다
- 그럼 이 상태를 그대로 반환할 것인지? 아니면 커스텀할 것인지? 결정하는 것
- 아래와 같이
Authentication
관련 에러들은 뭐든지HttpStatus.UNAUTHORIZED
이걸로 바꿀 수 있다
- 아래와 같이
SecurityJavaConfig.class
@Override
public void configure(HttpSecurity http) throws Exception {
Filter authenticationFilter = new JwtAuthenticationFilter(authenticationManager(), authenticationService);
Filter authenticationErrorFilter = new AuthenticationErrorFilter();
http.csrf().disable()
.addFilter(authenticationFilter)
.addFilterBefore(authenticationErrorFilter , JwtAuthenticationFilter.class)
.exceptionHandling()
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
}
Wikipedia
Bcrypt- 암호화된 메시지를 읽을 수 있도록 다시 평문으로 변환하는 것을 복호화라고 부른다.
- 그런데 복호화를 할 수 없는 암호화가 존재한다.
- 가장 대표적인 예가 bcrypt다.
- 사용자의 비밀번호를 데이터베이스에 저장할 때 주로 쓰인다.
- 한번 암호화된 비밀번호는 복호화 할 수 없어 원본 비밀번호를 알 수 없다.
- 해시 함수
- 정해지지 않은 길이의 문자열을 입력받아
고정된 크기의 결괏값
을 출력하는 함수이다. - 단방향 함수(one-way function) 여야 한다.
- 즉 메시지
m
을 알면h(m)
을 구하기는 쉽지만,h(m)
을 을 만족하는x
를 찾는 것은 구할 수 없어야 한다. - 어떤 값을 계산할 수 있지만 그 역을 구하는 것은 어려운 함수를 말한다.
- 정해지지 않은 길이의 문자열을 입력받아
- 충돌 저항성(collision resistance)
- 충돌이란 두 개의 서로 다른 입력 m1과 m2가 있을 때
h(m1) = h(m2)
가 되는 상황을 말한다. - 해시 함수의 입력은 무한하므로, 이와 같은 충돌을 수없이 만들 수 있다.
- 그래서 해시 함수에 완벽한 충돌 회피란 없다.
- 충돌이란 두 개의 서로 다른 입력 m1과 m2가 있을 때
- 역상 저항성
- 해시 함수의 결과를 이미 알고 있다. 그때 이 결과를 만드는 입력값 m을 찾는 것이 얼마나 어려운지를 나타낸다.
- 제 2 역상 저항성
- 입력값 m을 이미 알고 있는 상태에서
h(m) = x
을 만족하는 x를 얼마나 찾기 쉬운지를 나타낸다.
- 입력값 m을 이미 알고 있는 상태에서
johngrib
OAuthbuzzvill
멀티리포 vs 모노리포- 7주차 과제 풀이
user
자신의 정보만 수정 가능하게- ROLE 엔티티 추가 후 사용자마다 가지고 있게 변경
- Spring Security Filter에서 로그인 시 사용자의 ROLE 정보 모두 조회 후 SimpleGrantedAuthority를 추가 해줬다
Spring Reference
SecurityContextHolderRFC1945 section11.1
Basic Authentication SchemeSpring Docs
OncePerRequestFilterSpring Reference
Access Control using @PreAuthorize and @PostAuthorize
- 테스트 코드를 작성할 때 픽스처를 작성하면서
final
로 선언할 수 있는 것들은 클래스의 필드나 메소드 내에서 선언하는 것이@BeforEach
에 작성하는 것보다 좋을까? - 종립님 리뷰
- "저는 final이 많으면 많을수록 좋다고 생각합니다. 한편 테스트에서는 @BeforeEach는 준비 작업을 명시해줄 필요가 있을 때 쓰죠. 용도보다는 가독성을 더 생각하는 쪽이 바람직하다는 관점이라 할 수 있겠어요."
jojoldu
테스트 픽스처 올바르게 사용하기- 절대 테스트 메소드를 이해하기 위해 다른 부분을 찾아보게 만들어선 안된다.
- 테스트를 수정해도 다른 테스트에 영향을 주어서는 안되게 하는 것이 좋은 테스트의 기본조건이다.
두 분다 공통적으로 말씀하시는 부분은 **"가독성"**이다.
그리고 격리된 테스트 코드를 짜는게 힘들었다.
항상 ddl-auto: create
로 설정했었는데 실무에서는 이렇게 할 수 없을 것이다..
스프링은 JUnit을 이용하는 테스트 컨텍스트 프레임워크를 제공한다.
내가 작성한 테스트 코드들이 어떤 식으로 실행되는지 이해가 부족한 것 같다
Spring REST Docs와 Docker, Gradle 명령어를 처음 사용해 보았다
Spring REST Docs를 사용하면서 테스트 코드를 짜야하는 이유가 한 개 더 생겼다
@Nested
를 사용한 계층 테스트 코드를 작성하고document()
를 사용하면 예외가 발생한다.- 계층형 내부 클래스에 어노테이션들을 반복적으로 작성해줘야 한다.
- 이번 과제에서 REST Docs와 관련된 테스트 코드는
given-when-then
으로 작성했다.
Docker를 사용하면서 로컬에 깔려있는 디비를 지우고 컨테이너로 사용하는 방법을 배우고 설정 정보도 분리 할 수 있다는 것을 배웠다
- 토비의 스프링 읽기모임 예제 레포도 토비님이 도커를 넣어놓으신걸 이번 과제를 배우고 깨달았다 ㅎㅎ
- 따로 강의를 사서 듣고 싶다
특정 도메인 서비스를 위한 한 가지의 기능만 가진 인터페이스
- 컨트롤러와 서비스를 한 개의 책임만 가지게 세세한게 분리해 보았다
- 테스트 코드를 짜거나 수정할 때 편리하다는 것을 느꼇다
- 이렇게 까지 분리할 수 있구나 싶었다
벌써 코드숨이 끝났다
개발도 많이 배웠지만 공부하는 방법이나 방향성에 대해서도 많이 배운 것 같다
혼자 TDD와 테스트 코드를 배웠다면 이렇게 못 배웠을것이다..
다른 사람이 보기에는 고작 8주 가지고 뭐 대단하게 바뀌겠냐 라고 말 하겠지만, 이 교육을 듣기 전과 후로 나뉘는 것 같다.
- 테스트 코드를 왜 짜는지?
- 테스트 주도 개발이 무엇인지?
- 통합 테스트와 단위 테스트가 무엇인지?
- 생각을 하면서 개발하는 것 (개발에 정답은 없지만 이렇게 작성한 이유를 설명할 수 있게)
- 객체 지향 프로그래밍이 무엇인지?
- DTO와 불변 클래스를 왜 쓰는지? 등등
정말 소중한 경험이었다.
8주 동안 아낌없이 조언을 해주신 리뷰어분들과 윤석님에게 감사하다.
아래의 3개에서 선택해야 했다
- 우테캠프로 사전 과제
- 사전 과제는 객체지향설계 능력을 많이 보는 과제들이다
- 코드숨 포트폴리오반
- 협업 경험, 데브옵스 경험이 가능하다
- 하고 싶은 것
- 북마크 토이 프로젝트 하기
- JPA 공부
- 캐싱 , Redis 사용해보기
- 스프링 시큐리티 컨텍스 홀더 분석해보기
- 토프링 읽기 모임, 오브젝트 스터디 진도 따라가기
항상 우테캠을 듣고 싶었어서 사전과제를 선택했다. (시간남으면 하고 싶은 것 하기)
일단 사전 과제에 집중하자
뭔가를 공부할 때 한 개만 선택해서 한 개에만 몰두하는 단점이 있다
- 여러 개를 해보면서 버리는 것은 문제가 안된다
- 스스로 뭐가 중요한지 고민을 잘 하고 최선의 선택을 하기
- 최소의 노력으로 최대의 결과 만들기