본문 바로가기
개발 공부

[개발 공부] Spock Test Framework 실습

by 볼링치는 개발자 2021. 7. 20.
반응형

지난 포스팅에서는 Spock Framework에 대한 개념과 JUnit테스트와의 차이점 등에 대해 이론적으로 포스팅했었습니다.

https://programforlife.tistory.com/101

 

[개발 공부] Spock Test Framework

이번 포스팅에서는 인턴을 시작하면서 받은 교육 중, Spock Test Framework에 대해 정리해보려 합니다. Java를 사용한 개발을 진행하면서 들어본 테스트는 JUnit이라는 것인데, Spock Framework는 생소한 기

programforlife.tistory.com

이번 포스팅에서는 Spock Test Framework을 통한 실무에 사용할 수 있는 실습을 해보려고 합니다.


실습 준비

먼저, 실습을 진행하기 위한 프로젝트는 구조는 다음과 같습니다.

  1. 클라이언트가 보낸 총액 계산 요청을 SalesRestController가 받습니다.
  2. SalesRestController는 OrderServiceImpl에게 결과 값을 요청합니다.
  3. 모든 계산 로직은 OrderServiceImpl에 있으며, 계산에 필요한 상품의 가격 정보를 ProductStoreImpl에게 요청해서 받아오고 계산을 합니다.
  4. 계산이 완료되면 SalesRestController에게 리턴되며, SalesRestController가 클라이언트 측으로 계산 결괏값을 전송합니다.

테스트 코드 작성

프로젝트에 src / test에 테스트 코드를 생성하는데, 구조를 main과 똑같이, 아래 사진처럼 만들어 줍니다.

그럼 OrderServiceTest에 작성한 테스트 코드를 실행하면, 기존에 작성된 컨트롤러, 서비스의 로직을 테스트하게 됩니다.

OrderServiceTest를 생성하고 열어보면 아래와 같이 생긴 것을 확인할 수 있습니다.

테스트에 사용할 OrderServiceImpl과 ProductStoreImpl을 @Autowired 어노테이션으로 테스트 클래스에 추가시켜줍니다.

@Autowired 어노테이션으로 해당 빈들을 받기 위해 클래스의 최상단에 @SpringBootTest라는 어노테이션을 추가해줍니다.

이렇게 @Autowired와 @SpringBootTest어노테이션으로 빈을 주입받으면 실행시간이 오래 걸릴 수 있다는 단점이 있습니다.

이를 해결하기 위해 코드를 아래와 같이 수정해 봅시다.

OrderServiceImpl 과 ProductStoreImpl 은 추후 테스트에서 모두 공용으로 사용될 것들이니 @Shared 어노테이션으로 표시해줍니다. 이렇게 @Shared 어노테이션으로 표시해 둔 것이 @Autowired로 표시해 둔 것보다 훨씬 직관적입니다.


계산 로직 테스트 코드

그럼 방금 생성한 클래스를 바탕으로, OrderService의 로직을 테스트하는 코드를 작성해 보겠습니다.

해당 클래스가 테스트 클래스 인것을 명시하기 위해 Specification 클래스를 상속받고 테스트 코드를 아래처럼 작성했습니다.

Given 블록에서 A 상품, B 상품들을 담을 DTO를 선언하고, A 상품, B 상품의 아이디와 개수를 설정해주고 DTO에 담았습니다.

When 블록에서 A 상품, B 상품이 담긴 DTO를 calculate메서드를 통해 계산했습니다.

Then 블록에서 나온 결과값을 비교했습니다.

위 테스트 코드를 실행하면 다음과 같은 결과를 얻을 수 있습니다.

기댓값은 1800원인데, 결과로는 800원이 나온 것을 확인할 수 있습니다.

그럼 계산로직이 들어있는 OrderServiceImpl파일을 확인해보겠습니다.

다음 사진은 OrderServiceImpl 파일에 있는 calculate메서드의 일부분입니다.

계산 로직을 보면 DTO에 담긴 아이템들의 개수를 곱하는 로직이 전혀 없고, 각 가격만 더하고 있습니다.

즉, A 상품은 500원, B 상품은 300원으로 B가 3개 있음에도 불구하고 더하기만 해서 800원이 나오는 것을 확인할 수 있습니다.

그럼 위 로직을 다음과 같이 수정해줍니다.

그리고 다시 테스트 코드를 실행시켜 보면, 정상적으로 통과하는 것을 확인할 수 있습니다.

 

아까 작성한 테스트 코드의 Given 블록을 가독성을 좋게 하기 위해서 다음과 같이 수정할 수도 있습니다.

왼쪽: 수정하기 전, 오른쪽: 수정하고 난 후

A 상품이랑 B 상품의 ID와 개수를 설정하고 items에 추가하는 코드가 훨씬 간결해졌죠?


예외처리 테스트 코드

이제 계산 로직에 대한 테스트를 마치고, 테스트 결과를 기반으로 잘못된 부분의 수정까지 해봤습니다.

