ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,95 @@
+import { CartItemType, ProductType } from '../types';
+import { generateBasicToken } from './auth';
+
+const API_URL = import.meta.env.VITE_API_URL;
+const USER_ID = import.meta.env.VITE_USER_ID;
+const USER_PASSWORD = import.meta.env.VITE_USER_PASSWORD;
+
+/**
+ * 공통 요청을 처리하는 함수
+ * @param {string} endpoint - API endpoint
+ * @param {RequestInit} options - fetch options
+ * @returns {Response} - fetch response
+ */
+async function makeRequest(endpoint: string, options: RequestInit): Promise<Response> {
+  const token = generateBasicToken(USER_ID, USER_PASSWORD);
+
+  const response = await fetch(`${API_URL}${endpoint}`, {
+    ...options,
+    headers: {
+      ...options.headers,
+      Authorization: token,
+      'Content-Type': 'application/json',
+    },
+  });
+
+  if (!response.ok) {
+    throw new Error(`Failed to ${options.method?.toLowerCase()} ${endpoint}`);
+  }
+
+  return response;
+}
+
+interface FetchProductsParams {
+  category?: string;
+  page?: number;
+  size?: number;
+  sort?: string;
+}
+
+/**
+ * fetchCartItems - API에서 상품 목록을 fetch합니다.
+ * @returns {Promise<ProductType[]>}
+ */
+export async function fetchProducts(params: FetchProductsParams = {}): Promise<ProductType[]> {
+  const queryString = Object.entries(params)
+    .map(([key, value]) => {
+      if (key === 'category' && value === 'all') {
+        return;
+      } else {
+        return `${key}=${value}`;
+      }
+    })
+    .join('&');
+  const response = await makeRequest(`/products?${queryString}`, {
+    method: 'GET',
+  });
+  const data = await response.json();
+
+  return data.content;
+}
+
+/**
+ * addCartItem - 선택한 상품을 장바구니에 추가합니다.
+ * @returns {Promise<void>}
+ */
+export async function addCartItem(productId: number): Promise<void> {
+  await makeRequest('/cart-items', {
+    method: 'POST',
+    body: JSON.stringify({ productId }),
+  });
+}
+
+/**
+ * fetchCartItem - API에서 장바구니 목록을 fetch합니다.
+ * @returns {Promise<CartItemType[]>}
+ */
+export async function fetchCartItem(): Promise<CartItemType[]> {
+  const response = await makeRequest('/cart-items?page=0&size=100', {
+    method: 'GET',
+  });
+
+  const data = await response.json();
+
+  return data.content;
+}
+
+/**
+ * deleteCartItem - 선택한 상품을 장바구니에서 삭제합니다.
+ * @returns {Promise<void>}
+ */
+export async function deleteCartItem(cartItemId: number): Promise<void> {
+  await makeRequest(`/cart-items/${cartItemId}`, {
+    method: 'DELETE',
+  });
+}

