해당 포스트는 인프런의 Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트 강의의 도움을 받았습니다
간단한 계산기 프로그램입니다.
기능을 세 가지 클래스로 나누겠습니다.
계산하는 로직을 분리하겠습니다.
public class Calculator {
public long calculate(long num1, String operator, long num2) {
return switch (operator) {
case "+" -> num1 + num2;
case "-" -> num1 - num2;
case "*" -> num1 * num2;
case "/" -> num1 / num2;
default -> throw new InvalidOperatorException();
};
}
}
이렇게 만들어진 코드에 대해서 테스트를 해봅시다.
어떤 테스트를 해야 할까요?
public class CalculatorTest {
@Test
public void 덧셈_연산을_할_수_있다() {
// given
long num1 = 2;
String operator = "+";
long num2 = 3;
Calculator calculator = new Calculator();
// when
long result = calculator.calculate(num1, operator, num2);
// then
Assertions.assertEquals(5, result);
}
임의의 파라미터를 두고, calculate()
메서드가 잘 실행되는지 확인해 보았습니다.
@Test
public void 잘못된_연산자가_요청으로_들어올_경우_에러가_난다() {
// given
long num1 = 4;
String operator = "x"; // 잘못된 연산자입니다.
long num2 = 2;
Calculator calculator = new Calculator();
// when
// then
Assertions.assertThrows(InvalidOperatorException.class, () -> {
calculator.calculate(num1, operator, num2);
});
}
assertThrows는 두 번째 인자인 람다식을 실행했을 때 첫 번째 인자로 주어지는 예외 사항이 발생하는지를 테스트합니다.
입력을 받아오는 것도 클래스로 분리해 봅시다.
기존 코드를 보니, 다음과 같은 역할을 하면 되겠습니다.
public class CalculationRequestReader {
public String[] read() {
Scanner scanner = new Scanner(System.in);
System.out.println("Enter two numbers and an operator (e.g 1 + 2):");
String result = scanner.nextLine();
return result.split(" ");
}
}
역시 이에 대하여 테스트 코드를 작성해 봅니다.
표준 입력은 System.setIn(new ByteArrayInputStream("".getBytes()));
로 구현할 수 있다고 합니다.
public class CalculationRequestReaderTest {
@Test
public void System_in으로_데이터를_읽어들일_수_있다() {
// given
CalculationRequestReader calculationRequestReader = new CalculationRequestReader();
// when
System.setIn(new ByteArrayInputStream("2 + 3".getBytes()));
String[] result = calculationRequestReader.read();
// then
Assertions.assertEquals("2", result[0]);
Assertions.assertEquals("+", result[1]);
Assertions.assertEquals("3", result[2]);
}
}
객체로 입력받은 데이터를 관리해 봅시다.
public class CalculationRequst {
private long num1;
private String operator;
private long num2;
public CalculationRequst(String[] parts) {
this.num1 = Long.parseLong(parts[0]);
this.num2 = Long.parseLong(parts[2]);
this.operator = parts[1];
}
public long getNum1() {
return num1;
}
public String getOperator() {
return operator;
}
public long getNum2() {
return num2;
}
}
입력받은 결과로 나온 String 배열에서 숫자는 long으로, 연산자는 String으로 변환하여 받습니다.
@SpringBootApplication
public class SampleApplication {
public static void main(String[] args) {
String[] parts = new CalculationRequestReader().read();
CalculationRequst calculationRequst = new CalculationRequst(parts);
long answer = new Calculator().calculate(
calculationRequst.getNum1(),
calculationRequst.getOperator(),
calculationRequst.getNum2()
);
System.out.println(answer);
}
}
클래스를 활용하여 main 메서드를 수정하였습니다.
여기서 보니, CalculationRequestReader에서 String[]이 아니라 CalculationRequest를 반환해도 될 것 같습니다.
public class CalculationRequestReader {
public CalculationRequst read() {
Scanner scanner = new Scanner(System.in);
System.out.println("Enter two numbers and an operator (e.g 1 + 2):");
String result = scanner.nextLine();
String[] parts = result.split(" ");
return new CalculationRequst(parts); // 추가
}
}
@SpringBootApplication
public class SampleApplication {
public static void main(String[] args) {
CalculationRequst calculationRequst =
new CalculationRequestReader().read(); // 한 줄로 줄었습니다.
long answer = new Calculator().calculate(
calculationRequst.getNum1(),
calculationRequst.getOperator(),
calculationRequst.getNum2()
);
System.out.println(answer);
}
}
CalculationRequest는 데이터의 일관성이 필요하므로 확인하는 코드를 작성해 봅시다.
public class CalculationRequst {
private final long num1;
private final long num2;
private final String operator;
public CalculationRequst(String[] parts) {
if(parts.length != 3) { // 숫자, 연산자, 숫자 조합이 아닌 경우
throw new BadRequestException();
}
if(parts[1].length() != 1 || isInvalidOperator(parts[1])) {
// 연산자의 길이 또는 종류가 부적절한 경우
throw new InvalidOperatorException();
}
this.num1 = Long.parseLong(parts[0]);
this.num2 = Long.parseLong(parts[2]);
this.operator = parts[1];
}
private static boolean isInvalidOperator(String operator) {
return !operator.equals("+") &&
!operator.equals("-") &&
!operator.equals("*") &&
!operator.equals("/");
}
...
}
추가된 예외 상황에 대하여 테스트코드를 추가적으로 작성합니다.
public class CalculationRequestTest {
@Test
public void 유효한_길이의_숫자가_들어오지_않으면_에러를_던진다() {
// given
String[] parts = new String[]{"232", "+"};
// when
// then
Assertions.assertThrows(BadRequestException.class, () -> {
new CalculationRequst(parts);
});
}
테스트 커버리지는 코드 중 테스트로 구현된 부분이 얼마나 되는지를 말합니다.
intellij에서는 테스트 폴더에서 이 부분을 실행시키면 됩니다.