[Gradle] Gradle 기초

2021. 10. 12. 12:56Build Tools/Gradle

1. Gradle, slf4j, logback, junit, assertj

1. Gradle

1. task

하나의 작업 단위를 묶어놓은 것이다. 터미널에서 gradle run 을 입력하면 아래 task를 실행할 수 있다.

apply plugin: 'java'

task run(type: JavaExec) {
    mainClass: 'exercise.HelloWorld'
    classpath: sourceSets.main.runtimeClasspath
}

task runAfterCompile(dependsOn: classes, type: JavaExec) {
    mainClass: 'exercise.HelloWorld'
    classpath: sourceSets.main.runtimeClasspath
}
  • apply plugin
    • 프로젝트에서 사용할 플러그인을 명시한다.
  • dependsOn
    • task가 의존할 gradle 명령어를 지정한다.
    • classes라고 작성하면 이 task 실행 전에 gradle classes, 즉 자바 파일에 대해 컴파일 작업을 수행한다.
  • type: JavaExec
    • runJavaExec을 상속받는 task임을 나타낸다.
  • mainClass
    • main이 정의되어 있는 클래스의 패키지 경로를 지정한다.
  • classpath
    • 프로그램 실행에 있어서 필요한 라이브러리 경로를 지정한다.
    • sourceSets
      • gradle 프로젝트 경로 중 src 까지의 패키지 경로를 의미한다.
    • runtimeClasspath
      • 런타임에 필요한 라이브러리 경로들을 의미한다.

plugin: application

위의 run과 같은 task는 자주 사용되므로 gradle에서는 application이라는 플러그인으로 지원한다. 이때 mainClassName으로 main이 정의된 클래스를 지정해주면 된다. 이 또한 gradle run 커맨드로 실행할 수 있다.

apply plugin: 'application'

mainClassName = 'exercise.HelloWorld'

2. dependencies

  • implementation
    • sourceSets의 자바 소스를 컴파일 할 때와 런타임 시에 모두 필요한 라이브러리를 지정한다.
  • runtimeOnly
    • sourceSets의 자바 소스를 런타임 시에만 필요한 라이브러리를 지정한다.
  • compileClasspath
    • sourceSets의 자바 소스를 컴파일 할 때 필요한 라이브러리를 지정한다.
  • gradle dependencies(dep)
    • 터미널에서 gradle 프로젝트의 의존성을 확인하는 명령어이다.
    • 옵션으로 런타임, 컴파일 등 의존성을 분리하여 확인할 수도 있다.
      • gradle dependencies —configuration runtimeOnly
        • 런타임 시 사용되는 의존성만 출력한다.
      • gradle dependencies —cofiguration runtimeClasspath
        • 런타임 시 사용되는 의존성과 연관된 모든 의존성을 함께 표시한다.
      • gradle dependencies —configuration compileOnly
        • 컴파일 시 사용되는 의존성만 출력한다.
      • gradle dependencies —cofiguration compileClasspath
        • 컴파일 시 사용되는 의존성과 연관된 모든 의존성을 함께 표시한다.
      • gradle dependencies —configuration implementation
        • 런타임 및 컴파일 시 사용되는 의존성만 출력한다.

3. Logging options

출력되는 로그 레벨도 지정할 수 있다.

  • -q, —quiet
    • 에러 로그만 출력한다.
    • gradle -q dep —configuration runtimeOnly
  • -w, —warn
    • 경고, 에러 로그만 출력한다.
    • gradle -w dep —configuration runtimeOnly
  • -i, —info
    • 정보, 경고, 에러 로그만 출력한다.
    • gradle -i dep —configuration runtimeOnly
  • -d, —debug
    • 디버그, 정보, 경고, 에러 로그만 출력한다.
    • gradle -d dep —configuration runtimeOnly

2. slf4j, logback

