우아한테크코스 level 2-2 지하철 노선도 미션 회고 및 학습 프로필
와일더의 학습 프로필 🃏
atdd-subway-map 미션을 하면서 새롭게 학습한 내용을 정리했습니다.
[TDD] 인수 테스트 (Acceptance Test Driven Development) - 5
내용
-
시스템이 실제 운영 환경에서 사용될 준비가 되었는지 최종적으로 확인하는 테스트 단계다.
- 예상대로 시스템이 동작하는지 확인한다.
- 시스템의 인수를 위해 요구사항을 사용자가 직접 테스트하여 개발이 완료되었음을 증명하는 테스트다.
-
인수 테스트 목적
확신, 배포가능성 평가, 준수성 확인, 피드백
- 아래의 세가지 테스트와는 약간 scope 가 다르다.
- 구현하고자 하는 기능(비즈니스 레벨)에 대한 테스트이다 보니 인수 종단 간 테스트를 사용하여 테스트하는 경우가 많다.
- 아래의 세가지 테스트 모두 인수테스트의 범위에 들어갈 수 있다.
링크
[TDD] 종단 간 테스트 (End to End Test) - 5
내용
- 시스템이 외부 요구사항을 충족하며 전체 시스템을 끝까지 테스트해서 목표를 달성하는지 확인한다.
- 다른 테스트와 다르게 컴포넌트, 아키텍쳐에 관계 없이 시스템이 전체적으로 비지니스 목표를 충족하는지 확인하는데 집중한다.
- 블랙박스 테스트를 진행한다.
- 내부 설계는 고려하지 않는다.
- 요구사항 및 기능성에 기반해서 이루어진다.
- 테스트를 만들기 힘들고 만든 테스트를 신뢰하기 힘들다.
링크
[TDD] 통합 테스트 (Integrate Test) - 2
내용
- 단위 테스트가 끝난 모듈을 통합하는 과정에서 발생할 수 있는 버그를 찾는 테스트다.
- 모듈간의 상호작용을 확인하는 것이 주목적이다.
링크
[TDD] 단위 테스트 (Unit Test) - 2
모듈(프로그램의 기본 단위) 테스트
내용
- 구현 단계에서 각 모듈을 개발한 뒤 명세에 맞게 개발이 되었는 지 확인한다.
- 내부 구조를 모두 확인 가능한 ‘화이트 박스’테스팅을 수행한다.
- 특정 모듈 테스트 수행 시 해당 모듈과 맞물리는 상위/하위 모델이 필요할 수 있다.
- 보통 메서드 단위의 가장 작은 단위 테스트다.
- 즉각적인 피드백이 나온다.
링크
[Terms] 단위 테스트, 인수테스트, 통합테스트 란?
[Spring] @Transactional - 4
내용
트랜잭션
-
여러 과정을 하나의 행위로 묶을 때 사용한다.
-
여러 단계를 수행했을 때 하나라도 실패하면 모두 취소한다. (데이터 무결성 보장)
-
Service 레이어에서 적용했다.
@RequiredArgsConstructor @Transactional(readOnly = true) @Service public class LineService { ... }
링크
[Spring] @Valid & BindingResult - 4
내용
valid
-
Api Controller 에서 간편하게 데이터 검증을 하기 위해서 사용했다.
-
별도의 의존성 추가가 필요하다.
dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' }
-
@Range, @NotNull, @Size, @Positive 등의 애너테이션 검증이 가능하다.
@InitBinder("lineRequest") // Custom Validator 를 등록한다. private void initBind(WebDataBinder webDataBinder) { webDataBinder.addValidators(new LineValidator()); } @PostMapping public ResponseEntity<LineResponse> createLine(@RequestBody @Valid LineRequest lineRequest, BindingResult bindingResult) { if (bindingResult.hasErrors()) { throw new InsufficientLineInformationException(); } Station upStation = stationService.find(lineRequest.getUpStationId()); Station downStation = stationService.find(lineRequest.getDownStationId()); Line line = lineService.create(lineRequest, upStation, downStation); LineResponse lineResponse = LineResponse.of(line); return ResponseEntity.created(URI.create("/lines/" + line.getId())).body(lineResponse); }
public class LineValidator implements Validator { @Override public boolean supports(Class<?> clazz) { return LineRequest.class.isAssignableFrom(clazz); } @Override public void validate(Object target, Errors errors) { LineRequest lineRequest = (LineRequest) target; if (Objects.isNull(lineRequest.getUpStationId()) || Objects.isNull(lineRequest.getDownStationId())) { throw new InsufficientLineInformationException(); } if (lineRequest.getDownStationId().equals(lineRequest.getUpStationId())) { errors.rejectValue("downStationId", "duplicatedStation", "중복된 역입니다."); } } }
BindingResult
- validator 를 상속받는 클래스에서 객체 값을 검증하는 방식이다.
- hibernate-validator 가 아닌 validation-api 라이브러를 사용한다.
링크
[JDBC] RowMapper & BeanPropertyRowMapper - 3
내용
RowMapper
-
SELECT 쿼리로 조회한 결과를 사용자가 원하는 형태로 여러 개의 값을 반환할 수 있도록 구현해주는 객체다.
-
queryForObject 를 통해 객체로도 반환이 가능해진다.
private RowMapper getRowMapper() { return (rs, rowNum) -> { Long id = rs.getLong("id"); String name = rs.getString("name"); String color = rs.getString("color"); return Line.of(id, name, color); }; }
BeanPropertyRowMapper
-
모든 Bean Property 를 담아주는 RowMapper 를 자동으로 생성해 주는 객체다.
private final RowMapper rowMapper; public JdbcLineDao() { this.rowMapper = new BeanPropertyRowMapper(Line.class); }
-
다만 위의 클래스로 인스턴스를 생성할 경우 Line.class 에 Setter, NoArgsConstructor 가 필요하기 때문에 사용하지 않았다.
링크
[TDD] RestAssured - 5
내용
-
Spring Framework Test 클래스 중 하나인 MockMvc 와 다르게 직접 의존성을 추가해야 한다.
// build.gradle dependencies { testImplementation 'io.rest-assured:rest-assured:3.3.0' }
-
@SpringBootTest 로 등록된 Spring Bean 을 전부 로드한 뒤 테스트를 수행하기 때문에 시간이 오래 걸린다.
-
Presentation Layer 외의 Bean 을 다수 사용할 때, MockMvc 테스트를 진행하는 것보다 비용보다 적게 소모된다.
-
BDD 스타일로 작성할 수 있고 가독성이 좋다. (인수 테스트에서 사용하기 적절함)
-
json data 를 쉽고 편하게 검증할 수 있다.
@DisplayName("노선 수정 - 실패(변경하려는 노선 이름 중복)")
@Test
void updateLine_duplicatedName() {
// given
String uri = 노선_생성("신분당선", "bg-red-600").extract().header("Location");
노선_생성("구분당선", "bg-blue-600");
LineRequest lineRequest = new LineRequest("구분당선", "bg-blue-600");
// when and then
RestAssured.given().log().all()
.body(lineRequest)
.contentType(ContentType.JSON)
.when()
.put(uri)
.then().log().all()
.statusCode(HttpStatus.BAD_REQUEST.value())
.body(equalTo("이미 등록되어 있는 노선 이름입니다."));
}
링크
[TDD] Mockmvc - 5
내용
- 웹 애플리케이션을 서버에 배포하지 않고도 스프링 MVC 동작을 재현할 수 있는 라이브러리
- 주로 Controller Layer Unit Test (단위 테스트)에 사용된다.
- @SpringBootTest 를 사용하지 않고 @WebMvcTest 를 통해 Presentation Layer Bean 만 불러온다.
- 그 외 Bean 은 Mock 객체 설정을 해줘서 순수한 Controller 로직을 테스트 한다.
@Test
@DisplayName("노선 생성 - 실패(노선 중복 이름)")
void createLine_duplicatedName() throws Exception {
// given
lineDao.save(Line.of("1호선", "bg-red-600"));
Long upStationId = stationDao.save(Station.from("잠실역")).getId();
Long downStationId = stationDao.save(Station.from("석촌역")).getId();
final LineRequest lineRequest =
new LineRequest("1호선", "bg-green-600", upStationId, downStationId, 10);
// when
ResultActions result = 노선_생성(lineRequest);
// then
result.andDo(print())
.andExpect(status().isBadRequest())
.andExpect(content().string("중복되는 라인 정보가 존재합니다."));
}
링크
[Spring] @ExceptionHandler & @ControllerAdvice - 4
내용
ExceptionHandler
-
@Controller, @RestController 가 적용된 Bean 내에서 발생하는 예외를 잡아서 하나의 메서드에서 처리해주는 기능을 제공한다.
@ExceptionHandler({SubwayException.class //, OtherException.class}) public ResponseEntity<String> handleBadRequest(Exception e) { return ResponseEntity.badRequest().body(e.getMessage()); }
-
위와 같이 사용할 수 있으며 애너테이션에 여러 예외 클래스를 추가할 수 있다.
ControllerAdvice
-
@ExceptionHandler 가 하나의 클래스에 대한 것이라면, @ControllerAdvice 는 전역에서 발생할 수 있는 예외를 잡아서 처리해준다.
@RestControllerAdvice public class SubwayAdvice { @ExceptionHandler({SubwayException.class}) public ResponseEntity<String> handleBadRequest(Exception e) { return ResponseEntity.badRequest().body(e.getMessage()); } @ExceptionHandler({SubwayNotFoundException.class}) public ResponseEntity<String> handleNotFound(Exception e) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage()); } }
-
여러 예외 클래스가 추가되면 가독성이 떨어지므로 같은 예외에 대해서는 상속처리를 하여 예외 처리에 대한 가독성을 향샹시켰다.
링크
[JAVA] PojoBean & Bean - 2
내용
POJO Classes
-
자바 객체의 표준이며, Java Language Specification 에 의한 어떤 특정한 엄격한(restriction) 경계가 존재하지 않는다.
-
가독성과 재사용성을 중요시하게 사용된다.
-
POJO 의 3가지 규칙
- Extend prespecified classes
- Implement respecified interfaces
- Contain prespecified annotations
Beans Classes
- POJO 와 달리 엄격한 규칙을 가지고 있다.
- 모든 JavaBeans 는 POJOs 지만 POJOs 는 JavaBeans 가 아니다.
- 모든 필드의 접근제어자는 private 이어야 한다.
- 모든 필드는 getter, setter 를 가져야 하고 getter, setter 로만 접근할 수 있어야 한다.
- 기본 생성자만 있어야 한다.
링크
[ORM] 객체 관계 매핑 (Object Relational Mapping) - 4
내용
Object Modeling
- OOP 를 기반으로 생성된 시스템을 표현한다.
Relational Modeling
- 서술어와 비슷한 truth statement 로 정보를 표현한다.
ORM 이란?
- 객체와 관계를 연결해 주는 개념이다.
- ORM 을 이용한 개발은 객체와 데이터베이스의 변형에 유연하게 대처할 수 있도록 해준다.
- 관계형 데이터베이스에 제약을 최대한 받지 않으면서 객체를 클래스로 표현하는 것과 같이 관계형 데이터베이스를 객체처럼 쉽게 표현 또는 사용하는 것이다.
링크
[Srping] MediaType & ContentType - 2
내용
-
테스트에서 body 의 타입을 지정해주는 부분에서 궁금증이 생겼었다.
RestAssured.given().log().all() .body(params) .contentType(MediaType.APPLICATION_JSON_VALUE) // 1 ExtractableResponse<Response> response = RestAssured.given().log().all() .body(params) .contentType(ContentType.JSON) // 2
MediaType
-
Request 와 Response 에서 요청을 주고 받을 때 사용하는 MediaType 을 지정하여 이외의 값을 거를 수 있다.
This method can be used to parse an Accept or Content-Type header.
이 메서드는 Accept 또는 Content-Type 헤더를 구문 분석하는 데 사용할 수 있다.
ContentType
-
명시한 타입으로 보낸다는 뜻이다.
This may be used to specify a request or response.
요청 또는 응답을 지정하는 데 사용할 수 있다.
-
명시한 타입으로 받을 때는 Accept 를 사용한다.
링크
[REST] REST API (Representational State Transfer) - 4
내용
-
자원의 이름으로 구분하여 해당 자원의 상태(정보)를 주고 받는 모든 것을 의미한다.
-
자원(resource)의 표현(representation)에 의한 상태 전달
-
Restful 하게 URI 를 명시한다는 것을 제이슨은 이렇게 말했다.
이러한 요청을 하면 어떠한 응답이 오겠다.
-
즉, 예상한 이름으로 정보를 요청하면 예상한 결과가 나오게끔 만드는 것을 의미한다고 생각하면 된다.
@PostMapping("/lines") // 노선에서 Body 에 노선 정보를 담아서 PostMapping 을 하면 추가할 수 있을 것이다. @GetMapping("/lines/{lineId}") // 노선에서 아이디를 주고 GetMapping 을 하면 조회를 할 수 있을 것이다. @PutMapping("/lines/{lineId}") // 노선에서 아이디를 주고 PutMapping 을 하면 수정할 수 있을 것이다. @DeleteMapping("/lines/{lineId}") // 노선에서 아이디를 주고 DeleteMapping 을 하면 삭제할 수 있을 것이다.
링크
[Design Pattern] IoC & DI - 4
내용
IoC(Inversion of Control)
- GoF 디자인 패턴에서 유래된 것이다.
- EJB 에서 WAS Servlet Container 에서도 사용된 개념이다.
- 개발자가 직접 객체를 생성하는 것이 아닌 @Autowired 를 통해 Spring Container 에서 직접(제어) 객체를 생성하여 해당 객체에 주입해주는 것이다.
- 위와 같은 상황에서 제어가 역전되었다고 말한다.
- 역할과 책임의 분리를 할 수 있다.
- 객체 생명 관리, 흐름 제어를 제 3자에게 위함하는 프로그래밍 모델이다.
의존성 주입(Dependency Injection)
-
어떤 객체가 사용하는 의존 객체를 직접 만들어 사용하는게 아니라 주입 받아서 사용하는 방법이다.
// new 연산자를 사용하여 직접 객체를 생성한다. class LineService { private final LineDao lineDao; public LineService(LineDao lineDao) { this.lineDao = lineDao; } } // Line 이 스프링 컨테이너에 관리되고 있는 Bean 일 경우 @Autowired 를 통해 객체를 주입 받을 수 있다. class LineService { @Autowired private final LineDao lineDao; }
-
장점
- 재사용성 증가
- 테스트에 용이
- 코드 단순화
- 코드 가독성 증가
- 종속성 감속
- 결합도는 낮추고 응집도는 높임
- 객체간의 의존관계를 설정할 수 있음
-
단순한 제어의 역전, 객체를 주입하여 유연한 프로그래밍을 할 수 있도록 하는 패턴에 더 명확한 이름을 부여하기 위해 DI 라는 용어를 만들었다.
링크
[Java] Reflection - 2
내용
-
객체를 통해 클래스의 정보를 분석해 내는 프로그램 기법을 말한다.
-
BeanFactory 는 어플리케이션이 실행한 후 객체가 호출 될 당시 객체의 인스턴스를 생성한다.
-
그 때 필요한 기술이 Reflection 이다.
-
자바는 동적으로 객체를 생성하는 기술이 없었기 때문에 동적으로 인스턴스를 생성하는 Reflection 으로 그 역할을 대신한다.
-
즉, 또다른 클래스를 동적로딩 하여 생성자, 멤버 빌드, 멤버 메서드 등을 사용할 수 있도록 한다.
-
미션에 제공된 Reflection 예시
private Station createNewObject(Station station) { Field field = ReflectionUtils.findField(Station.class, "id"); field.setAccessible(true); ReflectionUtils.setField(field, station, ++seq); return station; }