-
Groovy Spock 을 쓰는 이유, 코드 작성 예시 소개[IT] 공부하는 개발자/Open Source 2021. 2. 15. 08:45
목차
1. Spock의 소개
2. Spock 시작하기
3. Spock 으로 테스트 코드 작성 예시
1. Spock Introduction
Spock의 특징
개발에서 테스트 코드의 중요성은 아무리 강조해도 지나치지 않습니다. 훌륭한 테스트 코드 프레임워크라면, 사용자가 다양한 케이스의 검증을 구현할 수 있게 도와줌으로써, 보다 적은 힘을 들여 Code Coverage를 높일 수 있게 기여해야 합니다.
Java 진영의 대표적인 테스트 코드 프레임워크인 JUnit 은 TDD 기반인 것에 비해, Spock 은 BDD 기반입니다.
TDD 는 테스트 자체에 집중하여 개발하는 방식인 반면, BDD는 비즈니스 요구사항에 집중하여 테스트 케이스를 개발하게 됩니다.
BDD는 전개도 사용자가 직관적으로 알 수 있도록 자연어 형식을 따르고 있습니다. 이런 식입니다.
Spock 문법
given:
이런 테스트 세트를 주고,
when:
테스트를 실행하면,
expect;
이런 결과가 나오기를 원해.
Spock의 장점
- 같은 테스트도 더 짧은 코드로 작성할 수 있습니다.
- jUnit 은 퓨어 자바 코드로 작성해야 하지만 Spock 은 Groovy로 작성하기 때문입니다.
- jUnit 은 유닛테스트만을 위한 프레임워크인 반면 Spock 은 유닛 테스트, 통합 테스트, Mocking, Stubbing을 함께 지원합니다.
- 더 가독성이 좋습니다.
- given, when, then 등의 블록으로 테스트 파트를 명시적으로 분리할 수 있기 때문입니다. jUnit으로는 주석으로 따로 표기하는 방법밖에 없습니다.
2. Spock 시작하기
임포트 버전은 이 포스팅을 작성하는 2021년 2월 현재까지 가장 많이 사용되는 1.1 의 2.4 버전으로 합니다.
mvn-repository에서 Usages 탭을 참고하여 정하실 수 있습니다.
Gradle Import
testCompile "org.spockframework:spock-core:1.1-groovy-2.4" testCompile "org.spockframework:spock-spring:1.1-groovy-2.4"
스프링으로 개발하지 않는다면 core 디펜던시만 추가해도 무방합니다.
Maven Import
<dependency> <groupId>org.spockframework</groupId> <artifactId>spock-core</artifactId> <version>1.1-groovy-2.4</version> <scope>test</scope> </dependency> <dependency> <groupId>org.spockframework</groupId> <artifactId>spock-spring</artifactId> <version>1.1-groovy-2.4</version> <scope>test</scope> </dependency>
역시, 스프링으로 개발하지 않는다면 core 디펜던시만 추가해도 무방합니다.
3. Spock으로 테스트 코드 작성 예시
저의 개인 토이 프로젝트인 Stock Management를 불러와서 테스트 코드를 작성해 보겠습니다.
3.1 Groovy Class 생성
main 디렉토리와 같은 레벨에 존재하는 test 디렉토리에, 테스트 코드를 작성할 Groovy Class를 하나 만듭니다.
3.2 Specification
클래스는 spock.lang.Specification 을 상속하여 테스트를 구현해야 합니다.
package com.stock.management.service import spock.lang.Specification class PortfolioItemPutLogService extends Specification{ }
3.3 def
def 는 동적 타입을 선언하는 예약어입니다. 선언 후에는 어떤 타입의 객체든 주입할 수 있습니다.
class PortfolioItemPutLogService extends Specification{ def "테스트 1번"(){ def randomNumber = 10 } }
def로 생성자를 구현하여 선언하면 하나의 유닛 테스트가 됩니다. "테스트 1번" 이라는 테스트를 생성하였습니다.
혹은 아무 타입의 객체를 주입할 수도 있습니다. 테스트 안에서 randomNumber라는 랜덤한 타입을 선언하고 int 10을 주입했습니다.
3.4 setup()
테스트 클래스가 생성될 때에 setup() 메소드 안의 코드가 가장 먼저 실행됩니다.
유닛 테스트가 실행되기 전에 선행되어야 할 코드를 작성해줍니다.
class PortfolioItemPutLogServiceTest extends Specification{ PortfolioItemPutLogService portfolioItemPutLogService def portfolioItemPutLogRepository void setup() { portfolioItemPutLogRepository = Mock(PortfolioItemPutLogRepository) portfolioItemPutLogService = new PortfolioItemPutLogService( portfolioItemPutLogRepository ) } def "테스트 1번"(){ def randomNumber = 10 } }
portfolioItemPutLogService에서 호출되는 repository에 Mock 객체를 주입했습니다.
3.5 given, when, then, expect, where
given, when, then, expect, where 예약어를 사용하여 테스트를 작성합니다.
각 예약어의 역할
Keyword Role given 테스트가 실행되기 전 수행되어야 할 코드를 작성합니다. setup() 과 동일하지만, setup 은 클래스 전역에 적용되는 반면 given 은 작성된 테스트 내에만 적용됩니다. when 검증하고 싶은 작업 작성 then 검증 값 입력 expect 기대하는 수행 동작 where 검증하고 싶은 파라미터 값을 추가하여, 조건과 결과만 바꿔가며 테스트 할 수 있다. 쉬운 예시를 작성해봅니다.
def "테스트 1번"() { given: def testValue = 1 when: def returnValue = testValue + param then: returnValue == result where: param | result 0 | 1 10 | 11 100 | 101 }
where 예약어로 테스트 케이스를 3개 작성했기 때문에, 테스트는 3번 수행됩니다.
(선택) Gradle 사용자는 Spock 테스트에 사용할 빌드 수단을 선택할 수 있습니다. 저는 IntelliJ 내장 그래들을 사용합니다.
3.6 메소드 대치 표현식
예시로, 위의 saveLog 메소드에 대한 테스트 코드를 작성해 보겠습니다.
def "테스트 2번"() { when: def portfolioItem = new PortfolioItem(id: 1) def quality = 100 def priceSum = 200000 portfolioItemPutLogRepository.save(_) >> null // check! then: portfolioItemPutLogService.saveLog(portfolioItem, quality, priceSum) where: param | result 0 | 1 10 | 11 100 | 101 }
saveLog 메소드 안에서 호출되는 portfolioItemPutLogRepository의 save의 리턴 값을 null로 대치하도록, '>>' 표현식을 대입하여 작성합니다. 이를 통해 코드에서 save 메소드가 호출되면 >> 우측의 값이 리턴됩니다.
지금은 리턴 값으로 null이라는 정적 값을 지정해주었는데요, 만약 동적 값을 받아서 리턴시키고 싶다면 람다식으로 정의해서 쓰면 됩니다.
위의 코드에서는 save의 인수로 어떤 입력값이 들어오던 무조건 null을 리턴시키지만, (Groovy에서 _ 표현식은 wildcard라서 그렇습니다.)
portfolioItemPutLogRepository.save(_) >> {args -> args[0]}
으로 작성한다면, save() 안에 0번째 인수로 들어온 PortfolioItemPutLog를 리턴시켜 줍니다.
3.8 실행 횟수 검증
실행 횟수도 검증이 가능합니다.
portfolioItemPutLogRepository.save 가 실행되는 횟수를 검증할 수 있습니다.
def "테스트 3번"() { when: def portfolioItem = new PortfolioItem(id: 1) def quality = 100 def priceSum = 200000 1 * portfolioItemPutLogRepository.save(_) >> null then: portfolioItemPutLogService.saveLog(portfolioItem, quality, priceSum) }
saveLog 가 1번 실행되어야 테스트가 통과합니다.
시험 삼아 실행 횟수를 0으로 바꿔보면,
0 * portfolioItemPutLogRepository.save(_) >> null
이렇게 테스트를 통과하지 못하고 실패합니다.
혹은 최소 실행 횟수와 최대 실행 횟수를 지정하여 검증할 수도 있습니다.
// 최소 1번 이상 실행 (0.._) * portfolioItemPutLogRepository.save(_) >> null // 최대 2번까지 실행 (_..2) * portfolioItemPutLogRepository.save(_) >> null // 최소 1번에서 최대 2번까지 실행 (1..2) * portfolioItemPutLogRepository.save(_) >> null
이 포스팅이 도움이 되셨다면 포스팅 하단의 하트를 눌러주세요.
다른 포스팅 둘러보기
2019/09/10 - [[IT] 공부하는 개발자/JAVA] - [JAVA 메모리 트러블 슈팅] 콘솔에서 JVM Heap 메모리 추적하기 : jstat, jmap
2019/10/28 - [[IT] 공부하는 개발자/JAVA] - [JAVA Collections API] 자료구조 요약: 구조/성능/용도
'[IT] 공부하는 개발자 > Open Source' 카테고리의 다른 글
[ELK Logstash] config 파일 작성 예시 - input, filter, output (0) 2022.02.20 맥 Numbers 앱에서 환율 / 주식 함수 사용하기 (STOCK, CURRENCY) (2) 2021.05.08 logstash 'unknown setting *** for jdbc' 에러 메시지 (0) 2020.12.07 [Kafka] 터미널에서 카프카 토픽 생성, 발행, 컨슘하기 (0) 2020.05.07 [엘라스틱서치] ElasticSearch & 키바나 시큐리티 기능 구현 (0) 2019.09.08 - 같은 테스트도 더 짧은 코드로 작성할 수 있습니다.