리뷰: 맞아요... 임시방편이었는데 그냥 까먹고 넘어갔네요 ㅜㅜ
@@ -0,0 +1,95 @@
+import { CartItemType, ProductType } from '../types';
+import { generateBasicToken } from './auth';
+
+const API_URL = import.meta.env.VITE_API_URL;
+const USER_ID = import.meta.env.VITE_USER_ID;
+const USER_PASSWORD = import.meta.env.VITE_USER_PASSWORD;
+
+/**
+ * 공통 요청을 처리하는 함수
+ * @param {string} endpoint - API endpoint
+ * @param {RequestInit} options - fetch options
+ * @returns {Response} - fetch response
+ */
+async function makeRequest(endpoint: string, options: RequestInit): Promise<Response> {
+  const token = generateBasicToken(USER_ID, USER_PASSWORD);
+
+  const response = await fetch(`${API_URL}${endpoint}`, {
+    ...options,
+    headers: {
+      ...options.headers,
+      Authorization: token,
+      'Content-Type': 'application/json',
+    },
+  });
+
+  if (!response.ok) {
+    throw new Error(`Failed to ${options.method?.toLowerCase()} ${endpoint}`);
+  }
+
+  return response;
+}
+
+interface FetchProductsParams {
+  category?: string;
+  page?: number;
+  size?: number;
+  sort?: string;
+}
+
+/**
+ * fetchCartItems - API에서 상품 목록을 fetch합니다.
+ * @returns {Promise<ProductType[]>}
+ */
+export async function fetchProducts(params: FetchProductsParams = {}): Promise<ProductType[]> {
+  const queryString = Object.entries(params)
+    .map(([key, value]) => {
+      if (key === 'category' && value === 'all') {
+        return;
+      } else {
+        return `${key}=${value}`;
+      }
+    })
+    .join('&');
+  const response = await makeRequest(`/products?${queryString}`, {
+    method: 'GET',
+  });
+  const data = await response.json();
+
+  return data.content;
+}
+
+/**
+ * addCartItem - 선택한 상품을 장바구니에 추가합니다.
+ * @returns {Promise<void>}
+ */
+export async function addCartItem(productId: number): Promise<void> {
+  await makeRequest('/cart-items', {
+    method: 'POST',
+    body: JSON.stringify({ productId }),
+  });
+}
+
+/**
+ * fetchCartItem - API에서 장바구니 목록을 fetch합니다.
+ * @returns {Promise<CartItemType[]>}
+ */
+export async function fetchCartItem(): Promise<CartItemType[]> {
+  const response = await makeRequest('/cart-items?page=0&size=100', {
+    method: 'GET',
+  });
+
+  const data = await response.json();
+
+  return data.content;
+}
+
+/**
+ * deleteCartItem - 선택한 상품을 장바구니에서 삭제합니다.
+ * @returns {Promise<void>}
+ */
+export async function deleteCartItem(cartItemId: number): Promise<void> {
+  await makeRequest(`/cart-items/${cartItemId}`, {
+    method: 'DELETE',
+  });
+}

리뷰: 그런데.. 이번 장바구니 api는 20개만 담을 수 있게 만들어져있더라구요..

지금 api 구현사항에 맞출지, 아니면 미래의 변경에도 용이하게 코드를 바꿀지는 생각해 봐야할 문제인 것 같아요.

이 부분을 바꿔야 한다면

위의
>if (key === 'category' && value === 'all')

이 코드에서도  default로 들어갈 수 있는 sort=id,asc도 생각해봐야 할 것 같고요
@@ -0,0 +1,21 @@
+import { SelectHTMLAttributes, PropsWithChildren } from 'react';
+import styles from './Select.module.css';
+
+interface Props extends PropsWithChildren<SelectHTMLAttributes<HTMLSelectElement>> {
+  options: Record<string, string>;
+}
+
+const Select = ({ options, defaultValue, children, ...props }: Props) => {
+  return (
+    <select {...props} className={styles.selectContainer} defaultValue={defaultValue}>
+      {children}
+      {Object.keys(options).map((option) => (
+        <option key={option} value={option}>
+          {options[option]}
+        </option>
+      ))}
+    </select>
+  );
+};
+
+export default Select;

리뷰: entries라는 함수도 있으니 찾아보시면 좋을 것 같아요..!
``` js
Object.entries(options).map([optionEn,optionKo])=>{
...
}
```
@@ -30,15 +30,17 @@ const OrderItem = () => {
           </div>
         </div>
       </div>
-      <div className="flex flex-row justify-center gap-3">
-        <Link className="w-1/2" href={ROUTE_PATHS.ORDER_DETAIL}>
+      <div className="flex flex-row gap-3">
+        <Link className="w-full" href={`${ROUTE_PATHS.ORDERS_DETAIL}/1`}>
           <Button size="s" className="h-10">
             주문 상세
           </Button>
         </Link>
-        <Button variant="grayFit" size="s" className="h-10 w-1/2">
-          리뷰 달기
-        </Button>
+        <Link className="w-full" href={ROUTE_PATHS.REVIEW}>
+          <Button variant="grayFit" size="s" className="h-10">
+            리뷰 달기
+          </Button>
+        </Link>
       </div>
     </div>
   )