좋은 테스트 코드는 로직만 검사하고 마무리하는 것이 아닌, 모든 상황에서의 테스트를 할 수 있어야 합니다.

이번에는 예외처리에 대한 테스트 코드를 작성해 보겠습니다.

이 테스트 코드가 의미하는 바는, -1개의 개수를 가진 아이템이 계산 로직에 들어왔을 때, RuntimeException이 나와야 성공적으로 통과되는 테스트 코드입니다.

하지만 현재 OrderService의 계산 로직에는 음수의 개수를 가진 아이템이 들어왔을 때의 예외처리가 정의되어 있지 않습니다.

위 테스트 코드를 실행시켜 보면 테스트가 실패했음을 알 수 있습니다.

에러 메시지를 보면 "RuntimeException이 예상되지만, 예외처리가 없었다!"라고 합니다

그럼 OrderService의 계산 로직을 아래와 같이 아이템의 숫자가 음수면 RuntimeException을 던지도록 수정해봤습니다.

코드를 이렇게 수정하고 테스트를 다시 수행하면, 이번에는 통과하는 것을 확인할 수 있었습니다.

 

또 다른 예외처리를 하나 해보겠습니다.

상품의 개수가 음수가 아닌, 상점에 등록되지 않은 F 상품이 들어왔을 경우의 예외처리를 해보겠습니다.

F 상품이 들어왔을 때, RuntimeException이 발생해야 통과되는 테스트 코드지만, 현재 해당 예외처리는 구현하지 않아서 테스트가 통과되지 않는 것을 확인할 수 있습니다.

계산 로직이 담겨있는 OrderService의 calculate 메서드에서 RuntimeException을 던지지 않아서 메시지가 null이라 에러가 발생합니다.

그럼 calculate 메서드에 아래와 같은 예외처리 코드를 추가시켜주었습니다.

상점에 있는 상품인 A, B, C를 제외하고 다른 상품이 들어오면 예외를 발생시키는 코드입니다.

코드에 이처럼 예외처리 코드를 추가하니, 테스트 코드가 성공적으로 수행되는 것을 확인할 수 있었습니다.

 

현재 OrderService의 calculate 메서드 안에 모든 예외처리를 해주었지만, 실무에서는 별도의 메서드로 정의해서 예외처리를 해주는 것이 바람직하다고 합니다.


JUnit과의 비교

이번에는 아까 했던 A 상품 3개, B 상품 1개 구매 시 1800원 결과 테스트를 JUnit으로도 작성해서 코드와 결과를 모두 비교해보려 합니다.

좌측: Spock 테스트 코드                           우측: JUnit 테스트 코드

위 두개의 테스트 코드는 정확히 동일한 로직을 갖고 있습니다.

A 상품 3개, B 상품 1개를 구매했을 때 1800원인지 검사하는 테스트 코드입니다.

일부러 테스트를 실패하기 위해 마지막에 결괏값을 18,000원으로 설정해 놓았습니다.

코드만 보더라도 다음과 같은 차이점을 볼 수 있었습니다.

  1. Spock 테스트 코드가 함수의 정의, 블록의 문서화 등에서 JUnit코드보다 직관적이고 깔끔합니다.
  2. Spock 테스트 코드가 items에 상품을 추가할 때 코드가 더 깔끔합니다.
  3. 마지막에 결과를 비교할 때 spock 테스트 코드는 단순 표현식으로 비교하지만, JUnit은 assetEquals를 사용합니다.
  4. JUnit의 테스트 코드에는 @Test, @DisplayName 어노테이션이 별도로 필요합니다. 습관을 들이면 좋지만, 이는 선택사항이라 누락하는 개발자도 많습니다. 반면, Spock 테스트 코드는 테스트 코드의 이름을 명시적으로 작성하게 만들어 테스트 코드의 품질이 높아집니다.

이제 위 두 테스트 코드의 실행 결과를 비교해 보겠습니다.

좌측: Spock 테스트 결과                           우측: JUnit 테스트 결과

이 결과를 보면 Spock은 객체 자체를 다 열어서 결과를 보여주는데, JUnit은 결과 값 자체로만 가지고 비교를 하는 것을 볼 수 있습니다.


결론

이렇게 Spock Framework를 실제 코드에 적용하고, 간단한 예제였지만 TDD(Test Driven Development)를 체험할 수 있었습니다.

소프트웨어 개발을 진행할 때, 요구사항 명세서대로 테스트 코드를 작성해두고, 프로덕션 코드를 작성하여 테스트를 동반하면서 개발하면, 예외처리, 계산 로직의 실수 같은 사소한 에러들을 사전에 방지할 수 있고, 더욱 꼼꼼하게 개발을 진행할 수 있을 것 같습니다.

JUnit과의 차이점을 비교해보면서 Spock Framework의 장점 및 강점을 알 수 있게 되었습니다.

비록 꼭 Spock Framework가 아닌, JUnit으로도 좋은 테스트 코드를 작성하는 습관을 들이게 되면 좋은 개발자가 될 수 있을것 같습니다.

반응형

댓글