티스토리 뷰

먼저 읽어볼만한 글

라이브러리 종속성 추가

/build.gradle 파일에 아래 내용을 추가한다.

dependencies {
    testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test'
    testCompile group: 'org.assertj', name: 'assertj-core', version: '3.9.0'
}
  • spring-boot-starter-test Starter POM의 추가 만으로 Spring Test, JUnit, Hamcrest, Mockito를 모두 사용하여 테스트 클래스를 작성할 수 있다.

테스트 클래스 작성

/src/test/java/com.jsonobject.example/ExampleTest.java 파일을 아래와 같이 작성한다.

import com.jsonobject.example.Application;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.WebIntegrationTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@TestPropertySource(properties = "server.port=8081")
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@ActiveProfiles
public class ExampleTest {

    @Autowired
    @Qualifier("anotherDAO")
    private SomeDAO someDAO;

    @Resource(name = "anotherService")
    private SomeService someService;

    @Autowired
    private SomeController someController;

    @Test
    public void aTest() {
        // 테스트 코드 작성
    }

    @Test
    public void bTest() {
        // 테스트 코드 작성
    }
}
  • 클래스에 @SpringApplicationConfiguration를 명시하면 ApplicationContext를 선택하여 테스트를 할 수 있다. 이에 따라 클래스 멤버에 애플리케이션 컨텍스트의 빈을 주입하여 테스트에 사용할 수 있다. @Autowired, @Qualifier, @Resource 모두 사용 가능하다.

  • 클래스에 @SpringBootTest를 명시하면 실제 웹 애플리케이션이 구동되는 환경과 동일하게 HTTP 요청 테스트가 가능하다. webEnvironment 옵션을 지정할 수 있는데 SpringBootTest.WebEnvironment.DEFINED_PORT을 지정하면 application-{profile}.xml 파일에 명시된 server.port 값을 따르게 된다. 즉, 적용된 프로파일과 동일한 포트를 사용하여 유닛 테스트를 수행할 수 있다. 고려할 점은 이미 서비스가 기동 중인 환경에서는 같은 포트를 쓰게 되므로 충돌이 나서 테스트가 불가능하다. 이러한 포트 충돌을 예방하려면 유닛 테스트를 기동할 때 다른 포트를 사용해야 한다. 위 예제의 @TestPropertySourceproperties 옵션을 사용하여 충돌이 나지 않는 포트를 명시할 수 있다.

  • HTTP 요청에 의한 테스트 방법은 매우 간단하다. @Test가 명시된 메써드 범위에서 로컬로 지정된 포트에 대해 원하는 요청을 실행하여 기대 결과를 확인하면 된다. 내 경우 Retrofit을 사용하면 서버 사이드 코드를 테스트에 재사용할 수 있어 선호하는 편이다. [관련 글]

  • 클래스에 @ActiveProfiles를 명시하면 테스트 실행시 적용될 프로파일을 적용할 수 있다.

  • 메써드에 @Test를 명시하면 테스트 메써드로 선언되어 메써드 단위 테스트가 가능해진다. 클래스 단위 테스트시 실행될 대상 메써드가 된다.

  • JUnit에서는 클래스 단위 테스트 실행시 메써드 간의 테스트 순서가 임의로 진행된다. 클래스에 @FixMethodOrder를 명시하면 테스트 메써드 간의 실행 순서를 결정할 수 있다. MethodSorters.NAME_ASCENDING은 메써드의 이름 순으로 실행 순서를 결정하겠다는 의미이다.

Assertion 작성

  • 유닛 테스트의 기본은 실제 결과가 기대 결과와 일치하는가를 확인하는 것이다. 일치하는가? 부분만 일치하는가? 포함하는가? 특정 값보다 큰가? 또는 작은가? 와 같은 기대 결과를 작성하는 도구를 Assertion 라이브러리라고 부른다. Java 진영에서는 전통적으로 Hamcrest가 널리 사용되어 왔고 최근 AssertJ가 급속도로 인기를 얻고 있다. 개인적으로 물 흐르듯한 직관적인 문법의 AssertJ를 선호하여 아래 간단히 사용 예를 정리하였다. 앞서 설명한 예제의 @Test 메써드 범위에서 사용이 가능하다.