리뷰: href={`${ROUTE_PATHS.ORDER_DETAIL}/1`} 로 변경해준다면 추후에 route가 변경됐을 때 하나하나 찾아서 변경해주지 않아도 될 거 같아요~
나중에는 1 대신 ${id} 로 변경해주시구요!
@@ -7,8 +7,8 @@ const buttonVariants = cva('inline-flex items-center justify-center gap-2 whites
   variants: {
     variant: {
       default: 'bg-primary text-white hover:bg-primary/90 w-full px-4',
-      primaryFit: 'w-fit px-4 text-primary border-solid border border-primary',
-      grayFit: 'w-fit px-4 text-gray-400 border-solid border border-gray-400',
+      primaryFit: 'w-full px-4 text-primary border-solid border border-primary',
+      grayFit: 'w-full px-4 text-gray-400 border-solid border border-gray-400',
     },
 
     size: {
@@ -34,7 +34,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
     return (
       <button className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
     )
-  },
+  }
 )
 Button.displayName = 'Button'
 

리뷰: 공통 컴포넌트를 변경할 때는 꼭 팀원들에게 변경해도 될지 물어봐주시는게 좋아요!
누군가는 w-fit에 맞춰서 개발해놨는데 나중에 들어가보니 ui가 깨져있을 수도 있거든요 ㅠ
@@ -7,8 +7,8 @@ const buttonVariants = cva('inline-flex items-center justify-center gap-2 whites
   variants: {
     variant: {
       default: 'bg-primary text-white hover:bg-primary/90 w-full px-4',
-      primaryFit: 'w-fit px-4 text-primary border-solid border border-primary',
-      grayFit: 'w-fit px-4 text-gray-400 border-solid border border-gray-400',
+      primaryFit: 'w-full px-4 text-primary border-solid border border-primary',
+      grayFit: 'w-full px-4 text-gray-400 border-solid border border-gray-400',
     },
 
     size: {
@@ -34,7 +34,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
     return (
       <button className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
     )
-  },
+  }
 )
 Button.displayName = 'Button'
 

리뷰: 넵 다음에는 공지 후에 변경하도록 하겠습니다!
@@ -0,0 +1,11 @@
+package baseball.util;
+
+import java.util.Random;
+
+public class RamdomNumberGenerator {
+	private static final Random RANDOM = new Random();
+
+	public static Integer generate() {
+		return RANDOM.nextInt(9) + 1;
+	}
+}

