Spring Test Code - 기능을 클래스로 분리하자

양말·2024년 6월 14일
0

SpringTestCode

목록 보기
1/10
post-thumbnail

해당 포스트는 인프런의 Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트 강의의 도움을 받았습니다

간단한 계산기 프로그램입니다.

기능을 세 가지 클래스로 나누겠습니다.

Calculator

계산하는 로직을 분리하겠습니다.

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();
        };
    }
}

이렇게 만들어진 코드에 대해서 테스트를 해봅시다.
어떤 테스트를 해야 할까요?

  1. 사칙연산이 잘 되는지
  2. 올바르지 않은 파라미터에 대해 예외를 던지는지

사칙연산이 잘 될까요?

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는 두 번째 인자인 람다식을 실행했을 때 첫 번째 인자로 주어지는 예외 사항이 발생하는지를 테스트합니다.

CalculationRequsetReader

입력을 받아오는 것도 클래스로 분리해 봅시다.

기존 코드를 보니, 다음과 같은 역할을 하면 되겠습니다.

  1. 입력 받기
  2. 입력 데이터를 띄어쓰기 단위로 파싱하기
  3. 파싱한 결과를 String 배열 형태로 반환하기
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]);
    }
}

CalculationRequest 객체

객체로 입력받은 데이터를 관리해 봅시다.

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 생성자에서 데이터를 확인

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에서는 테스트 폴더에서 이 부분을 실행시키면 됩니다.

정리하기

  1. 분리가 가능한 기능을 찾아 클래스로 분리해 보았습니다.
  2. 분리된 클래스를 위한 테스트를 작성해 보았습니다.
profile
코끼리

0개의 댓글