assertThat(object).isNull();
assertThat(object).isNotNull();
assertThat(value).isEqualTo(expectedValue);
assertThat(value).as("HTTP Status Code").isEqualTo(200);
assertThat(stringValue).hasSize(10);
assertThat(localDateTime).isAfter(today);
assertThat(stringList).contains("Ronaldo", "Messi");
assertThat(stringList).contains("Ronaldo").doesNotContain("Neymar");
assertThat(objectList).extracting("name").contains("Ronaldo");

웹 브라우저 테스트

  • 추가적으로 웹 브라우저에서의 동작을 테스트하려면 웹 브라우저 테스팅 프레임워크인 SeleniumSelenide 라이브러리 정보를 아래와 같이 추가해야 한다.
dependencies {
    compile('org.seleniumhq.selenium:selenium-api:3.10.0')
    compile('org.seleniumhq.selenium:selenium-chrome-driver:3.10.0')
    compile('org.seleniumhq.selenium:selenium-edge-driver:3.10.0')
    compile('org.seleniumhq.selenium:selenium-firefox-driver:3.10.0')
    compile('org.seleniumhq.selenium:selenium-ie-driver:3.10.0')
    compile('org.seleniumhq.selenium:selenium-java:3.10.0')
    compile('org.seleniumhq.selenium:selenium-opera-driver:3.10.0')
    compile('org.seleniumhq.selenium:selenium-remote-driver:3.10.0')
    compile('org.seleniumhq.selenium:selenium-safari-driver:3.10.0')
    compile('org.seleniumhq.selenium:selenium-support:3.10.0')
    compile('com.codeborne:selenide:4.10')
}
  • 셀레니움은 라이브러리 본체와 웹 브라우저 인터페이스로 구성된다. 따라서 실제 웹 브라우저 역할을 수행하는 드라이버가 로컬 시스템에 설치되어야 한다. 본 예제에서는 가장 대중적인 Google Chrome Driver를 기준으로 설명한다. 여기를 클릭하여 운영체제에 맞는 드라이버를 다운로드한다.
  • 로컬에 위치한 드라이버 파일은 개인 프로젝트라면 문제가 없지만 다수가 사용하는 테스트 도구라면 항상 바이너리 파일이 특정 위치에 설치되어 있어야 하는 번거로움이 생긴다. 그래서 프로젝트 내의 /src/main/resources 디렉토리에 웹 드라이버 바이너리 파일을 포함하여 배포하고 테스트시 해당 파일을 각 로컬 시스템에 복사하여 사용하도록 구현하는 것이 편리하여 추천한다.
  • 웹 브라우저 테스트 목적으로는 설치된 웹 드라이버와 Selenium 만으로도 충분하다. 하지만 Selenide를 사용하면 철저히 브라우저를 사용하는 사용자의 느낌으로 쉽고 직관적인 테스트 코드 작성이 가능해진다. 마치 JavascriptjQuery의 관계와 같다.
  • 본격적으로 테스트 코드를 작성하기 전에 편의를 위해 자주 변경될 가능성이 높은 옵션을 환경설정 파일로 분리시키는 것이 좋다. /src/main/resources/application.yml 파일을 아래와 같이 작성한다.