리뷰: 무작위의 숫자를 생성하는 클래스를 따로 빼서 테스트를 용이하게 할 수 있게 한 게 좋네요!
@@ -0,0 +1,101 @@
+package baseball.domain;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class NumericBalls implements Iterable {
+	private List<NumericBall> numericBalls;
+
+	public NumericBalls() {
+		numericBalls = new ArrayList<>();
+	}
+
+	public NumericBalls(List<Integer> numbers) {
+		validate(numbers);
+		List<NumericBall> newBalls = new ArrayList<>();
+		for (Integer number : numbers) {
+			newBalls.add(new NumericBall(number));
+		}
+		this.numericBalls = newBalls;
+	}
+
+	private void validate(List<Integer> numbers) {
+		if (numbers.size() != 3) {
+			throw new IllegalArgumentException();
+		}
+	}
+
+	public List<Integer> match(NumericBalls otherBalls) {
+		List<Integer> matchPoints = new ArrayList<>();
+		int matchedCnt = matchBall(otherBalls);
+		int strikeCnt = strikeCheck(otherBalls);
+		matchPoints.add(strikeCnt);
+		matchPoints.add(matchedCnt - strikeCnt);
+		return matchPoints;
+	}
+
+	private Integer matchBall(NumericBalls otherBalls) {
+		Map<Integer, Integer> map = new HashMap<>();
+		for (NumericBall num : numericBalls) {
+			map.put(num.numericBall(), 0);
+		}
+
+		int ballCnt = 0;
+
+		Iterator otherIterator = otherBalls.iterator();
+		while(otherIterator.hasNext()) {
+			NumericBall otherBall = otherIterator.next();
+			if (map.containsKey(otherBall.numericBall())) {
+				map.put(otherBall.numericBall(), map.get(otherBall.numericBall()) + 1);
+			}
+		}
+		for (Integer value : map.values()) {
+			ballCnt += value;
+		}
+		return ballCnt;
+	}
+
+	private Integer strikeCheck(NumericBalls otherBalls) {
+		int strikeCnt = 0;
+		for (int i = 0; i < numericBalls.size(); i++) {
+			if (numericBalls.get(i).equals(otherBalls.numericBall(i))) {
+				strikeCnt++;
+			}
+		}
+		return strikeCnt;
+	}
+
+	public NumericBall numericBall(int index) {
+		return numericBalls.get(index);
+	}
+
+	public int size() {
+		return numericBalls.size();
+	}
+
+	@Override
+	public Iterator iterator() {
+		return new NumericBallsIterator(this);
+	}
+
+	private static class NumericBallsIterator implements Iterator {
+		private int index = 0;
+		private NumericBalls numericBalls;
+
+		public NumericBallsIterator(NumericBalls numericBalls) {
+			this.numericBalls = numericBalls;
+		}
+
+		@Override
+		public boolean hasNext() {
+			return (this.index < this.numericBalls.size());
+		}
+
+		@Override
+		public NumericBall next() {
+			return numericBalls.numericBall(this.index++);
+		}
+	}
+}

리뷰: 리스트에 넣고 맵에 다시 넣는 것보다 처음부터 필드를 맵으로 선언하고 맵의 숫자와 인덱스를 비교하면 효율이 더 좋을 것 같아요.
@@ -0,0 +1,23 @@
+package baseball.view;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+
+public class InputView {
+	private static final Scanner SCANNER = new Scanner(System.in);
+	public static List<Integer> inputNumbers() {
+		System.out.println("숫자를 입력해 주세요 : ");
+		List<Integer> nums = new ArrayList<>();
+		nums.addAll(parse(List.of(SCANNER.next().split(""))));
+		return nums;
+	}
+
+	private static List<Integer> parse(List<String> strArr) {
+		List<Integer> nums = new ArrayList<>();
+		for (String str : strArr) {
+			nums.add(Integer.parseInt(str));
+		}
+		return nums;
+	}
+}

리뷰: 객체를 상수로 선언하면 코드가 훨씬 간편해지지만, final임에도 값이 바뀔 수 있어 관련이슈가 있을 수도 있습니다. 유의하고 사용해주세요:)
@@ -0,0 +1,18 @@
+package racingcar.view;
+
+import camp.nextstep.edu.missionutils.Console;
+
+public class InputView {
+    private static final String ASK_CAR_NAMES = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)";
+    private static final String ASK_TURN = "시도할 회수는 몇회인가요?";
+
+    public static String readCarNames() {
+        System.out.println(ASK_CAR_NAMES);
+        return Console.readLine();
+    }
+
+    public static String readTurn() {
+        System.out.println(ASK_TURN);
+        return Console.readLine();
+    }
+}

리뷰: 메시지 관리에 대해서 static이 좋은지 enum이 좋은지 이야기 나눠보면 좋을 것 같습니다. 항상 어떤게 나은지 고민해서 매번 쓸때마다 한번은 이렇게 한번은 저렇게 했었거든요.
@@ -0,0 +1,18 @@
+package racingcar.view;
+
+import camp.nextstep.edu.missionutils.Console;
+
+public class InputView {
+    private static final String ASK_CAR_NAMES = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)";
+    private static final String ASK_TURN = "시도할 회수는 몇회인가요?";
+
+    public static String readCarNames() {
+        System.out.println(ASK_CAR_NAMES);
+        return Console.readLine();
+    }
+
+    public static String readTurn() {
+        System.out.println(ASK_TURN);
+        return Console.readLine();
+    }
+}

