ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,49 @@
+package lotto.domain;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class LottoGame {
+    private static final int TICKET_PRICE = 1000;
+
+    List<LottoTicket> lottoTickets;
+    LottoMatch lottoMatch;
+
+    public LottoGame() {
+        this.lottoTickets = new ArrayList<>();
+    }
+
+    public List<LottoTicket> buyLottoTicket(int money) {
+        int ticketNum = money / TICKET_PRICE;
+
+        for (int i = 0; i < ticketNum; i++) {
+            this.lottoTickets.add(new LottoTicket());
+        }
+
+        return this.lottoTickets;
+    }
+
+    public HashMap<LottoMatchNumber, Integer> winLotto() {
+        return this.lottoMatch.matchResult(lottoTickets);
+    }
+
+    public void inputTargetNumbers(String input) {
+        lottoMatch = new LottoMatch(input);
+    }
+
+
+    public double checkIncome(HashMap<LottoMatchNumber, Integer> results, int money){
+        double income = 0;
+
+        int totalIncome = 0;
+        for (LottoMatchNumber lottoMatchNumber : LottoMatchNumber.values()) {
+            totalIncome += lottoMatchNumber.totalPrizeMoney(results.get(lottoMatchNumber));
+        }
+
+        income = (double) totalIncome/money;
+
+        return income;
+    }
+
+}

리뷰: 이런 형태로 사용할거면 인스턴스 변수 없이 바로 리턴하는 형태로 사용하면 어떨까요?
@@ -0,0 +1,47 @@
+package lotto.domain;
+
+
+import java.util.*;
+
+public class LottoMatch {
+
+    List<Integer> targetNumbers;
+
+
+    public LottoMatch(String input) {
+        targetNumbers = new ArrayList<>();
+        String[] inputs = input.split(", ");
+
+        for (int i = 0; i < inputs.length; i++) {
+            targetNumbers.add(Integer.valueOf(inputs[i]));
+        }
+    }
+
+    public HashMap<LottoMatchNumber, Integer> matchResult(List<LottoTicket> myNumbers) {
+        HashMap<LottoMatchNumber, Integer> results = new HashMap<>();
+        results.put(LottoMatchNumber.MATCH3, 0);
+        results.put(LottoMatchNumber.MATCH4, 0);
+        results.put(LottoMatchNumber.MATCH5, 0);
+        results.put(LottoMatchNumber.MATCH6, 0);
+
+        for (LottoTicket myNumber : myNumbers) {
+            int matchCount = myNumber.checkMatch(targetNumbers);
+            inputMatchResult(matchCount, results);
+        }
+
+        return results;
+    }
+
+    private void inputMatchResult(int matchCount, HashMap<LottoMatchNumber, Integer> results) {
+        for (LottoMatchNumber lottoMatchNumber : LottoMatchNumber.values()) {
+            inputMatchCount(matchCount, results, lottoMatchNumber);
+        }
+    }
+
+    private void inputMatchCount(int matchCount, HashMap<LottoMatchNumber, Integer> results, LottoMatchNumber lottoMatchNumber) {
+        if (lottoMatchNumber.getMatchNumber() == matchCount) {
+            results.put(lottoMatchNumber, results.get(lottoMatchNumber) + 1);
+        }
+    }
+
+}

리뷰: Stream의 groupingBy나 Function.identity를 활용해보아도 좋을 것 같네요.
@@ -0,0 +1,47 @@
+package lotto.domain;
+
+
+import java.util.*;
+
+public class LottoMatch {
+
+    List<Integer> targetNumbers;
+
+
+    public LottoMatch(String input) {
+        targetNumbers = new ArrayList<>();
+        String[] inputs = input.split(", ");
+
+        for (int i = 0; i < inputs.length; i++) {
+            targetNumbers.add(Integer.valueOf(inputs[i]));
+        }
+    }
+
+    public HashMap<LottoMatchNumber, Integer> matchResult(List<LottoTicket> myNumbers) {
+        HashMap<LottoMatchNumber, Integer> results = new HashMap<>();
+        results.put(LottoMatchNumber.MATCH3, 0);
+        results.put(LottoMatchNumber.MATCH4, 0);
+        results.put(LottoMatchNumber.MATCH5, 0);
+        results.put(LottoMatchNumber.MATCH6, 0);
+
+        for (LottoTicket myNumber : myNumbers) {
+            int matchCount = myNumber.checkMatch(targetNumbers);
+            inputMatchResult(matchCount, results);
+        }
+
+        return results;
+    }
+
+    private void inputMatchResult(int matchCount, HashMap<LottoMatchNumber, Integer> results) {
+        for (LottoMatchNumber lottoMatchNumber : LottoMatchNumber.values()) {
+            inputMatchCount(matchCount, results, lottoMatchNumber);
+        }
+    }
+
+    private void inputMatchCount(int matchCount, HashMap<LottoMatchNumber, Integer> results, LottoMatchNumber lottoMatchNumber) {
+        if (lottoMatchNumber.getMatchNumber() == matchCount) {
+            results.put(lottoMatchNumber, results.get(lottoMatchNumber) + 1);
+        }
+    }
+
+}

