https://docs.gradle.org/5.0/release-notes.html

Gradle 5.0 이 나왔습니다.

Java 11 및 운영가능한 수준으로 Kotlin DSL 1.0 를 지원합니다.

이미 나온지 꽤 시간이 흐른 Junit 5를 살펴보기 시작한다.


[spring-boot] Junit5 적용기

JUnit5 의존성 추가

JUnit5가 세상에 모습을 드러내놓은지는 제법 됐다. 새로운 프로젝트를 시작하면서 JUnit5 와 Spock 을 기반으로 한 테스트를 작성하고자 한다.

스프링 부트 2에서 JUnit5 에 대한 의존성을 추가하고 테스트를 작성하는 방법을 설명한다.

buildscript {
    ext {
        springBootVersion = '2.0.6.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'io.honeymon.study'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 10

repositories {
    mavenCentral()
}

dependencies {
    implementation('org.springframework.boot:spring-boot-starter-data-jpa')
    implementation('org.springframework.boot:spring-boot-starter-web')

    runtimeOnly('com.h2database:h2')
    compileOnly('org.projectlombok:lombok')

    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude module: 'junit'
    }
    testImplementation('org.junit.jupiter:junit-jupiter-api:5.2.0')
    testCompile('org.junit.jupiter:junit-jupiter-params:5.2.0')
    testRuntime('org.junit.jupiter:junit-jupiter-engine:5.2.0')
}

test {
    useJUnitPlatform()
}

junit4 제외

스프링 부트 스타터 org.springframework.boot:spring-boot-starter-test는 junit4에 대한 의존성을 가지고 있다. 그래서 junit5를 사용하기 위해서는 spring-boot-starter-test에 추가되어 있는 junit4를 제외해야 한다.

testImplementation('org.springframework.boot:spring-boot-starter-test') {
    exclude module: 'junit'
}
Note

그레이들에서 모듈에 대한 의존성 정의는 3개 부분으로 나뉜다.

  • 예: org.springframework.boot:spring-boot-starter-test

    • group: org.springframework.boot

    • module: spring-boot-starter-test

    • version: 생략

이런 구분을 이해하고 나면 위에서 설명한 제외(exclude)방법을 활용할 수 있을 것이다.

  • 그룹과 모듈을 정의하는 경우: exclude group: 'junit', module: 'junit' 으로 정의할 수 있다.

이어서 junit5에 대한 의존성을 추가한다.

dependencies {
    // 생략
    testImplementation('org.junit.jupiter:junit-jupiter-api:5.2.0')
    testCompile('org.junit.jupiter:junit-jupiter-params:5.2.0')
    testRuntime('org.junit.jupiter:junit-jupiter-engine:5.2.0')
    // 생략
}

이어서 test 태스크에서 useJUnitPlatform()를 선언한다. useJUnitPlatform는 테스트 실행시 JUnit 플랫폼(JUnit 5)이라는 것을 정의한다.

/**
 * Specifies that JUnit Platform (a.k.a. JUnit 5) should be used to execute the tests. <p> To configure JUnit platform specific options, see {@link #useJUnitPlatform(Action)}.
 *
 * @since 4.6
 */
@Incubating
public void useJUnitPlatform() {
    useJUnitPlatform(Actions.<JUnitPlatformOptions>doNothing());
}

이렇게 해서 build.gradle에서 JUnit5를 사용하기 위한 위존성 정리를 마쳤다.

테스트 작성

package io.honeymon.study.junit5;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)  // (1)
@SpringBootTest
public class SpringBoot2Junit5ApplicationTests {