리뷰: 저는 System.out.print~사용하는 부분은 전부 출력으로 사용자에게 보여지는 부분이라고 생각해서 모두 OutputView에 작성하고 있습니다. 사실 첨에 저도 InputView에 써도 될지 고민했는데 이 부분에 대해서는 어떻게 생각하시는지 궁금합니다.
@@ -0,0 +1,38 @@
+package racingcar.view;
+
+import java.util.stream.Collectors;
+import racingcar.domain.RacingCar;
+import racingcar.domain.RacingCars;
+import racingcar.domain.Winner;
+
+public class OutputView {
+    private static final String RACE_RESULT_MESSAGE = "\n실행 결과";
+    private static final String DISTANCE_UNIT = "-";
+    private static final String RACE_RESULT_FORMAT = "%s : %s%n";
+    private static final String WINNER = "최종 우승자 : ";
+    private static final String DELIMITER = ", ";
+
+    public static void printRaceResultNotice() {
+        System.out.println(RACE_RESULT_MESSAGE);
+    }
+
+    public static void printRaceResult(RacingCars racingCars) {
+        racingCars.getRacingCars()
+                .forEach(racingCar -> System.out.printf(RACE_RESULT_FORMAT
+                        , racingCar.getCarName(), changeDistanceForm(racingCar.getDistance())));
+        System.out.println();
+    }
+
+    private static String changeDistanceForm(int distance) {
+        return DISTANCE_UNIT.repeat(distance);
+    }
+
+    public static void printWinner(Winner winner) {
+        System.out.print(WINNER);
+        String winners = winner.getWinner()
+                .stream()
+                .map(RacingCar::getCarName)
+                .collect(Collectors.joining(DELIMITER));
+        System.out.println(winners);
+    }
+}

