.png)
모든 코드는 여기를 클릭 하시면 확인 하실 수 있습니다.
안녕하세요. 2탄에서는 사용자와 함께 플레이 할 이름이 입력되었을 때 다양한 상황에 대해서 구현해보았습니다. 이번 포스팅에서는 아래와 같은 부분을 진행하겠습니다.
package kail.study.java.racing;
import static org.assertj.core.api.Assertions.*;
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;
public class CarTest {
@ParameterizedTest
@ValueSource(strings = {"kyle", "hodle", "pobi"})
@DisplayName("이름 규칙에 문제가 없어서 정상적으로 생성되는 경우")
void 정상적인_경우(String name) {
assertThat(new Car(name));
}
@ParameterizedTest
@ValueSource(strings = {"overLength", "helloWorld", "ImTheKing"})
void 길이_초과(String name) {
assertThatThrownBy(() -> {
new Car(name);
}).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("길이가 초과");
}
@ParameterizedTest
@ValueSource(strings = {"", " ", " "})
void 공백_예외(String name) {
assertThatThrownBy(() -> {
new Car(name);
}).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("공백은 입력할 수 없습니다.");
}
@Test
void 널이라서_예외() {
assertThatThrownBy(() -> {
new Car(null);
}).isInstanceOf(NullPointerException.class)
.hasMessageContaining("널값은 입력할 수 없습니다.");
}
}
package kail.study.java.racing;
import java.util.Objects;
public class Car {
public static final int MAX_LENGTH = 5;
private final String name;
public Car(String name) {
validate(name);
this.name = name;
}
private void validate(String name) {
checkNull(name);
checkBlank(name);
checkLength(name);
}
private void checkNull(String name) {
if(name == null)
throw new NullPointerException("널값은 입력할 수 없습니다.");
}
private void checkBlank(String name) {
if(name.trim().isEmpty())
throw new IllegalArgumentException("공백은 입력할 수 없습니다.");
}
private void checkLength(String name) {
if (name.length() > MAX_LENGTH)
throw new IllegalArgumentException("길이가 초과되었습니다.");
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Car car = (Car)o;
return Objects.equals(name, car.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
public String getName() {
return name;
}
}
package kail.study.java.racing;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class RacingCars {
private final List<Car> cars;
public RacingCars(List<Car> cars) {
validate(cars);
this.cars = cars;
}
private void validate(List<Car> names) {
checkDuplication(names);
}
private void checkDuplication(List<Car> names) {
Set<Car> set = names.stream().collect(Collectors.toSet());
if(set.size() != names.size())
throw new IllegalArgumentException("중복된 이름이 존재합니다.");
}
}
현재 프로젝트 구조입니다. 보시다시피 도메인의 역할을 하는 부분과 View의 역할을 하는 부분이 존재하며 Utility 클래스가 존재하기에 패키지별로 묶어서 분류를 해보도록 하겠습니다.

도메인의 기능을 담당하고 있는 Car와 RacingCars 를 도메인 패키지에 추가하고 Utility 클래스는 Util 패키지로, View를 담당하고 있는 부분을 View 패키지로 분리해 전체적으로 MVC 패턴을 따르는 형태로 분리하였습니다.

지금까지는 사용자의 이름을 입력 받고 그 이름을 기반으로 각각의 자동차가 정상적으로 생성되는지 테스트 및 구현을 했다. 기능 2에서는 몇 라운드를 진행하고 싶은지에 대해 입력 받고 그 라운드를 검증하는 부분을 작성해보려 한다.
라운드와 관련된 규칙은 아래와 같고, 아래의 규칙들을 하나씩 Test —> Production —> Refactoring 하는 형태로 진행하고자 한다.
규칙 1 라운드의 수는 1회 ~ 30회 사이로 설정 할 수 있으며 이 경우 정상작동 해야한다.
public class RoundTest {
@ParameterizedTest
@ValueSource(ints = {1,30,25,12})
@DisplayName("1회~30회 사이의 라운드가 정상적으로 입력 된 경우")
void correctRound(int round) {
assertThat(new Round(round));
}
}
package kail.study.java.racing.domain;
public class Round {
private final int round;
public Round(int round) {
this.round = round;
}
}
public Round(int round) {
validate(round);
this.round = round;
}
private void validate(int round) {
if (round < 1 || round > 30) {
throw new IllegalArgumentException("플레이 할 라운드는 1~30회만 가능합니다.");
}
}
// RoundTest 에 추가하시면 됩니다.
@ParameterizedTest
@ValueSource(ints = {-1,0,-3})
void smallerThanOne(int round) {
assertThatThrownBy(()->{
new Round(round);
}).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("플레이 할 라운드는 1~30");
}
@ParameterizedTest
@ValueSource(strings = {"문자열은","아니되오","숫자로","가자구"})
void stringException(String round) {
assertThatThrownBy(()->{
new Round(round);
}).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("플레이 할 라운드는 1~30");
}
Integer.parseInt() 를 통해 검증해야 할까?NumberFormatException 은 검증하는 것도 좋은 방법이라 생각한다. 하지만 프로그램 전체로 봤을 때는 입력 오류라기 보다는 Round 생성 규칙 위반에 가깝다고 생각되고 InputView를 검증하는 것 보다 Round를 검증하는 부분이 테스트하기도 용이하기 때문에 전자로 진행하겠다. 이 부분은 개인의 선택이 아닐까 싶다. package kail.study.java.racing.domain;
import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
public class RoundTest {
@ParameterizedTest
@ValueSource(strings = {"1","30","25","12"})
@DisplayName("1회~30회 사이의 라운드가 정상적으로 입력 된 경우")
void correctRound(String round) {
assertThat(new Round(round));
}
@ParameterizedTest
@ValueSource(strings = {"-1","0","-3"})
void smallerThanOne(String round) {
assertThatThrownBy(()->{
new Round(round);
}).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("플레이 할 라운드는 1~30");
}
@ParameterizedTest
@ValueSource(strings = {"문자열은","아니되오","숫자로","가자구"})
void stringException(String round) {
assertThatThrownBy(()->{
new Round(round);
}).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("문자열은 입력 할 수 없습니다.");
}
}
package kail.study.java.racing.domain;
public class Round {
private final int round;
public Round(String round) {
validate(round);
this.round = Integer.parseInt(round);
}
private void validate(String round) {
checkNumberFormat(round);
checkRange(round);
}
private void checkNumberFormat(String round) {
try {
Integer.parseInt(round);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("문자열은 입력 할 수 없습니다.");
}
}
private void checkRange(String round) {
int rounds = Integer.parseInt(round);
if (rounds < 1 || rounds > 30) {
throw new IllegalArgumentException("플레이 할 라운드는 1~30회만 가능합니다.");
}
}
}
private static final int MIN_ROUND = 1;
private static final int MAX_ROUND = 30;
private void checkRange(String round) {
int rounds = Integer.parseInt(round);
if (rounds < MIN_ROUND || rounds > MAX_ROUND) {
throw new IllegalArgumentException("플레이 할 라운드는 1~30회만 가능합니다.");
}
}
command + alt + c 를 활용하면 public 으로 자동완성을 지원하는데 이 경우 타 클래스에서 상수를 활용 할 수 있다. 원시타입인 Int 형이기 때문에 값을 변경할 수는 없지만 현재 명시적으로 저 상수들을 사용 할 것이라는 곳이 존재하지 않기 때문에 private로 변경하였다.