Build System으로 IntelliJ를 사용하는 자바 프로젝트 환경에서 JUnit5, AssertJ를 설치하고 테스트 코드를 작성해봤다.
처음 프로젝트를 생성할 때 빌드 시스템을 익숙하던 Gradle로 했어야 했는데, 별도로 설정을 하지 않아서 IntelliJ로 됐었다. 그래서 이번에는 IDE에 내장된 기능으로 의존성을 추가해봤다.

File - Project Structure - Modules에서 '+' - Library - From Maven... 기능을 선택한다.

여기서 원하는 라이브러리 키워드(JUnit, AssertJ 등)를 입력하고 검색 버튼을 누르면 Maven 레포지토리에서 검색 결과를 보여준다. 하지만 아무거나 선택하면 공식이 아닌 유저가 올린 라이브러리를 받을 수도 있기 때문에 Maven 공식 사이트에서 확인해주자. 원하는 버전 선택하면 된다.

<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>

빌드 시스템이 Maven이라면 저 코드를 그대로 복붙하면 되겠지만, 지금은 안 되기 때문에 IntelliJ에서 검색해보자. {groupId}:{artifactId}:{version}으로 검색하면 된다.

원하는 위치에 test 디렉터리를 생성한다. 이후 해당 디렉터리 우클릭 - Open Module Settings를 열고, Mark as로 Tests를 지정한다.
이렇게 하면 Ctrl + Shift + T 등으로 테스트 파일을 생성하면 자동으로 이 디렉터리가 지정된다.
public <T> BigDecimal calculate(T a, T b, OperatorType operatorType) throws ArithmeticException {
// 어떤 데이터 타입이 들어오더라도 BigDecimal 타입으로 변환
BigDecimal bdA = new BigDecimal(String.valueOf(a));
BigDecimal bdB = new BigDecimal(String.valueOf(b));
// 연산 수행 후 결과 저장 및 반환
BigDecimal result = operatorType.calculate(bdA, bdB).stripTrailingZeros();
resultQueue.offer(result);
return result;
}
public enum OperatorType {
ADD("+", BigDecimal::add),
SUB("-", BigDecimal::subtract),
MUL("*", BigDecimal::multiply),
DIV("/", (a, b) -> a.divide(b, 10, RoundingMode.HALF_EVEN));
private final String operator;
private final BiFunction<BigDecimal, BigDecimal, BigDecimal> calculateFunc;
OperatorType(String operator, BiFunction<BigDecimal, BigDecimal, BigDecimal> calculateFunc) {
this.operator = operator;
this.calculateFunc = calculateFunc;
}
public BigDecimal calculate(BigDecimal a, BigDecimal b) {
return this.calculateFunc.apply(a, b);
}
public static OperatorType getOperator(String s) {
return Arrays.stream(values())
.filter(op -> op.operator.equals(s))
.findFirst()
.orElseThrow(() -> new InvalidOperatorException("유효하지 않은 연산자입니다."));
}
}
간단한 계산기 코드를 테스트해 볼 것이다. calculate() 메소드에서는 입력 파라미터를 제네릭 타입 T로 받고 있다. 이 T에 어떤 타입이 들어와도 항상 일정한 결과를 return 하는지 테스트하고 싶다.
JUnit의 @ParameterizedTest는 여러 개의 파라미터를 테스트 코드에 적용할 때 사용할 수 있다.
넘겨줄 파라미터값은 어노테이션을 통해 지정할 수 있는데, 방법이 여러 가지 있다. 나는 이 중 @MethodSource를 사용했다. 타입이 다른 3개의 인자(complex arguments)를 넘겨줘야 하기 때문이다.
@DisplayName("계산기 파라미터 테스트 - int")
@ParameterizedTest
@MethodSource("validIntInput")
void 계산기_파라미터_테스트_int(int a, int b, OperatorType operatorType, int expected) {
// Given
// When
BigDecimal actual = arithmeticCalculator.calculate(a, b, operatorType);
// Then
assertThat(actual).isEqualTo(new BigDecimal(String.valueOf(expected)));
}
@DisplayName("계산기 파라미터 테스트 - double")
@ParameterizedTest
@MethodSource("validDoubleInput")
void 계산기_파라미터_테스트_double(double a, double b, OperatorType operatorType, double expected) {
// Given
// When
BigDecimal actual = arithmeticCalculator.calculate(a, b, operatorType);
// Then
// double -> BigDecimal 변환하면서 불필요한 0을 제거해줘야 함
assertThat(actual).isEqualTo(new BigDecimal(String.valueOf(expected)).stripTrailingZeros());
}
@DisplayName("계산기 파라미터 테스트 - String")
@ParameterizedTest
@MethodSource("validStringInput")
void 계산기_파라미터_테스트_String(String a, String b, OperatorType operatorType, String expected) {
// Given
// When
BigDecimal actual = arithmeticCalculator.calculate(a, b, operatorType);
// Then
// double -> BigDecimal 변환하면서 불필요한 0을 제거해줘야 함
assertThat(actual).isEqualTo(new BigDecimal(String.valueOf(expected)).stripTrailingZeros());
}
static Stream<Arguments> validIntInput() {
return Stream.of(
arguments(1, 2, OperatorType.ADD, 3),
arguments(5, 3, OperatorType.SUB, 2),
arguments(4, 7, OperatorType.MUL, 28),
arguments(10, 2, OperatorType.DIV, 5),
arguments(-3, -6, OperatorType.ADD, -9),
arguments(9, -2, OperatorType.SUB, 11),
arguments(-4, 3, OperatorType.MUL, -12),
arguments(8, -4, OperatorType.DIV, -2),
arguments(0, 5, OperatorType.ADD, 5),
arguments(7, 0, OperatorType.SUB, 7),
arguments(0, 10, OperatorType.MUL, 0),
arguments(10, 10, OperatorType.DIV, 1)
);
}
static Stream<Arguments> validDoubleInput() {
return Stream.of(
arguments(1.5, 2.3, OperatorType.ADD, 3.8),
arguments(5.75, 3.25, OperatorType.SUB, 2.5),
arguments(4.1, 7.2, OperatorType.MUL, 29.52),
arguments(10.8, 2.4, OperatorType.DIV, 4.5),
arguments(-3.7, -6.2, OperatorType.ADD, -9.9),
arguments(9.9, -2.1, OperatorType.SUB, 12),
arguments(-4.5, 3.3, OperatorType.MUL, -14.85),
arguments(8.4, -4.2, OperatorType.DIV, -2),
arguments(0.0, 5.5, OperatorType.ADD, 5.5),
arguments(7.3, 0.0, OperatorType.SUB, 7.3),
arguments(0.0, 10.9, OperatorType.MUL, 0),
arguments(10.2, 10.2, OperatorType.DIV, 1)
);
}
static Stream<Arguments> validStringInput() {
return Stream.of(
arguments("1.5", "-2", OperatorType.MUL, "-3.0"),
arguments("2", "3", OperatorType.ADD, "5"),
arguments("-4.5", "2", OperatorType.MUL, "-9.0"),
arguments("10", "2.5", OperatorType.DIV, "4.0"),
arguments("7.8", "3", OperatorType.SUB, "4.8"),
arguments("-10", "-2.5", OperatorType.ADD, "-12.5"),
arguments("0", "4.2", OperatorType.MUL, "0"),
arguments("8.5", "-2", OperatorType.DIV, "-4.25"),
arguments("3", "0.5", OperatorType.MUL, "1.5"),
arguments("-3.2", "5", OperatorType.ADD, "1.8"),
arguments("7", "-2.2", OperatorType.SUB, "9.2"),
arguments("10.4", "2", OperatorType.DIV, "5.2")
);
}
}

(테스트 데이터 생성은PT의 힘을 빌렸다)
calculate() 메소드의 T에 int, double, String 등 어떤 타입이 와도 정상적으로 동작하는 것을 확인할 수 있었다.
만약 a나 b에 "asdf" 같은 숫자가 아닌 데이터가 들어오면 NumberFormatException 등이 발생하겠지만, 이 부분은 이미 input을 받는 메소드에서 처리하고 있기 때문에 따로 설정해주지 않았다.