리뷰: 생성자는 이미 생성이란 책임을 가지고 있는데 생성자 내부에서 split을 통한 파싱 및 여러 책임이 포함되어 있네요.

생성자에 로직을 넣고 싶다면 정적 팩터리로 분리하고 정적 팩터리에 로직을 추가한 뒤 거기서 생성자를 호출하거나 하는 형태로 구현하는건 어떨까요?
@@ -0,0 +1,65 @@
+package lotto.domain;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class LottoTicket {
+
+    private static final int PICK_NUMBERS = 6;
+
+    List<Integer> lottoNumbers;
+
+    public LottoTicket() {
+        this.lottoNumbers = new ArrayList<>();
+        for (int i = 1; i <= 45; i++) {
+            this.lottoNumbers.add(i);
+        }
+        lottoNumbers = issueTicket();
+    }
+
+    public LottoTicket(List<Integer> ticketNumbers) {
+        this.lottoNumbers = new ArrayList<>(ticketNumbers);
+    }
+
+    public List<Integer> issueTicket() {
+        Collections.shuffle(this.lottoNumbers);
+        this.lottoNumbers = this.lottoNumbers.subList(0, PICK_NUMBERS);
+        Collections.sort(this.lottoNumbers);
+
+        return this.lottoNumbers;
+    }
+
+
+    public int checkMatch(List<Integer> targetNumbers) {
+        if (targetNumbers.size() != this.lottoNumbers.size()) {
+            throw new IllegalArgumentException("당첨 번호 개수가 일치 하지 않습니다.");
+        }
+
+        int count = 0;
+        for (int i = 0; i < this.lottoNumbers.size(); i++) {
+            count = compareTargetnumbers(targetNumbers, i, count);
+        }
+
+        return count;
+    }
+
+    private int compareTargetnumbers(List<Integer> targetNumbers, int i, int count) {
+        for (int j = 0; j < targetNumbers.size(); j++) {
+            count = compareNumbers(targetNumbers, i, count, j);
+        }
+        return count;
+    }
+
+    private int compareNumbers(List<Integer> targetNumbers, int i, int count, int j) {
+        if (this.lottoNumbers.get(i) == targetNumbers.get(j)) {
+            count++;
+        }
+        return count;
+    }
+
+    @Override
+    public String toString() {
+        return lottoNumbers.toString();
+    }
+}

리뷰: 제약 조건 검증이 전혀 포함되어 있지 않네요. 도메인의 특성을 잘 나타내지 못하는 것 같습니다.

1. 1 - 45까지의 숫자만 존재해야 함
2. 6개의 중복되지 않은 숫자로 이루어져야 함

위와 같이 로또 도메인의 특성이 클래스에 반영되도록 설계해보시면 좋겠습니다.
@@ -0,0 +1,39 @@
+package nextstep.auth.authentication;
+
+import nextstep.member.application.UserDetailsService;
+import nextstep.member.domain.User;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public abstract class Authenticator implements HandlerInterceptor {
+
+    private final UserDetailsService userDetailsService;
+
+    public Authenticator(UserDetailsService userDetailsService) {
+        this.userDetailsService = userDetailsService;
+    }
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
+        AuthenticationToken token = convert(request);
+        User user = userDetailsService.loadUserByUsername(token.getPrincipal());
+
+        checkAuthentication(user, token.getCredentials());
+        authenticate(user, response);
+
+        return false;
+    }
+
+    abstract public AuthenticationToken convert(HttpServletRequest request) throws IOException;
+
+    abstract public void authenticate(User user, HttpServletResponse response) throws IOException;
+
+    private void checkAuthentication(User user, String password) {
+        if (!user.checkPassword(password)) {
+            throw new AuthenticationException();
+        }
+    }
+}