자바 생태계에서 현재 가장 널리 사용되는 로깅 라이브러리이다. 로그 기능을 사용하려면 사용하려는 클래스에서 loggerfactory를 통해 logger를 생성해서 사용한다. 인수로는 사용하는 클래스의 class 객체를 넘겨준다.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Slf4jDemo {

    private static final Logger log = LoggerFactory.getLogger(Slf4jDemo.class);

    public static void main(String[] args) {

        String name = "Bill";
        Integer age = 37;

        log.error("Error Message : ({} {})", name, age);
        log.warn ("Warn  Message : ({} {})", name, age);
        log.info ("Info  Message : ({} {})", name, age);
        log.debug("Debug Message : ({} {})", name, age);
        log.trace("Trace Message : ({} {})", name, age);

        System.out.println("End");
    }
}

로그 레벨

로그는 5개의 레벨로 구분되어 출력할 수 있다. 우선순위가 높은대로 error, warn, info, debug, trace이다. 각 레벨은 각 단어의 뜻과 같은 의미를 가진다. error는 에러 상황에, warn은 경고, info는 변수 정보 같은 개발자에게 유용한 정보, debug는 디버깅을 할 때, trace는 모든 메시지에 대해 출력한다.

로그의 기본 레벨은 debug이며 이때 trace는 출력되지 않는다. 즉 설정한 레벨에 따라 출력되는 로그가 달라진다.

로깅 라이브러리를 사용하면 로그가 찍힌 시간, 실행 중이던 쓰레드, 로그 레벨과 패키지, 클래스명까지 전부 출력된다. 그리고 로그를 콘솔에 출력할 것인지, 파일에 저장할 것인지, 외부 PC에 저장할 것인지 등까지 선택할 수 있다. System.out.println()과 같은 메서드는 그저 콘솔에 출력만 할 뿐이다. 그러므로 로깅 라이브러리를 알게 된 시점 이후부터는 로깅 라이브러리를 사용하는 편이 바람직하다.

로그 설정

<?xml version="1.0" encoding="utf-8"?>

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%highlight(%5level) %cyan(%24logger{24})| %msg%n</pattern>
        </encoder>
    </appender>

    <logger name="demo" level="INFO" />

    <root level="WARN">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>
  • appender
    • 로그를 출력할 위치를 지정한다. 콘솔, 파일, 외부 PC 전송 등 여러 옵션을 결정한다. 위와 같이 class를 지정하면 콘솔에 출력된다. name은 아래 appender-ref에서 참조할 이름이다.
  • encoder
    • 로그 이벤트를 바이트 배열로 변환하고, 그 배열을 OutputStream에 쓰는 작업을 수행한다. 이때 내부에 패턴이 있으면 패턴의 형식대로 출력한다.
  • pattern
    • 로그를 출력할 형식을 지정한다.
  • logger
    • name으로 지정한 패키지 하위의 로그 레벨을 설정한다. 예제에서는 demo 하위 패키지들은 INFO 레벨로 설정됐다.
  • root
    • 프로젝트에서 사용되는 모든 로그에 대해 레벨을 지정해준다. 이때 의존 라이브러리들의 레벨도 함께 지정된다.
  • appender-ref
    • 로그를 출력할 appender를 지정해준다. 위에서 작성한 STDOUTref로 작성하면 해당 appender가 반영된다.

3. JUnit and assertj

테스트는 복잡한 애플리케이션을 개발할 때 오류를 최소화하기 위해 현대 개발에서 필수 요소가 되었다. JUnit은 자바 생태계에서 가장 많이 사용되는 테스트 프레임워크이다. 그리고 이를 보조해주는 라이브러리로 hamcrestassertj가 대표적이고 현재 assertj가 많이 사용된다.

설정

// build.gradle
apply plugin: 'java'
apply plugin: 'application'

application {
    mainClass = "demo.HelloWorld"
}

repositories {
    mavenCentral()
}