리뷰: Winner클래스의 getWinner()에서 아래 연산이 끝난 결과를 리턴하면 getter를 사용하지 않고도 결과를 받아올 수 있다고 생각하는데 어떻게 생각하시나요?
@@ -0,0 +1,17 @@
+package racingcar.domain;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class CarNameTest {
+    @ParameterizedTest
+    @DisplayName("차량이름은 1-5글자이고 특수문자를 포함하지 않는다.")
+    @ValueSource(strings = {"abcdef", "", "$", "&", " ", "!", "(", "$", "%"})
+    void createCarName(String input) {
+        assertThatThrownBy(() -> new CarName(input))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+}
\ No newline at end of file

리뷰: assertThatCode를 이용해서  예외가 발생하지 않고 올바르게 되는지 한번 테스트 해보는 것도 추천 드립니다.
@@ -0,0 +1,22 @@
+package racingcar.domain;
+
+public enum ErrorMessage {
+    ERROR("[ERROR] "),
+    INVALID_NAME_LENGTH(ERROR + "차량 이름은 1-5글자 사이여야 합니다."),
+    INVALID_NAME_TYPE(ERROR + "차량 이름은 특수문자를 포함할 수 없습니다."),
+    DUPLICATED_NAME(ERROR + "차량 이름은 중복될 수 없습니다."),
+    INVALID_RACING_CARS_SIZE(ERROR + "경주에는 최소 2대의 차량이 참여해야합니다."),
+    INVALID_TURN_RANGE(ERROR + "시도횟수는 최소 1회 이상이어야 합니다."),
+    INVALID_TURN_TYPE(ERROR + "숫자 이외의 값은 입력할 수 없습니다."),
+    CANT_FIND_CAR(ERROR + "차량을 찾을 수 없습니다.");
+
+    private final String message;
+
+    ErrorMessage(String message) {
+        this.message = message;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+}

리뷰: ERROR은 변함 없을 것 같은데 상수로 두고 생성자 부분에서 
this.message = "ERROR" + message로 한다면 메시지 작성할 때 중복을 피하는데 도움 될 것 같습니다.
@@ -0,0 +1,63 @@
+package racingcar.domain;
+
+import static racingcar.domain.ErrorMessage.CANT_FIND_CAR;
+import static racingcar.domain.ErrorMessage.DUPLICATED_NAME;
+import static racingcar.domain.ErrorMessage.INVALID_RACING_CARS_SIZE;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class RacingCars {
+    private static final int MIN_SIZE = 2;
+    private final List<RacingCar> racingCars;
+    private RacingCar maxDistanceCar;
+
+    public RacingCars(List<RacingCar> racingCars) {
+        validateSize(racingCars);
+        validateDuplicated(racingCars);
+        this.racingCars = racingCars;
+    }
+
+    private void validateSize(List<RacingCar> racingCars) {
+        if (racingCars.size() < MIN_SIZE) {
+            throw new IllegalArgumentException(INVALID_RACING_CARS_SIZE.getMessage());
+        }
+    }
+
+    private void validateDuplicated(List<RacingCar> racingCars) {
+        if (isDuplicated(racingCars)) {
+            throw new IllegalArgumentException(DUPLICATED_NAME.getMessage());
+        }
+    }
+
+    private boolean isDuplicated(List<RacingCar> racingCars) {
+        int sizeAfterCut = (int) racingCars.stream()
+                .distinct()
+                .count();
+        return racingCars.size() != sizeAfterCut;
+    }
+
+    public void startRace() {
+        racingCars
+                .forEach(racingCar ->
+                        racingCar.moveForward(RandomNumberGenerator.generateRandomNumber()));
+    }
+
+    public Winner selectWinner() {
+        findMaxDistanceCar();
+        List<RacingCar> winner = racingCars.stream()
+                .filter(maxDistanceCar::isWinner)
+                .collect(Collectors.toList());
+        return new Winner(winner);
+    }
+
+    private void findMaxDistanceCar() {
+        maxDistanceCar = racingCars.stream()
+                .max(RacingCar::compareTo)
+                .orElseThrow(() -> new IllegalArgumentException(CANT_FIND_CAR.getMessage()));
+    }
+
+    public List<RacingCar> getRacingCars() {
+        return racingCars;
+    }
+}

리뷰: return에 바로 작성하면 가독성이 조금 떨어져 보일까요? 
이 메소드 처럼 한번만 사용하는 변수라면 변수로 지정하지 않고 바로 작성해도 될 것 같은데...
이러면 가독성이 떨어져 보이기도 할 것 같아 고민이 되서 여쭤봅니다.
@@ -0,0 +1,51 @@
+package racingcar.domain;
+
+import static racingcar.domain.ErrorMessage.INVALID_NAME_LENGTH;
+import static racingcar.domain.ErrorMessage.INVALID_NAME_TYPE;
+
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+public class CarName {
+    private static final int MAX_LENGTH = 5;
+    private static final String REGEX = "[0-9|a-zA-Zㄱ-ㅎㅏ-ㅣ가-힣]*";
+    private final String carName;
+
+    public CarName(String carName) {
+        validateLength(carName);
+        validateType(carName);
+        this.carName = carName;
+    }
+
+    private void validateLength(String carName) {
+        if (carName.isEmpty() || carName.length() > MAX_LENGTH) {
+            throw new IllegalArgumentException(INVALID_NAME_LENGTH.getMessage());
+        }
+    }
+
+    private void validateType(String carName) {
+        if (!Pattern.matches(REGEX, carName)) {
+            throw new IllegalArgumentException(INVALID_NAME_TYPE.getMessage());
+        }
+    }
+
+    public String getCarName() {
+        return carName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof CarName carName1)) {
+            return false;
+        }
+        return Objects.equals(carName, carName1.carName);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(carName);
+    }
+}

