ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spock로 테스트 코드 작성 시작하기
    Language/Java 2026. 1. 23. 21:00

    spring으로 프로젝트를 진행하면서 테스트 코드를 작성해보기로 했다.

    groovy 기반 테스트 프레임워크 Spock를 설치하는 방법에 대해 적어보려고 한다.

    검색해도 생각보다 잘 안나오고, 시키는대로 했는데도 잘 동작하지 않아서 꽤 헤맸기 때문에 나와 같은 사람이 하나라도 있다면 도움받기를 바라며 적어본다.

    배경

    프로젝트를 진행하면서 TDD에 관심을 가지게 되었다.

    js로 개발할 땐 jest, java일 땐 junit을 테스트 코드를 위해 사용해봤는데 사실 잘 쓸 줄 모른다.
    서비스가 커질수록 테스트 코드의 복잡도도 높아져서 늘 중간부터는 테스트 코드 작성을 제대로 이어가지 못했다.

    이번에는 다르다..!반드시 테스트 코드를 쓰는 연습을 야무지게 이어가보리라 다짐하면서 테스트 프레임워크에 대해 찾아보았다.

    junit의 반복적이고 직관적이지 못한 코드 때문에 유독 더 쓰기 힘들었던 기억이 있어서 이번에는 Spock를 이용해 테스트 코드를 작성해기로 했다.

    Spock

    출처: 공식문서

    Spock는 JVM을 사용하는 개발자들을 위한 테스트, 명세, 모킹 프레임워크이다.

    • Spock 공식문서
    • Spock는 groovy DSL을 기반으로 작성한다.
    • Mocking과 Stubbing을 내장 기능으로 지원한다.
      • junit은 mockito같은 외부 라이브러리를 사용한다.
    • 가독성이 아주 좋다.
      • BDD(Behavior Driven Development) 지향
        • TDD에서 파생되어, 사용자-개발자가 비즈니스 요구사항에 대한 개발과 테스트를 자연어로 표현하며 소통하는 개발 철학
    • Spock2.0부터 Junit Platform위에서 동작하기 때문에 Intellij, Eclipse, Gradle 등과 완벽하게 호환된다.
    • given:, when:, then: 와 같은 편리한 블록을 문법적으로 제공하여 테스트 의도를 명확하게 분리할 수 있다.
    • Data Table이라는 기능으로 쉽게 여러 테스트 케이스를 정의할 수 있다.

    Junit 5 vs Spock - 문법 비교

    junit과 코드로서 비교해보면, 특징을 명확하게 알 수 있다.

    주문 서비스에서

    1. 상품 가격과 수량으로 총액을 계산하고,
    2. 할인 정책에 따라 할인하여

    최종 금액을 산출하는 로직

    을 테스트하는 코드를 작성해보자.

    Junit 5

    @ExtendWith(MockitoExtension.class)
    class OrderServiceTest {
    
        @Mock
        private DiscountPolicy discountPolicy;
    
        @InjectMocks
        private OrderService orderService;
    
        @Test
        @DisplayName("상품 주문 시 할인된 최종 금액을 계산한다")
        void calculateTotalAmountTest() {
            // given
            int price = 10000;
            int quantity = 2;
            when(discountPolicy.applyDiscount(20000)).thenReturn(18000);
    
            // when
            int result = orderService.order(price, quantity);
    
            // then
            assertEquals(18000, result);
            verify(discountPolicy, times(1)).applyDiscount(20000);
        }
    }
    • mockito 라이브러리 사용
    • 어노테이션, 메서드 호출로 동작
    • java로 작성

    Spock

    class OrderServiceSpec extends Specification {
    
        def discountPolicy = Mock(DiscountPolicy)
        def orderService = new OrderService(discountPolicy)
    
        def "상품 주문 시 할인된 최종 금액을 계산한다"() {
            given:
            def price = 10000
            def quantity = 2
    
            when:
            def result = orderService.order(price, quantity)
    
            then:
            1 * discountPolicy.applyDiscount(20000) >> 18000
            result == 18000
        }
    }
    • 별도 라이브러리 없이 Mock()으로 모킹(Mocking)
    • given-when-then구조를 명확하게 사용
    • 단순하고 간결하지만, 1 *, >> 등의 익숙지 않은 문법
    • groovy truth ==로 바로 비교하여 assertion
    • 메서드 이름에 바로 문자열 사용 가능

    문법에서부터 가독성 차이가 많이 난다고 느꼈다.

    java에서는 template처럼 given-when-then 주석을 사용해 테스트코드를 작성하는데,
    spock는 아예 이런 블록자체를 제공한다는 것도 호감이었다.

    내가 느낀 가장 큰 차이는 테스트 케이스를 명시하는 방식이었다.

    Junit5 vs Spock - 테스트 케이스 명세 비교

    Junit 5

    @ParameterizedTest 
    @CsvSource({ 
        "10000, 2, 18000", 
        "5000, 1, 4500", 
        "20000, 3, 54000" })

    junit 5에서는 테스트 케이스를 위와 같이 어노테이션으로 적는다.
    이것 또한 익숙해지면 편해보일 것이다.

    하지만 spock는 거의 사기 수준의 기능을 제공한다.

    Spock

    where: 
    price | quantity || expected
    10000 | 2        || 18000
    5000  | 1        || 4500 
    20000 | 3        || 54000

    놀랍게도 위와 같이 명시하면 된다..

    테스트 케이스 명시 방식을 보고 spock에게 완전히 반해버렸다.
    바로 사용하기로 결정.

    설치를 시작했다.

    Spock 테스트 환경 구성하기

    spock 공식 문서에서 spock-example이라는 github repo를 제공해준다.

    나는 이곳에 있는 build.gradle을 보며 환경을 구성했다.

    Prerequisites (준비할 것)

    • JDK 17 이상
    • Maven 또는 Gradle
    • h2 같은 가벼운 인메모리 DB(테스트에서 사용)

    더 옛 버전의 java나 junit 4환경 위에서 spock를 사용하고 싶다면, README.md 를 참고하자.

    1. build.gradle 편집

    1. build.gradle에 groovy 추가

    plugins {  
        id 'java'  
        id 'groovy'  // 추가
        id 'org.springframework.boot' version '4.0.1'  
        id 'io.spring.dependency-management' version '1.1.7'  
    }

    2. h2 추가

    dependencies {
        ...
        runtimeOnly 'com.h2database:h2'  
        ...
    • DB 연동 테스트를 위해 사용한다.3. apache.groovy-bom 추가
    • dependencies { ... implementation platform('org.apache.groovy:groovy-bom:5.0.3') implementation 'org.apache.groovy:groovy' ...

    4. spockframework 추가

    dependencies {
        ...
        testImplementation platform("org.spockframework:spock-bom:2.4-groovy-5.0")  
        testImplementation "org.spockframework:spock-core"  
        testImplementation "org.spockframework:spock-spring"  
        ...

    5. junit 추가

    dependencies {
        ...    
        testImplementation(platform('org.junit:junit-bom:6.0.1'))
        ...
    • spock2.0 이상부터는 junit 위에서 구동하므로 잊지말고 넣어주자.

    참고로 bom(BOMs)은 import하려는 프로젝트들의 버전을 관리하는 섹션을 가지는 POM파일들로 만든 의존성 프로젝트를 의미한다.

    2. 테스트 application.properties 만들기

    1. 프로젝트 디렉토리 안에 test 디렉토리 내부에 resources디렉토리 생성
    2. resources > application.properties 생성
    3. 나의 경우, 테스트용 DB로 h2를 사용하기로 했기 때문에 아래와 같이 내용을 넣었다.
    spring.datasource.url=jdbc:h2:mem:test 
    spring.datasource.driver-class-name=org.h2.Driver 
    spring.datasource.username=sa 
    spring.datasource.password= 
    spring.jpa.database-platform=org.hibernate.dialect.H2Dialect 
    spring.jpa.hibernate.ddl-auto=create-drop //시작하면 테이블 생성, 끝나면 삭제 
    spring.jpa.show-sql=true

    3. test > groovy 디렉토리 만들기

    테스트 코드를 담을 groovy 디렉토리도 추가하고, 여기 안에 테스트 코드를 작성해야한다.

    테스트 코드까지 작성해 추가하면, 아래와 같은 tree가 된다.
    (찾기 쉽도록 junit에서처럼 main 코드와 test 코드의 계층을 동일하게 구성해보았다.)

    프로젝트 디렉토리 tree

    │   └── src
    │       ├── main
    │       │   ├── java
    │       │   │   └── kr
    │       │   │       └── co
    │       │   │           └── bookvault
    │       │   │               ├── BookvaultApplication.java
    │       │   │               ...
    │       │   └── resources
    │       │       └── application.properties
    │       └── test
    │           ├── groovy
    │           │   └── kr
    │           │       └── co
    │           │           └── bookvault
    │           │               └── domains
    │           │                   └── member
    │           │                       ├── repository
    │           │                       │   └── MemberRepositorySpec.groovy
    │           │                       └── service
    │           │                           └── MemberServiceSpec.groovy
    │           ├── java //사용 x
    │           │   └── kr
    │           │       └── co
    │           │           └── bookvault
    │           │               └── BookvaultApplicationTests.java
    │           └── resources
    │               └── application.properties

    마무리

    Spock로 테스트할 수 있는 환경을 구성해보았다.

    아직 문법이 익숙하지 않아 쉽지 않지만, 테스트 코드는 ai를 활용해서 꾸준히 작성해보려고 한다.

    테스트 코드를 작성하는 생각의 흐름이 결국 메인 코드들에게 영향을 주어

    더 명확하고 응집도 높은 코드를 만드는 날이 오기를 기대해본다.

    'Language > Java' 카테고리의 다른 글

    MVC Pattern  (1) 2024.10.29
    [Java] 내가 잊어버릴까봐 쓰는 자바 컨벤션  (0) 2024.10.22
    [IDEA] 디버깅 모드  (0) 2024.10.22
    [Java의 정석] 2-2. 변수의 타입  (1) 2024.10.21
    [Java] 다형성과 바인딩  (0) 2024.10.21
Designed by Tistory.