    @Test // (2)
    public void contextLoads() {
    }

}
  1. ExtendWith는 JUnit5 에서 반복적으로 실행되는 클래스나 메서드에 선언한다. SpringExtension는 스프링 5에 추가된 JUnit 5의 주피터(Jupiter) 모델에서 스프링 테스트컨텍스트(TestContext)를 사용할 수 있도록 해준다.

  2. `@Test의 경로도 변경(org.junit.Testorg.junit.jupiter.api.Test)되었다.

이제 JUnit5를 기반으로 통합테스트를 위한 준비를 마쳤다.

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

Note

junit5는 람다를 기반으로 한 선언(assertion)을 지원한다. junit4에서 지원했던 기능이 부족하여 assertJ 의존성을 추가해야 했던 불편함을 해소할 수 있다.

[spring-boot] gradle multi module 사용하면서 gradle plugin bootJar.enabled=false 선언했을 때 jar 파일 생성안된다면

멀티 모듈을 가지는 스프링 부트 기반의 멀티프로젝트를 구성하는 과정에서 조금 당황스런 상황을 겪었다.

bootJar.enabled=false

빌드 했을 때 위처럼 선언된 공통 모듈이 빌드된 배포본에 포함되지 않는 상황이 발생했다.

Note

인텔리제이에서 테스트 러너를 그레이들로 설정하지 않으면 당황스런 순간을 맞이하게 된다.

인텔리제이 기본 테스트 러너에서는 인텔리제이에서 컴파일한 build 디렉터리를 기반으로 테스트를 진행해서 별다른 문제가 없지만 그레이들 빌드 테스트의 경우에는 컴파일 및 빌드를 하면서 테스트가 진행되는데 위에서 언급한 bootJar.enabled=false만 선언된 모듈은 jar 파일을 생성하지 않기 때문에 이를 참조하는 하위 모듈에서 관련된 파일을 읽어오지 못하게 된다.

이와 관련된 문제를 찾아보다가 발견한 한줄기 빛!

마지막 댓글을 보면

bootJar.enabled=false
jar.enabled=true

jar.enabled=true 옵션을 추가하면 Jar 파일 생성이 진행된다. 우후!

스프링 부트에서 bootRepackage 에서 bootJar 로 변경되면서 뭔가 이상한 짓을 한 듯 하다.

Note

BootJar 문서를 살펴보면 확장하면서 재정의한 영향으로 보인다. jar.enabled 옵션을 활성화한다.

그레이들 래퍼를 이용하면 프로젝트 구성원들이 일일이 그레이들을 설치는 번거로움을 피할 수 있다(한명만 고생하면 된다). 다음과 같이 실행하면 로컬에 설치되어 있는 그레이들 버전을 기준으로 그레이들 래퍼가 설치된다.

$ gradle wrapper
$ cat gradle/wrapper/gradle-wrapper.properties
#Fri Mar 24 21:30:00 KST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-bin.zip

하지만 그레이들 래퍼의 버전은 간단한 인자변경을 통해 손쉽게 할 수가 있다. 다음 명령어를 이용한다.

$ gradle wrapper --gradle-version={version}

3.4.1 버전을 설정한다고 치면!

$ gradle wrapper --gradle-version=3.4.1
:wrapper

BUILD SUCCESSFUL

Total time: 0.724 secs
$ cat gradle/wrapper/gradle-wrapper.properties
#Fri Mar 24 21:28:35 KST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-bin.zip

다음과 같은 형태로 설치되고, 이후에 그레이들 빌드를 진행하면 자연스럽게 래퍼를 변경한다.


IDE 에서 생성한 rebel.xml 이 프로젝트와 맞지 않아서 제대로 되지 않았는데, 이를 해결할 수 있는 방법을 찾았다.

build.gradle 에 아래 스크립트를 추가하면 war 태스크가 실행될 때 war 의존성을 걸어둔 generateRebel 가 호출되면서 build/resources/main 아래에reble.xml 이 생성된다. 프로젝트의 클래스패스와 웹경로의 항목들을 출력하는 특징을 가진다.

이 태스크가 실행되기 위해서는 프로젝트에 war 플러그인이 설치되어 있어야 한다. 

apply plugin: 'war'
// 생략

task generateRebel << {
    def rebelFile = sourceSets.main.output.classesDir.absolutePath + '/rebel.xml'
 
    def srcWebApp = project.webAppDir.absolutePath
    def writer = new FileWriter(rebelFile)
    new groovy.xml.MarkupBuilder(writer).application() {
        classpath{
            dir( name:sourceSets.main.output.classesDir.absolutePath )
        }
        web{
            link(target:'/'){
                dir(name:srcWebApp)
            }
        }
    }
}
war.dependsOn generateRebel
$ ./gradlew build   // or war 만 실행해도 됨
:processResources
:classes
:generateRebel
:war
생성된 rebel.xml
<?xml version="1.0" encoding="UTF-8"?>
<application generated-by="intellij" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.zeroturnaround.com" xsi:schemaLocation="http://www.zeroturnaround.com http://update.zeroturnaround.com/jrebel/rebel-2_1.xsd">
 
<classpath>
<dir name="/Users/honeymon/workspace/prototype-boot/build/classes/main"></dir>  (1)
<dir name="/Users/honeymon/workspace/prototype-boot/src/main/resources"></dir>  (2)
</classpath>
 
</application>

컴파일된 클래스 핫스와핑

변경된 설정파일 모니터링


+ Recent posts