그 주의 화요일, 금요일을 지정 날짜로 업데이트 합니다
jgrapht 라이브러리 를 활용하면 간편하게 최단거리를 조회할 수 있다.
라이브러리 코드 분석를 해보면
DijkstraShortestPath
는 BaseShortestPathAlgorithm
를 상속하고 있고,
BaseShortestPathAlgorithm
는 ShortestPathAlgorithm
최상위 인터페이스를 implements 하고 있다.
경로 조회 기능 미션
지하철역(Station)을 정점, 지하철역 연결정보(Section)를 간선, 거리(distance)를 가중치로 놓고
최단 거리를 조회하는 기능(다익스트라 알고리즘)을 구현하기 위해 외부 라이브러리인 jgrapht 라이브러리를 사용하였다.
인수테스트를 작성하고 기능을 구현하기에 앞서 2가지 방법에 관하여 정리를 해보고자 한다.
만약 최단 경로 조회
기능을 구현하려고 한다면 먼저 최단 경로 조회
인수 테스트를 작성하였을 것이다.
이후 최단 경로에 대한 domain(ex.path)을 먼저 작성할 것인지 아니면 controller 를 먼저 작성할 것인지에 관하여 2가지 방법으로 구현할 수 있다.
두 방향성에 따라 테스트가 협력 객체의 세부 구현에 의존하는 경우 (가짜 협력 객체 사용)와 테스트 대상이 협력 객체와 독립적이지 못하고 변경에 영향을 받는 경우 (실제 협력 객체 사용)를 비교해볼 수 있다.
Outside In
Mockist TDD 방식 으로 Controller 입장에서 볼 때 Service 는 가짜 객체를 이용하여 테스트하면 되므로 안만들어져도 상관이 없다는 측면이다. 때문에 상위 레벨 테스트 부터 시작하여 Controller → Service → Repository 개발 순서처럼 테스트를 검증해나갈 수 있다.
테스트 더블을 활용하여 테스트 대상이 의존하는 협력 객체의 예상 결과를 정의하고 다음 사이클로 테스트 더블로 미리 정의한 협력 객체를 테스트 대상으로 한다.
ex) 컨트롤러 레이어 구현 이후 서비스 레이어 구현 시, 서비스 test 우선 작성 후 → 기능을 구현한다.
서비스 test 내부에서 도메인들간의 로직의 흐름을 검증한다. 이 때 사용되는 도메인은 mock 객체를 활용한다.
외부 라이브러리를 활용한 로직을 검증할 때는 가급적 실제 객체를 활용한다.
Inside Out
Classic TDD 방식 으로 실제 객체를 다뤄야 하기 때문에 도메인 모델부터 시작하는 방식이다.
도메인 설계 후 → 도메인 test 를 시작으로 → 기능 구현을 시작한다.
해당 도메인의 단위 테스트를 통해 도메인의 역할과 경계를 설계한다.
도메인의 구현이 끝나면 해당 도메인과 관계를 맺는 객체에 대해 기능 구현을 시작한다.
+) 어떤 방식을 선택해야할까?
컨트롤러를 먼저 만들지 도메인 모델을 먼저 만들지 정답은 없다.
다만 인수 테스트 작성을 통해 요구사항과 기능 전반에 대한 이해를 선행하고,
도메인에 대한 이해도가 높지 않은 상태라면 Outside In 방식으로 방향성을 잡고 도메인 지식을 쌓다가 일정 수준 쌓이면 불필요한 부분을 지우고 Inside Out 방식으로 변경하는 방식도 효율적이다.
TDD 방법론에 관하여만 알고있었는데, 이번에 Mocktio 를 사용하며 Mockito, BDDMockito 의 차이가 궁금하여 BDD에 관하여 학습하게 되었다.
BDD(Behavior Driven Development)이란?
비즈니스 중심의 행위 주도 개발 방법론 으로 시나리오를 기반으로 테스트하는 패턴(Given,When,Then)을 권장한다.
TDD에 기반을 두고 있으나 TDD의 테스트 코드 목적은 기능 동작의 검증으로 모듈의 기능이 중심이라면 BDD는 서비스 유저 시나리오 동작의 검증으로 서비스 사용자 행위 중심이다.
Mockito vs BDDMockito
BDDMockito는 BDD에 맞게 테스트코드 작성시 시나리오에 맞게 테스트 코드가 읽히도록 하는 개선된 Mockito 프레임워크이다. (Mockito의 가독성을 조금 더 개선한 버전이라고 볼 수 있다.)
Stub vs Mock
Stub은 상태 검증을 위해 사용되고,Mock는 행위 검증을 위해 사용한다.
Stub
리턴되는 값, 예외 확인 등 상태 검증 을 위해 사용한다.
호출되었을 때 미리 준비된 답변으로 응답한다. (특정 상태를 사정한 값,메시지 등 하드코딩된 형태)
Mock
호출 시 값을 구체적으로 비교하기 보단 메소드 호출 여부 등 동작이 잘 되었는지 확인하는 등 행위 검증 을 위한 객체이다.
호출에 대한 기대값을 명세하고, 해당 내용에 따라 동작하도록 프로그래밍 된 객체이다.
특히 Mock은 특정 메서드로 Mocking 하기 때문에 내부의 특정 메서드에 대해 더욱 의존적일 수 있다.
(* Mocking : 단위 테스트를 작성할 때, 해당 코드가 의존하는 부분을 가짜(mock)로 대체하는 기법)
테스트 검증
테스트의 검증은 주로 상태 검증과 행위 검증으로 나뉜다.
상태 검증
특정 메서드가 수행된 후 객체들의 상태를 검증하는 것이다
상태를 검증하기 위해 상태를 노출하는 메서드가 많이 추가될 수 있다
행위 검증
특정 메서드가 호출되었는지와 같은 행위를 검사한다.
특정 메서드의 호출과 같은 것을 검증하기 때문에, 구현에 굉장히 의존적이게 될 수 있다.
Stub, Mock 을 이용한 Service 계층 단위 테스트 하기
인수 테스트 미션을 계속 진행하며 테스트에서 실제 객체를 사용하는 경우와 가짜 객체를 사용하여 테스트 하는 경우를 학습할 수 있었다.
또한 TDD 로 개발을 하면서 어떤 방향으로 TDD를 고려할 것인지를 더 생각하며 개발해 볼 수 있었고 추가적으로 BDD 방법론과 비교해보며 좀 더 TDD에 관해 이해해볼 수 있었다.
[참조]
https://edu.nextstep.camp/s/13RXWHcB/ls/EXEjdehj
https://unluckyjung.github.io/dev/2021/06/05/Library-DI/
https://joojimin.tistory.com/6
https://bb-library.tistory.com/263
https://luran.me/343
https://jojoldu.tistory.com/637
https://joont92.github.io/tdd/%EC%83%81%ED%83%9C%EA%B2%80%EC%A6%9D%EA%B3%BC-%ED%96%89%EC%9C%84%EA%B2%80%EC%A6%9D-stub%EA%B3%BC-mock-%EC%B0%A8%EC%9D%B4/