selenide:
    browserType: chrome
    browserLocation: chromedriver.exe
    headlessMode: true
  • browserType에 사용할 웹 브라우저의 종류를 명시하고 browserLocation에는 앞서 /src/main/resources 디렉토리에 복사한 바이너리 파일의 경로를 명시한다. 마지막으로 headlessMode 여부를 명시한다. true일 경우 테스트 과정에서 웹 브라우저 화면이 나타난다. false일 경우 눈에 보이지 않게 백그라운드로 진행된다.
  • 드라이버 설치까지 완료되면 아래와 같이 웹 브라우저 테스트 클래스를 작성할 수 있다.
  • import lombok.extern.slf4j.Slf4j;
    import org.junit.After;
    import org.junit.Before;
    import org.junit.FixMethodOrder;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.junit.runners.MethodSorters;
    import org.openqa.selenium.By;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.chrome.ChromeDriver;
    import org.openqa.selenium.chrome.ChromeOptions;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.ActiveProfiles;
    import org.springframework.test.context.junit4.SpringRunner;
    
    import static org.assertj.core.api.Assertions.assertThat;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    @FixMethodOrder(MethodSorters.NAME_ASCENDING)
    @ActiveProfiles
    @Slf4j
    public class ExampleTest {
    
        @Value("${selenide.browserType}")
        private String BROWSER_TYPE;
    
        @Value("${selenide.browserLocation}")
        private String BROWSER_LOCATION;
    
        @Value("${selenide.headlessMode}")
        private boolean HEADLESS_MODE;
    
        @Before
        public void INIT() throws IOException {
    
            // 프로젝트에 포함된 WebDriver 바이너리 파일을 테스트를 실행한 로컬 시스템에 복사
            if (!Files.exists(Paths.get(BROWSER_LOCATION))) {
                FileUtils.copyFile(new ClassPathResource(BROWSER_LOCATION).getFile(), new FileOutputStream(BROWSER_LOCATION));
            }
            Configuration.browser = BROWSER_TYPE;
            Configuration.headless = HEADLESS_MODE;
            System.setProperty("webdriver.chrome.driver", BROWSER_LOCATION);
        }
    
        @Test
        public void GET_GOOGLE_SEARCH_BUTTON_TEXT() {
    
            open("https://google.com");
            $("input[value=\"I’m Feeling Lucky\"]").should(exist);
        }
    }
    

    Gradle 테스트하기

    • Gradle 기반의 프로젝트는 아래와 같은 명령어로 앞서 작성한 유닛 테스트를 실행할 수 있다.
    # 테스트 후 빌드 진행, 테스트 실패시 빌드 불가
    $ gradle build
    
    # 테스트를 생략하고 빌드 진행
    $ gradle build -x test
    
    # 테스트만 진행
    $ gradle test
    
    # 프로파일 변수로 test를 전달하여 테스트 진행
    $ SPRING_PROFILES_ACTIVE=test gradle test
    
    • 테스트가 종료되면 결과 리포트 HTML 파일이 /build/reports/tests/test/index.html 경로에 생성되어 확인이 가능하다.
    • 한편, 테스트 결과로 생성되는 파일과 별개로 콘솔로 실시간으로 출력되는 테스트 결과의 테마를 지정할 수 있다. Gradle 플러그인으로 제공되는 com.adarshr.test-logger를 사용하는 것인데 /build.gradle 파일에 아래 내용을 추가하면 된다. 테마는 plain, standard, mocha 3가지가 제공된다. 가장 가독성이 뛰어난 것은 mocha 테마이다.
    plugins {
        id "com.adarshr.test-logger" version "1.1.2"
    }
    
    testlogger {
        theme 'mocha'
    }
    
    • 콘솔 출력시 로거에 의해 출력되는 로그는 오히려 방해가 된다. /src/test/resources/logback-test.xml 파일을 아래와 같이 작성하면 테스트 진행 중에 로그가 출력되지 않는다.
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration>
    
    <configuration />
    

    Gradle 코드 커버리지

    • 테스트 결과 못지 않게 중요한 것이 Code Coverage 확인이다. Gradle는 코드 커버리지 기능의 JaCoCo 플러그인을 제공한다. 먼저 프로젝트의 /build.gradle 파일에 아래 내용을 추가한다.
    apply plugin: 'jacoco'
    
    • 아래와 같이 테스트 결과에 대한 코드 커버리지를 확인할 수 있다. 반드시 코드 커버리지에 앞서 테스트가 선행되어야 한다. 리포트 HTML 파일은 /build/reports/jacoco/test/index.html 경로에 생성되어 확인이 가능하다.
    $ gradle test jacocoTestReport
    

    참고 글

TAG
,
댓글
댓글쓰기 폼