dependencies {
    implementation     'org.slf4j:slf4j-api:1.7.+'
    runtimeOnly        'ch.qos.logback:logback-classic:1.2.+'

    testImplementation 'org.junit.jupiter:junit-jupiter:5.6.+'
}

test {
    useJUnitPlatform()

    outputs.upToDateWhen { false }
    testLogging {
        events "passed", "skipped", "faile"
        showStandardStreams = true
        exceptionFormat = "full"
    }

    afterSuite { desc, result ->
        if (!desc.parent) {
            println "\nResults: ${result.resultType} " +
                "(${result.testCount} tests -> " +
                "${result.successfulTestCount} successes, " +
                "${result.failedTestCount} failures, " +
                "$result.skippedTestCount} skipped)"
        }
    }
}

dependencies를 확인하면 testImplementation'org.junit.jupiter:junit-jupiter:5.6.+'로 적힌 것을 확인할 수 있다. 이는 테스트 시 컴파일, 런타임 시 모두 사용되는 라이브러리로 지정한 것이다. 그리고 JUnit4에서 JUnit5로 버전이 업그레이드되면서 패키지명이 변경되었다. 따라서 테스트 코드 작성 시 import에 주의해야 한다. 또 한 가지 주의해야 할 점은 gradle 프로젝트는 기본적으로 JUnit4를 기본으로 적용하도록 되어 있다는 것이다. JUnit5로 설정하려면 test 블럭에 useJUnitPlatform() 을 꼭 작성해주어야 한다.

  • outputs.upToDateWhen
    • 테스트 코드 실행 시 이전에 테스트했던 것에 변화가 없어도 다시 테스트를 수행할 것인지에 대한 여부를 설정한다.
      • true - 다시 수행하지 않는다.
      • false - 항상 테스트를 수행한다.
  • testLogging
    • 테스트 로그에 대한 옵션을 지정한다.
    • events
      • 테스트에서 지정한 이벤트에 대해서 출력하도록 하는 옵션이다.
    • showStandardStreams
      • JVM 콘솔 표준 출력을 출력할 것인지 여부를 설정한다.
    • exceptionFormat
      • 콘솔에 기록되는 테스트 이벤트 및 자세한 레벨과 관련된 옵션을 설정한다.
  • afterSuite
    • 테스트가 모두 종료되면 테스트에 대한 종합적인 정보를 출력할 때 사용한다. 첫 번째 인자로 TestDescriptor, 두 번째 인자로 TestResult 인스턴스를 받는다.
    • 이와 비슷한 afterTest가 있는데 이는 하나의 테스트가 수행된 후마다 실행된다.

테스트 코드 작성

import java.util.*;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*;

public class TestCalculator {

    @Test
    public void testAdd() {
        Calculator cal = new Calculator();
        assertThat(cal.add(1, 2)).isEqualTo(3);
    }

    // JUnit5부터는 public을 제외해도 무방하다.
    @Test
    void testNumber() {
        assertThat(1).isEqualTo(1);
        assertThat(1).isNotEqualTo(2);
        assertThat(true).isTrue();
        assertThat(1 == 2).isFalse();
    }

    @Test
    void testDescribeAssertion() {
        assertThat(45)
            .as("%s's age should be not equal to 100", "Steve")
            .isNotEqualTo(100);
    }
}

대부분이 메서드명으로 어떤 테스트를 하는지 알 수 있다. as는 테스트 시 출력으로 표시할 메시지를 작성하는 것이다.

테스트 커맨드 옵션

  • gradle test —tests demo.FirstTest
    • —tests 뒤에 패키지명을 적어주면 그 테스트만 수행한다.
    • 메서드까지 지정할 수 있다.
      • gradle test —tests demo.TestCalculator.testAdd
  • gradle test —tests demo.TestCal*
    • 패키지명이 demo.TestCal로 시작하는 모든 테스트를 수행한다.
    • *anyOf*로 지정하면 anyOf를 패키지명에 포함하는 모든 테스트를 수행한다.