리뷰: 정규표현식에 문자지정한 후 {1,5}을 사용하면 글자 수 제한 하는데도 도움이 될 거에요.
@@ -0,0 +1,35 @@
+package racingcar.domain;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+import java.util.List;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class RacingCarsTest {
+    @ParameterizedTest
+    @DisplayName("경주에는 최소 2대 이상의 차량이 참여해야 하며 중복될 수 없다.")
+    @ValueSource(strings = {"상추", "상추,상추"})
+    void createRacingCars(String input) {
+        List<RacingCar> racingCars = InputConvertor.convertToRacingCars(input);
+        assertThatThrownBy(() -> new RacingCars(racingCars))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    @DisplayName("우승자는 상추와 배추다.")
+    void selectWinner() {
+        RacingCar sangchu = new RacingCar(new CarName("상추"));
+        RacingCar baechu = new RacingCar(new CarName("배추"));
+        RacingCar moodosa = new RacingCar(new CarName("무도사"));
+        RacingCars racingCars = new RacingCars(List.of(sangchu, baechu, moodosa));
+        sangchu.moveForward(5);
+        baechu.moveForward(6);
+        Winner winner = racingCars.selectWinner();
+        assertThat(winner.getWinner().get(0).getCarName()).isEqualTo("상추");
+        assertThat(winner.getWinner().get(1).getCarName()).isEqualTo("배추");
+    }
+}
\ No newline at end of file

리뷰: 2대 이상인 경우 / 이름 중복되면 안되는 경우에 대해 메소드를 나눠서 테스트 해도 좋을거같슴다
@@ -0,0 +1,17 @@
+package racingcar.domain;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class TurnTest {
+    @ParameterizedTest
+    @DisplayName("1회 미만, 숫자 이외 값 입력시 예외 발생")
+    @ValueSource(strings = {"0", "-1", "d", "#", "", " "})
+    void createTurn(String input) {
+        assertThatThrownBy(() -> new Turn(input))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+}
\ No newline at end of file

리뷰: 저는 validate 로직에 대해서 
1. 숫자만 입력 받아야하는 변수에 문자 입력되는 경우 -> input에서 검증
2. 1회 미만으로 입력받는 메소드 관련 검증 -> turn메소드에서 검증 하는 방식으로 항상 작성해 왔는데 이부분에 대해서 어떻게 생각하는지 궁금합니다 !
@@ -0,0 +1,18 @@
+package racingcar.view;
+
+import camp.nextstep.edu.missionutils.Console;
+
+public class InputView {
+    private static final String ASK_CAR_NAMES = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)";
+    private static final String ASK_TURN = "시도할 회수는 몇회인가요?";
+
+    public static String readCarNames() {
+        System.out.println(ASK_CAR_NAMES);
+        return Console.readLine();
+    }
+
+    public static String readTurn() {
+        System.out.println(ASK_TURN);
+        return Console.readLine();
+    }
+}

리뷰: 저는 이번에 프리코스를 진행하면서 나름의 기준을 세워봤습니다!
1. enum으로 사용할만큼 상수의 개수가 충분한지? 최소 3개 이상 
2. 프로그램이 확장된다면 해당 enum 에 상수가 추가될 가능성이 있는지?
3. 다른 클래스에서도 사용될 가능성이 있는지?
@@ -0,0 +1,18 @@
+package racingcar.view;
+
+import camp.nextstep.edu.missionutils.Console;
+
+public class InputView {
+    private static final String ASK_CAR_NAMES = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)";
+    private static final String ASK_TURN = "시도할 회수는 몇회인가요?";
+
+    public static String readCarNames() {
+        System.out.println(ASK_CAR_NAMES);
+        return Console.readLine();
+    }
+
+    public static String readTurn() {
+        System.out.println(ASK_TURN);
+        return Console.readLine();
+    }
+}

리뷰: 저도 처음에 1주차 미션을 진행할 때는 사용자에게 보이는 부분이라고 생각하여 OutputView에 작성했었는데 많은 분들 코드를 보다보니 입력을 요청하는 메시지는 InputView에서 처리하시더라구요. 그리고 결정적으로 우테코에서 제공한 요구사항 부분을 보시면 입력을 요구하는 메시지를 InputView에서 사용하는걸 보실 수 있습니다.
<img width="914" alt="image" src="https://github.com/parksangchu/java-racingcar-6/assets/142131857/87aa54ff-07c6-4c03-87fe-331d6aba9664">