리뷰: 아래 요구사항이 반영되지 않은 것 같아요 😢 

<img width="516" alt="스크린샷 2022-08-16 오후 10 07 08" src="https://user-images.githubusercontent.com/17218212/184886987-febc108f-8b2f-403b-a53e-bf36f069cc24.png">
@@ -0,0 +1,8 @@
+package nextstep.member.application;
+
+import nextstep.member.domain.User;
+
+public interface UserDetailsService {
+
+    User loadUserByUsername(String email);
+}

리뷰: 이 인터페이스가 `member` 패키지에 있는게 맞을까요? 🤔
@@ -10,7 +10,7 @@
 import static org.assertj.core.api.Assertions.assertThat;
 
 class MemberAcceptanceTest extends AcceptanceTest {
-    public static final String EMAIL = "email@email.com";
+    public static final String EMAIL = "email2@email.com";
     public static final String PASSWORD = "password";
     public static final int AGE = 20;
 
@@ -67,10 +67,32 @@ void deleteMember() {
     @DisplayName("회원 정보를 관리한다.")
     @Test
     void manageMember() {
+        // given
+        ExtractableResponse<Response> createResponse = 회원_생성_요청(EMAIL, PASSWORD, AGE);
+        String newEmail = "new@email.com";
+
+        // when
+        ExtractableResponse<Response> response = 회원_정보_수정_요청(createResponse, newEmail, PASSWORD, AGE);
+        ExtractableResponse<Response> member = 회원_정보_조회_요청(createResponse);
+
+        // then
+        assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
+        assertThat(member.jsonPath().getString("email")).isEqualTo(newEmail);
     }
 
     @DisplayName("나의 정보를 관리한다.")
     @Test
     void manageMyInfo() {
+        // given
+        ExtractableResponse<Response> createResponse = 회원_생성_요청(EMAIL, PASSWORD, AGE);
+        String newEmail = "new@email.com";
+
+        // when
+        ExtractableResponse<Response> response = 베이직_인증으로_내_회원_정보_수정_요청(EMAIL, PASSWORD, newEmail, PASSWORD, AGE);
+        ExtractableResponse<Response> member = 회원_정보_조회_요청(createResponse);
+
+        // then
+        assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
+        assertThat(member.jsonPath().getString("email")).isEqualTo(newEmail);
     }
 }
\ No newline at end of file

리뷰: 인수테스트를 읽는 사람이 누구인가에 대해 고민해봐야 하는데요.
클린 애자일의 구절을 인용하면
>인수테스트는 업무 분석가와 QA, 개발자가 함께 힘을 모아 작성한다.

이와 같이 인수테스트를 읽는 사람이 모두 개발자는 아닐 수 있다고 생각합니다 😃.
@@ -0,0 +1,8 @@
+package nextstep.member.application;
+
+import nextstep.member.domain.User;
+
+public interface UserDetailsService {
+
+    User loadUserByUsername(String email);
+}

리뷰: 만들면서 배우는 클린아키텍쳐를 보면 서비스의 인터페이스와 그 구현체를 같이 두기에 그게 익숙해서 member에 두었네요 ㅎㅎ
UserDetailService를 auth에서만 사용하지 않을 수도 있기에 구현체와 같이 두는게 한눈에 더 들어올 것 같습니다!

물론 인터페이스와 구현체의 구분은 폴더로라도 하면 좋은데 급하게 하느라... 🙏
@@ -10,7 +10,7 @@
 import static org.assertj.core.api.Assertions.assertThat;
 
 class MemberAcceptanceTest extends AcceptanceTest {
-    public static final String EMAIL = "email@email.com";
+    public static final String EMAIL = "email2@email.com";
     public static final String PASSWORD = "password";
     public static final int AGE = 20;
 
@@ -67,10 +67,32 @@ void deleteMember() {
     @DisplayName("회원 정보를 관리한다.")
     @Test
     void manageMember() {
+        // given
+        ExtractableResponse<Response> createResponse = 회원_생성_요청(EMAIL, PASSWORD, AGE);
+        String newEmail = "new@email.com";
+
+        // when
+        ExtractableResponse<Response> response = 회원_정보_수정_요청(createResponse, newEmail, PASSWORD, AGE);
+        ExtractableResponse<Response> member = 회원_정보_조회_요청(createResponse);
+
+        // then
+        assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
+        assertThat(member.jsonPath().getString("email")).isEqualTo(newEmail);
     }
 
     @DisplayName("나의 정보를 관리한다.")
     @Test
     void manageMyInfo() {
+        // given
+        ExtractableResponse<Response> createResponse = 회원_생성_요청(EMAIL, PASSWORD, AGE);
+        String newEmail = "new@email.com";
+
+        // when
+        ExtractableResponse<Response> response = 베이직_인증으로_내_회원_정보_수정_요청(EMAIL, PASSWORD, newEmail, PASSWORD, AGE);
+        ExtractableResponse<Response> member = 회원_정보_조회_요청(createResponse);
+
+        // then
+        assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
+        assertThat(member.jsonPath().getString("email")).isEqualTo(newEmail);
     }
 }
\ No newline at end of file

리뷰: 넵ㅎㅎ 좀더 한글화가 들어가면 좋을 것 같군요
@@ -0,0 +1,39 @@
+package nextstep.auth.authentication;
+
+import nextstep.member.application.UserDetailsService;
+import nextstep.member.domain.User;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public abstract class Authenticator implements HandlerInterceptor {
+
+    private final UserDetailsService userDetailsService;
+
+    public Authenticator(UserDetailsService userDetailsService) {
+        this.userDetailsService = userDetailsService;
+    }
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
+        AuthenticationToken token = convert(request);
+        User user = userDetailsService.loadUserByUsername(token.getPrincipal());
+
+        checkAuthentication(user, token.getCredentials());
+        authenticate(user, response);
+
+        return false;
+    }
+
+    abstract public AuthenticationToken convert(HttpServletRequest request) throws IOException;
+
+    abstract public void authenticate(User user, HttpServletResponse response) throws IOException;
+
+    private void checkAuthentication(User user, String password) {
+        if (!user.checkPassword(password)) {
+            throw new AuthenticationException();
+        }
+    }
+}

리뷰: 미션은 `auth` 패키지 내에 라이브러리를 제외한 비즈니스 패키지들이 들어오지 않아야 완료됩니다 😢 
현재 `auth` 패키지가 `member` 에 의존하고 있어요
@@ -1,9 +1,12 @@
-package nextstep.member.domain;
+package nextstep.auth.authentication;
 
 
+import nextstep.member.domain.Member;
+import nextstep.member.domain.User;
+
 import java.util.List;
 
-public class LoginMember {
+public class LoginMember implements User {
     private String email;
     private String password;
     private List<String> authorities;

리뷰: `LoginMember` 를 옮기시지 않아요 됩니다 😢
@@ -0,0 +1,8 @@
+package nextstep.member.application;
+
+import nextstep.member.domain.User;
+
+public interface UserDetailsService {
+
+    User loadUserByUsername(String email);
+}

리뷰: 해당 책에서의 구현의도는 단순히 인터페이스와 구현체를 같은 패키지에 두는 것은 아닙니다 😅 
클린 아키텍처에서 로버트 C.마틴은 아래와 같은 원을 그려서 설명하는데요.
조금 단순화해서 말씀드리면 원 외부의 객체만이 원 내부의 객체에 의존해야 한다는 것을 주장합니다.
말씀하신 만들면서 배우는 클린아키텍처는 `DIP`를 통해 의존성 격리하기 위해 인터페이스를 두고, 해당 인터페이스를 내부의 객체가 구현하고, 외부의 객체는 그 인터페이스를 `사용`함으로써 외부의 객체가 내부의 실제 구현객체를 알지 못하게 되는 역할을 합니다.
지금 미션에서 요구하는 것도 사실은 `DIP` 를 통한 의존성 격리입니다 😄 

[참고](https://www.youtube.com/watch?v=dJ5C4qRqAgA&t=2941s) 영상을 통해 공부해보시면 조금 더 이해가 쉬우실 것 같아요 🔥 

![1_0u-ekVHFu7Om7Z-VTwFHvg](https://user-images.githubusercontent.com/17218212/185444984-fd35acf7-3f97-47e7-be29-ed00ac40509b.png)
@@ -0,0 +1,8 @@
+package nextstep.member.application;
+
+import nextstep.member.domain.User;
+
+public interface UserDetailsService {
+
+    User loadUserByUsername(String email);
+}

리뷰: 네 말씀하신대로 원 외부의 객체만이 원 내부의 객체를 참조해야 한다는 것은 동의합니다.

그런데 현재 구조로는 의존성 격리가 충분히 되지 않았다는 말씀일까요? 현재도 member 과 auth간에 usecase를 통해 통신이 이루어지고 있기에, dip를 통해 구현체는 직접적으로 참조가 이뤄지지 않고 있어서 usecase를 어디에 둘 것인가는 부차적인 문제라고 생각했습니다. (그래서 그중 하나로 만들면서 배우는 클린아키텍쳐 내용을 예시로 든 것이고요. 첨부해주신 영상에서도 import문이 존재하기만 해도 의존관계가 있다고 하네요!)
미션이 그런 의미라면 수정하는건 문제없습니다만 궁금해서 질문드립니다 ㅎㅎ
@@ -0,0 +1,8 @@
+package nextstep.member.application;
+
+import nextstep.member.domain.User;
+
+public interface UserDetailsService {
+
+    User loadUserByUsername(String email);
+}

리뷰: 클린아키텍처를 언급한건 상은님이 얘기하신 부분에 대해 추가적인 언급을 드리다보니 그렇게 되었네요 😅 
클린아키텍처의 내용이 단순히 인터페이스와 구현체를 같은 패키지에 두라는 의미는 아니라고 생각해서 첨언을 드려봤습니다

현재 미션에 대해 이야기하면 auth 와 member 의 성질에 대해 고민해봐야하는 데요.
`auth` 는 인증/인가에 대한 책임을 가지는 `비즈니스 규칙`과는 조금 동떨어진 `공통`의 성질을 지닌 패키지라고 생각됩니다
반면에 `member`는 이 애플리케이션만의 `비즈니스 규칙`을 담을수도 있는 사용자에 대한 패키지라고 볼 수 있을 것 같아요
이에 따라 `auth` -> `member` 로의 의존성이 생기지 않도록 관리하는 것이 대부분의 상황에서 유리할 거라고 생각합니다.
또한 현재는 `MemberController` 가 `@AuthenticationPrincipal`를 사용하고, `LoginMember`가 `User`를 구현하기 때문에 패키지 간의 순환참조가 생겼습니다.
이를 끊어내기 위해 `DIP`를 사용하라고 말씀 드린 것이었습니다. 😅 

정리하자면, 미션의 의도는 `DIP`를 통해 (다른 방법이 있으면 쓰셔도 됩니다) 패키지간 순환참조를 끊어내자 입니다. 
영상에서 보신 것과 같이 auth 나 member 둘 중의 한곳에서는 다른 패키지의 import 가 없도록 수정해보시길 바랍니다.
@@ -0,0 +1,8 @@
+package nextstep.member.application;
+
+import nextstep.member.domain.User;
+
+public interface UserDetailsService {
+
+    User loadUserByUsername(String email);
+}

리뷰: 예시를 들어주신 만들면서 배우는 클린 아키텍처에서도 application 패키지의 인터페이스를 adapter 에서 구현 하는 형태로 개발을 하고 있습니다. 의존성 격리를 위해 `DIP`를 사용하면 개념상으로는 이것 저것 이야기 할 수 있겠지만, 결국엔 내부의 객체들이 외부에 의존하지 않도록 만드는 여러가지 방법들이라고 생각합니다 😄
@@ -0,0 +1,8 @@
+package nextstep.member.application;
+
+import nextstep.member.domain.User;
+
+public interface UserDetailsService {
+
+    User loadUserByUsername(String email);
+}

리뷰: > MemberController 가 @AuthenticationPrincipal를 사용하고, LoginMember가 User를 구현하기 때문에 패키지 간의 순환참조가 생겼습니다.

여기에 참조가 있다는건 생각못했네요! 넵 수정하겠습니다

> 예시를 들어주신 만들면서 배우는 클린 아키텍처에서도 application 패키지의 인터페이스를 adapter 에서 구현 하는 형태로 개발을 하고 있습니다

넵 처음 댓글에서 말씀드렸듯이 `물론 인터페이스와 구현체의 구분은 폴더로라도 하면 좋은데 급하게 하느라... 🙏` 😂 앞으로  참고하겠습니다. 지금까지 긴 설명 감사드립니다!
@@ -0,0 +1,8 @@
+package nextstep.member.application;
+
+import nextstep.member.domain.User;
+
+public interface UserDetailsService {
+
+    User loadUserByUsername(String email);
+}

리뷰: 저도 토론하며 재미있었습니다 
[참고](https://github.com/wikibook/clean-architecture) 의 예제의 의존관계를 직접 그림으로 그려보시면 더 좋은 인사이트를 얻으 실 수 있을것 같아요 👍
@@ -0,0 +1,39 @@
+package nextstep.subway.unit;
+
+import nextstep.subway.applicaion.LineService;
+import nextstep.subway.applicaion.PathService;
+import nextstep.subway.applicaion.StationService;
+import nextstep.subway.domain.Station;
+import nextstep.subway.utils.TestObjectFactory;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@ExtendWith(MockitoExtension.class)
+public class PathServiceMockTest {
+
+    @Mock
+    LineService lineService;
+
+    @Mock
+    StationService stationService;
+
+    @InjectMocks
+    PathService pathService;
+
+    TestObjectFactory testObjectFactory = new TestObjectFactory();
+
+    @Test
+    @DisplayName("실패_출발역과 도착역이 같은 경우")
+    void fail_same_start_end() {
+        Station 강남역 = testObjectFactory.역생성("강남역");
+        Station 양재역 = testObjectFactory.역생성("양재역");
+
+        assertThrows(IllegalArgumentException.class, () -> pathService.getPath(강남역.getId(), 양재역.getId()));
+    }
+}

리뷰: StationService의 createStationResponse 메서드를 그대로 사용하고 싶어서, stationService를 모킹하지 않고 따로 생성하도록 하였는데요
@InjectMocks도 사용할 수 없어 불편하고, 테스트할 대상 하나만 원본 클래스를 가져다 쓰고 나머지는 다 목으로 진행해야 한다는 목 테스트의 이념(?) 에도 맞지 않는 것 같아 고민이 됩니다.

혹시 다른 깔끔한 테스트 방법이나 리팩토링 방법이 있을까요?
@@ -0,0 +1,39 @@
+package nextstep.subway.unit;
+
+import nextstep.subway.applicaion.LineService;
+import nextstep.subway.applicaion.PathService;
+import nextstep.subway.applicaion.StationService;
+import nextstep.subway.domain.Station;
+import nextstep.subway.utils.TestObjectFactory;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@ExtendWith(MockitoExtension.class)
+public class PathServiceMockTest {
+
+    @Mock
+    LineService lineService;
+
+    @Mock
+    StationService stationService;
+
+    @InjectMocks
+    PathService pathService;
+
+    TestObjectFactory testObjectFactory = new TestObjectFactory();
+
+    @Test
+    @DisplayName("실패_출발역과 도착역이 같은 경우")
+    void fail_same_start_end() {
+        Station 강남역 = testObjectFactory.역생성("강남역");
+        Station 양재역 = testObjectFactory.역생성("양재역");
+
+        assertThrows(IllegalArgumentException.class, () -> pathService.getPath(강남역.getId(), 양재역.getId()));
+    }
+}

리뷰: 주로 지금과 같이 layered 아키텍처에서는 application(service) layer는 domain 객체들을 조합하는 역할을 하는데요,
지금 코드에서는 응답 Response 객체를 생성하는 역할 또한 service가 갖고 있어서 inject해야하는 문제가 생기는 것 같아요.

mock testing 방식을 사용했을 때 크게 느껴지는 단점 중 하나는 의존하는 객체가 많으면 많아질수록 테스트 작성이 까다로워진다는 거에요.
(when, thenReturn을 해야하니 내부 구현 자체를 직접적으로 알아야겠죠)

그런 점에서 service끼리 의존을 해야되는 경우엔 의존하려는 service에서 필요로 하는 기능이 정말 service에 있어야할까를 생각해보시면 좋을 것 같습니다 :)

지금의 경우는 Response 객체를 만드는걸 Response 객체로 넘겨주어도 될 것 같습니다 :)
(static factory method 등을 활용할 수 있을 것 같네요!)
@@ -0,0 +1,39 @@
+package nextstep.subway.unit;
+
+import nextstep.subway.applicaion.LineService;
+import nextstep.subway.applicaion.PathService;
+import nextstep.subway.applicaion.StationService;
+import nextstep.subway.domain.Station;
+import nextstep.subway.utils.TestObjectFactory;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@ExtendWith(MockitoExtension.class)
+public class PathServiceMockTest {
+
+    @Mock
+    LineService lineService;
+
+    @Mock
+    StationService stationService;
+
+    @InjectMocks
+    PathService pathService;
+
+    TestObjectFactory testObjectFactory = new TestObjectFactory();
+
+    @Test
+    @DisplayName("실패_출발역과 도착역이 같은 경우")
+    void fail_same_start_end() {
+        Station 강남역 = testObjectFactory.역생성("강남역");
+        Station 양재역 = testObjectFactory.역생성("양재역");
+
+        assertThrows(IllegalArgumentException.class, () -> pathService.getPath(강남역.getId(), 양재역.getId()));
+    }
+}

리뷰: 추가적으로 예외 상황에 대해서 테스트를 해주시면 좋을 것 같습니다!

![image](https://user-images.githubusercontent.com/34268173/184040674-834af3fb-ce97-41eb-84d9-7a723ebbf570.png)

대신에 지금과 같이 service layer에 비즈니스 로직이 들어가게 되면 테스트 해봐서 아시겠지만
service에서 필요한 (test double이든 실제 객체든)의존성이 갖춰진 상태에서 테스트를 해야하니 테스트 작성이 번거롭거나 까다로워지는데요.

첫번째 단계의 요구사항을 통해 경험하셨듯, `길 찾기`에 대한 `도메인 객체`를 만들어서 단위 테스트를 해보시는건 어떨까요? :)
@@ -0,0 +1,39 @@
+package nextstep.subway.unit;
+
+import nextstep.subway.applicaion.LineService;
+import nextstep.subway.applicaion.PathService;
+import nextstep.subway.applicaion.StationService;
+import nextstep.subway.domain.Station;
+import nextstep.subway.utils.TestObjectFactory;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@ExtendWith(MockitoExtension.class)
+public class PathServiceMockTest {
+
+    @Mock
+    LineService lineService;
+
+    @Mock
+    StationService stationService;
+
+    @InjectMocks
+    PathService pathService;
+
+    TestObjectFactory testObjectFactory = new TestObjectFactory();
+
+    @Test
+    @DisplayName("실패_출발역과 도착역이 같은 경우")
+    void fail_same_start_end() {
+        Station 강남역 = testObjectFactory.역생성("강남역");
+        Station 양재역 = testObjectFactory.역생성("양재역");
+
+        assertThrows(IllegalArgumentException.class, () -> pathService.getPath(강남역.getId(), 양재역.getId()));
+    }
+}

리뷰: 해당 리뷰를 반영하면서
`출발역과 도착역이 연결이 되어 있지 않은 경우` 에 대해 pathService 서비스 레이어 mock 테스트를 짜는데,
이를 pathService에서 테스트하려니 stationService를 목처리해둬서, 테스트를 짜려면 stationService.findById에서 에러를 던지도록 목 테스트를 진행해야 했습니다. 하지만, 이는 코드에 대한 테스트를 짜기보다는 테스트를 위한 테스트라는 생각이 들어서 결국 이 경우는 실제 객체 테스트에서 진행해야 했는데요. 
이와 같은 경우는 제가 한 것처럼 mock테스트를 피하는게 맞는 것인지? 아니면 다른 방법이 있는지 궁금합니다!