ArchUnit: 애플리케이션의 아키텍처를 테스트 할 수 있는 오픈 소스 라이브러리

=> package, class, layer, 슬라이스 간의 의존성을 확인할 수 있는 기능

www.archunit.org/userguide/html/000_Index.html#_what_to_check

 

ArchUnit User Guide

ArchUnit is a free, simple and extensible library for checking the architecture of your Java code. That is, ArchUnit can check dependencies between packages and classes, layers and slices, check for cyclic dependencies and more. It does so by analyzing giv

www.archunit.org

 

 

 

 

아키텍처 테스트란?

  • A 라는 패키지가 B (또는 C, D) 패키지에서만 사용 되고 있는지 확인 가능
  • XXXSerivce라는 이름의 클래스들이 XXXController 또는 XXXService라는 이름의 클래스에서만 참조하고 있는지 확인
  • XXXService라는 이름의 클래스들이 ..service.. 라는 패키지에 들어있는지 확인
  • A라는 애노테이션을 선언한 메소드만 특정 패키지 또는 특정 애노테이션을 가진 클래스를 호출하고 있는지 확인
  • 특정한 스타일의 아키텍처를 따르고 있는지 확인

 

ArchUnit을 사용하기 위해서 먼저 의존성을 추가해주자

testCompile 'com.tngtech.archunit:archunit:0.11.0'

 

그 다음 사용법은 아래와 같다.

참고: blogs.oracle.com/javamagazine/unit-test-your-architecture-with-archunit

@Test
public void Services_should_only_be_accessed_by_Controllers() {
    // 1. 특정 패키지에 해당하는 클래스를 (바이트코드를 통해) 읽어들인다
    JavaClasses importedClasses = new ClassFileImporter().importPackages("com.mycompany.myapp");

    // 2. 확인할 규칙을 정의한다
    ArchRule myRule = classes()
        .that().resideInAPackage("..service..")
        .should().onlyBeAccessed().byAnyPackage("..controller..", "..service..");

    // 3. 읽어들인 클래스들이 그 규칙을 잘 따르는지 확인한다
    myRule.check(importedClasses);
}

 

 

ArchUnit의 한가지 아쉬운점은 DisplayName을 설정하지 못한다

junit engine을 확장하여 archunit-junit 모듈을 만든 것이기 때문이다 (archunit engine을 따로 사용)

JMeter: 성능 특정 및 부하 테스트 기능을 제공하는 오픈 소스 자바 애플리케이션이다. 다양한 테스트를 지원하고 CLI도 지원한다

jmeter.apache.org/

 

Apache JMeter - Apache JMeter™

Apache JMeter™ The Apache JMeter™ application is open source software, a 100% pure Java application designed to load test functional behavior and measure performance. It was originally designed for testing Web Applications but has since expanded to oth

jmeter.apache.org

 

주요 개념

  • Thread Group: 한 쓰레드 당 유저 한명
  • Sampler: 어떤 유저가 해야 하는 액션
  • Listener: 응답을 받았을 할 일 (리포팅, 검증, 그래프 그리기 등)
  • Configuration: Sampler 또는 Listener가 사용할 설정 값 (쿠키, JDBC 커넥션 등)
  • Assertion: 응답이 성공적인지 확인하는 방법 (응답 코드, 본문 내용 등)

 

원래 제대로 성능테스트를 하려면 배포 서버와 성능 테스트용 서버가 달라야 한다.

왜냐면 같은 서버에서 하게 된다면 JMeter를 실행할 때에도 시스템 리소스를 사용하기 때문에 성능 테스트를 할 때 애플리케이션의 성능도 떨어질 수 있기 때문이다.

 

 

바이너리 zip 파일을 다운받은 후 압축을 푼 후에 ./bin/jmeter를 실행하면 아래와 같은 창이 뜬다

JMeter

 

 

가장 먼저 할 일은 Thread Group 만들기이다. (만든 테스트가 어떤 테스트인지 이름을 지어주는 것과 같다)

그 다음 Thread Group을 추가해주도록 한다.

  • Number of Threads: 쓰레드 개수 (동시에 요청을 보내는 유저의 수, 10명)
  • Ramp-up period: 쓰레드 개수를 만드는데 소요할 시간 (유저를 얼마나 빠른 시간동안 만들어 낼것이냐, 10초)
  • Loop Count: infinite 체크 하면 위에서 정한 쓰레드 개수로 계속 요청 보내기. 값을 입력하면 (해당 쓰레드 개수) X (루프 개수) 만큼 요청을 보낸다 (Sampler로 정의할 action을 몇 번 반복할 것인가?, 10명씩 2세트 총 20명)

 

그 다음엔 Sampler를 만들어야 한다. Sampler는 각각의 유저가 할 일을 정의해주는 것과 같다.

Sampler > HttpRequest

  • 여러 종류의 샘플러가 있지만 그 중에 우리가 사용할 샘플러는 HTTP Request 샘플러.
  • HTTP Sampler: 요청을 보낼 호스트, 포트, URI, 요청 본문 등을 설정
  • 여러 샘플러를 순차적으로 등록하는 것도 가능하다.

 

요청을 보낸 후 결과를 보기 위한 Listener 만들기 후에 아래 Listener 등을 추가해보자

  • View Results Tree
  • View Resulrts in Table
  • Summary Report
  • Aggregate Report

application의 한계를 알아보려면 요청을 많이 보내 보고 throughput을 보면 된다(TPS)

 

 

응답의 경우 Assertion을 추가하면 좀 더 자세하게 확인이 가능하다.

  • 응답 코드 확인
  • 응답 본문 확인

여기까지 UI로 jmx 파일을 만든 것이다. 이 것을 커맨드로 실행할려면 아래와 같은 명령어를 커맨트 창에 입력하면 된다.

 

 

CLI 사용하기

  • jmeter -n -t 설정 파일 -l 리포트 파일
    • -n: UI를 쓰지 않는다
    • -t: JMeter 테스트에 대한 설정
    • -l: 리포트

 

추가적으로 BlazeMeter을 사용하면 크롬에서 액션하는 것을 녹화한 후 JMeter에 추가할 수 있다.

chrome.google.com/webstore/detail/blazemeter-the-continuous/mbopgmdnpcbohhpnfglgohlbhfongabi?hl=en

 

BlazeMeter | The Continuous Testing Platform

Record Selenium and HTTP traffic to create a load and functional tests in less than 10 minutes (Apache JMeter Compatible).

chrome.google.com

 

'Java (+ Spring)' 카테고리의 다른 글

아키텍쳐 테스트: ArcUnit  (0) 2021.02.11
운영 이슈 테스트: Chaos Monkey  (0) 2021.02.11
도커로 테스트 하기: TestContainers  (0) 2021.02.10
Mock객체로 테스트 하기: Mockito  (0) 2021.02.10
Java Unit Test: Junit5  (0) 2021.02.10

TestContainers: 테스트에서 도커 컨테이너를 실행할 수 있도록 하는 라이브러리

www.testcontainers.org/

 

Testcontainers

 Testcontainers About Testcontainers is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container. Testcontainers make the followi

www.testcontainers.org

 

DB의 경우 DB마다 isolation, propagation 전략들이 다르다. (스프링에서는 DB에서 제공하는 이러한 전략들을 따를 뿐이다)

 

하지만 컨테이너의 테스트용 디비를 띄우고, 스크립트를 실행하고, 테스트가 끝난 후에는 컨테이너를 정리하는 등 복잡해질 수 있다.

이를 해결하기 위한 방법이 바로 TestContainers이다.

 

먼저 junit 확장체를 지원하는 testcontainers 모듈을 gradle에 추가한다.

여러 모듈 중에 현재 프로젝트에서 사용하고 있는 DB 모듈을 동일하게 사용하면 된다. 내 경우는 MariaDB를 사용하고 있음.

www.testcontainers.org/modules/databases/mariadb/

 

MariaDB Module - Testcontainers

 

www.testcontainers.org

testImplementation "org.testcontainers:junit-jupiter:1.15.1"
testCompile "org.testcontainers:mariadb:1.15.1"
@ActiveProfiles("test")
@Testcontainers
@SpringBootTest
class ToyRepositoryTest {

    @Autowired
    private ToyRepository toyRepository;

    @Autowired
    private OrganizationRepository organizationRepository;

    static MariaDBContainer mariaDB = new MariaDBContainer().withDatabaseName("toy_land_test");

    @BeforeAll
    static void beforeAll() {
        mariaDB.start();
    }

    @AfterAll
    static void afterAll() {
        mariaDB.stop();
    }
    
    @Test
    /* 테스트 */
}
spring:
  jpa:
    open-in-view: false
    generate-ddl: true
    show-sql: true
    hibernate:
      ddl-auto: create-drop
  datasource:
    url: jdbc:tc:mariadb:///toy_land_test
    driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver

 

위 코드의 경우 테스트를 실행시키기 전에 컨테이너를 띄우고 실행 후에는 컨테이너를 없애는 것이 반복되어 느릴 수 있다.

아래 코드처럼 컨테이너를 static으로 띄워두고 테스트를 실행 시키기 전에 DB를 비우는 방식으로 코드를 짤 수도 있다. (위의 경우보다 빠를 것이다.)

package com.openhack.toyland.domain.toy;

import static org.assertj.core.api.Assertions.*;
import com.openhack.toyland.domain.Organization;
import com.openhack.toyland.domain.OrganizationRepository;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.testcontainers.containers.MariaDBContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@ActiveProfiles("test")
@Testcontainers
@SpringBootTest
class ToyRepositoryTest {

    @Autowired
    private ToyRepository toyRepository;

    @Autowired
    private OrganizationRepository organizationRepository;

    @Container
    static MariaDBContainer mariaDB = new MariaDBContainer().withDatabaseName("toy_land_test");

    @BeforeEach
    void beforeEach() {
        toyRepository.deleteAll();
        organizationRepository.deleteAll();
    }

    @Test
    /* 테스트 */
}

 

 

TestContainers가 제공하지 않은 일반적인 컨테이너를 만드는 방법 

  • GenericContainer(dockerImageName): 이미지 이름만 있으면 컨테이너를 만드는 것이 가능하다. 이 경우 먼저 로컬에서 이미지를 찾아보고, 로컬에 없으면 퍼블릭한 원격에서 찾아서 가져온다.
    • withEnv: 환경 변수 세팅이 필요하다.
    • withExposedPorts: 포트 노출 -> 일반적으로는 컨테이너가 랜덤하게 매핑해준다
    • waitingFor: 해당 컨테이너가 띄워졌는지 확인 후 테스트 실행

 

TestContainers의 로그 살펴보기

  • getLogs(): 모든 로그를 보는 방법
  • followOutput(new Slf4jLogConsumer(log)): 로그 스트리밍

 

컨테이너 내부의 정보를 테스트 코드에서 활용하는 방법

ApplicationContextInitializer
스프링 ApplicationContext를 프로그래밍으로 초기화 할 때 사용할 수 있는 콜백 인터페이스로, 특정 프로파일을 활성화 하거나, 프로퍼티 소스를 추가하는 등의 작업을 할 수 있다.

TestPropertyValues
테스트용 프로퍼티 소스를 정의할 때 사용한다.

Environment
스프링 핵심 API로, 프로퍼티와 프로파일을 담당한다.

Testcontainer를 사용해서 생성한 컨테이너에 대해 ApplicationContextInitializer를 구현하여 생성된 컨테이너에서 정보를 Environment에 넣어준다.

그 다음 @ContextConfiguration을 사용해서 아까 구현했던 ApplicationContextInitializer 구현체를 등록해주면, 테스트 코드에서 Environment, @Value, @ConfigurationProperties 등 다양한 방법으로 프로퍼티를 사용할 수 있게 된다.

@ActiveProfiles("test")
@Testcontainers
@Slf4j
@ContextConfiguration(initializers = StudyServiceTest.ContainerPropertyInitializer.class)
class StudyServiceTest {

    @Mock MemberService memberService;

    @Autowired StudyRepository studyRepository;

    @Value("${container.port}") int port;

    @Container
    static GenericContainer postgreSQLContainer = new GenericContainer("postgres")
            .withExposedPorts(5432)
            .withEnv("POSTGRES_DB", "studytest");
    @BeforeAll
    static void beforeAll() {
        Slf4jLogConsumer logConsumer = new Slf4jLogConsumer(log);
        postgreSQLContainer.followOutput(logConsumer);
    }
    
    @BeforeEach
    void beforeEach() {
        System.out.println("===========");
        System.out.println(postgreSQLContainer.getMappedPort(5432));
        System.out.println(port);
        studyRepository.deleteAll();
    }

    @Test
    /* 테스트 */

    static class ContainerPropertyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

        @Override
        public void initialize(ConfigurableApplicationContext context) {
            TestPropertyValues.of("container.port=" + postgreSQLContainer.getMappedPort(5432))
                    .applyTo(context.getEnvironment());
        }
    }

}

 

 

 

 

만약 테스트 컨테이너들을 여러개를 동시에 띄워서 사용해야 한다면? -> DockerComposeContainer

www.testcontainers.org/modules/docker_compose/

 

Docker Compose Module - Testcontainers

 Docker Compose Module Benefits Similar to generic containers support, it's also possible to run a bespoke set of services specified in a docker-compose.yml file. This is intended to be useful on projects where Docker Compose is already used in dev or o

www.testcontainers.org

static DockerComposeContainer composeContainer =
    new DockerComposeContainer(new File("src/test/resources/docker-compose-test.yml"))
            .withExposedService("redis_1", REDIS_PORT, Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(30)))
            .withExposedService("elasticsearch_1", ELASTICSEARCH_PORT, Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(30)));

위와 같이 docker-compose 파일을 주면 여러 컨테이너를 띄울 수 있다. (이때, 호스트쪽 포트 매핑은 안해두는 것이 낫다)

아직 컨테이너가 뜨지도 않았는데 테스트를 실행하는 것을 방지하기 위해 Wait.forListeningPort()를 추가 해 주는 것이 좋다.

 

위의 경우 docker-compose로 띄운 서비스의 정보를 참조하기 위해 구현하는 ApplicationContextInitializer는 아래와 같이 구현하면 된다.

static class ContainerPropertyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext context) {
        TestPropertyValues.of("container.port=" + composeContainer.getServicePort("study-db", 5432))
                .applyTo(context.getEnvironment());
    }
}

 

 

Mock: 진짜 객체와 비슷하게 동작하지만 프로그래머가 직접 그 객체의 행동을 관리하는 객체.

Mockito: Mock을 지원하는 프레임워크로, 객체를 쉽게 만들고 관리하고 검증할 수 있는 방법을 제공한다.

 

martinfowler.com/bliki/UnitTest.html

 

bliki: UnitTest

Unit Tests are focused on small parts of a code-base, defined in regular programming tools, and fast. There is disagreement on whether units should be solitary or sociable.

martinfowler.com

단위 테스트를 생각할 때, 클래스나 오브젝트, 또는 행동과 연관된 로직이라고 다양하게 생각할 수 있다.

나중에 테스트를 작성하게 되면 단위테스의 단위를 어떻게 정의할 것인가, Mock은 어느 범위에서 적용할 것인가 등을 함께 정하는 것이 좋다고 함

 

 

Mockito의 경우 스프링 부트를 사용하게 되면 spring-boot-starter-test의 의존성을 따라 자동으로 들어오게 된다.

만약 이 것을 사용하지 않은 경우엔 아래 2가지 의존성 추가가 필요하다.

  • mockito-junit-jupiter: junit test에서 mockito를 연동해서 사용할 수 있는 추가적인 확장 구현체를 제공하는 라이브러리
  • mockito-core: mockito가 제공하는 기본적인 기능들이 들어있는 라이브러리

 

Mock은 그럼 언제 사용하냐면?

외부 api등을 호출할 때, 어떻게 답이 오는지 가정 등을 하고 mock 객체로 만들어 이 것이 어떻게 동작하는지 테스트할 경우 등 사용한다.

또, 내가 만들고 있는 코드가 의존하는 클래스의 구현체는 없지만 인터페이스는 있고, 그 인터페이스 기반으로 코드를 작성할 때 그 것을 확인하기 위해 사용되기도 한다.

아래와 같은 두 가지 방법으로 Mock 객체를 만들 수 있다.

  • Mockito.mock: 생성자로 주입하는 방법
  • @Mock + @ExtendWith(MockitoExtension.class) - 함수 밖에 테스트 클래스의 필드로 주입할 수도 있고, 파라미터 전달도 가능하다

 

Mock 객체를 만든 후에는 Mock 객체의 행동을 조작해야한다. 이를 Stubbing이라고 한다.

모든 Mock 객체의 행동은 기본적으로 아래와 같이 행동한다.

  • 리턴 타입이 있다면 Null 리턴한다. (Optional 타입은 Optional.empty 리턴)
  • Primitive 타입은 프리미티브 기본 값을 따름
  • 콜렉션은 비어있는 콜렉션으로 만들어준다
  • Void 메소드는 예외를 던지지 않고 아무런 일도 발생하지 않는다

Mock 객체를 조작하기 위해서는 

  • Mockito.when(/*조건*/).thenReturn(/*객체*/): 조건에 해당한다면 무조건 어떠한 객체를 리턴하라
  • Mockito.when(/*조건*/).thenReturn(/*객체*/).thenThrow~~.thenReturn~~: 메소드가 여러 번 호출될 때 stubbing을 같은 매개변수로 호출 되더라도 호출되는 순서에 따라 다르게 mocking. 즉, 동일한 조건에 대해 다양한 객체/예외를 리턴하도록 설정해 둘 수 있다.
  • doThrow(/*Exception*/).when(/*조건*/): 조건에 해당한다면 어떠한 예외를 던져라

 

마지막으로 Mockito로 만든 Mock 객체가 어떻게 사용됐는지(객체에 어떤 일이 일어났는지) 확인하는 방법이다.

  • verify(memberService, times(1)).notify(study): 예를 들어 mock객체 memberService의 함수 notify가 1번만 호출되어야 한다
  • inOrder(memberService): 함수 호출에 대해 순서를 부여하고 싶은 경우

다른 여러가지 조건은 아래 공식문서를 확인하도록 하자

javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#verification_timeout

 

Mockito - mockito-core 3.7.7 javadoc

Latest version of org.mockito:mockito-core https://javadoc.io/doc/org.mockito/mockito-core Current version 3.7.7 https://javadoc.io/doc/org.mockito/mockito-core/3.7.7 package-list path (used for javadoc generation -link option) https://javadoc.io/doc/org.m

javadoc.io

 

 

Mockito에서는 별개로 BDD 스타일의 테스트를 할 수 있는 방법을 BddMockito 클래스를 통해 제공한다.

BDD (Behaviour-Driven Development): 애플리케이션이 어떻게 “행동”해야 하는지에 대한 공통된 이해를 구성하는 방법으로, TDD에서 창안했다.

BDD는 시나리오를 기반으로 테스트 케이스를 작성하며 함수 단위 테스트를 권장하지 않는다. 이 시나리오는 개발자가 아닌 사람이 봐도 이해할 수 있을 정도의 레벨을 권장한다. 하나의 시나리오는 Given, When, Then 구조를 가지는 것을 기본 패턴으로 권장하며 각 절의 의미는 다음과 같다.

Feature : 테스트에 대상의 기능/책임을 명시한다
Scenario : 테스트 목적에 대한 상황을 설명한다
Given : 시나리오 진행에 필요한 값을 설정한다 - 어떤 상황이 주어졌을 때
When : 시나리오를 진행하는데 필요한 조건을 명시한다 - 무엇인가를 하면
Then : 시나리오를 완료했을 때 보장해야 하는 결과를 명시한다 - ~~할 것이다
위의 내용을 개발 측면에서 더 간략하게 정리하면 테스트 대상의 상태 변화를 테스트하는 것이다.
테스트 대상은 A 상태에서 출발하며(Given) 어떤 상태 변화를 가했을 때(When) 기대하는 상태로 완료되어야 한다. (Then)
또는 Side Effect가 전혀 없는 테스트 대상이라면 테스트 대상의 환경을 A 상태에 두고(Given) 어떤 행동을 요구했을 때(When) 기대하는 결과를 돌려받아야 한다. (Then)

 

Jnuit5 - java8 이상을 필요로 하고 단위테스트를 작성할 때 사용한다.

 

junit4는 써보진 않았지만, 하나의 jar dependency library 형태로 들어오고, junit이 참조하는 다른 라이브러리들이 있는 그런 형태였다면,

junit5는 그 자체로 여러 모듈화(junit platform, jupiter, vintage)가 되어있다. 

  • Platform:  테스트를 실행해주는 런처 제공. TestEngine API 제공 => 런쳐를 통해 콘솔이나 java 메인 메소드, intellij 등에서 테스트 실행 가능
  • Jupiter: TestEngine API 구현체로 JUnit 5를 제공
  • Vintage: JUnit 4와 3을 지원하는 TestEngine 구현체

스프링 부트 프로젝트에는 기본적으로 junit5의존성이 추가되어 있다.

 

기본적인 어노테이션은 아래와 같다.

  • @Test
  • @BeforeAll - test class 안에 있는 여러 테스트가 모두 실행 될 때, 모든 테스트가 실행되기 전에 한 번만 호출. 구현할 때에는 반드시 static 메소드로 구현해야 하고, private는 불가능하다. 또한, 리턴 타입이 있으면 안됨
  • @AfterAll - 마찬가지로 모든 테스트가 실행 된 이후 한 번만 호출. static void 형태로 작성한다.
  • @BeforeEach - 모든 테스트를 실행할 때, 각각의 테스트를 실행하기 이전에 한 번씩 호출된다. 마찬가지로 리턴 타입은 void이어야 하며 private를 사용하지 않도록 한다.
  • @AfterEach - 마찬가지로 모든 테스트 이후 각각 실행된다. 리턴 타입은 void, private는 사용하지 않도록 한다.
  • @Disabled - 필요 없는 테스트 등을 빼고 실행할 때 사용

공식문서에서는 private를 `must not`으로 사용하지 말라고 나와있지만,, 리플렉션 때문에 private를 사용해도 사실 돌아가긴 한다.

sowhat4.tistory.com/73

 

Junit5 @BeforeEach private method가 동작하는 이유

발단 어느 날과 다름없이 비즈니스 로직을 먼저 작성하고 테스트 코드를 작성하는 코드 몽키 ing 중 🐒 테스트 코드 실행 전 초기 설정이 필요하여 Junit5의 @BeforeEach 사용 호기심이 발동하여 접근

sowhat4.tistory.com

 

 

일반적으로 테스트를 실행하면 테스트에 이름을 표기하는 방법은 아래와 같다. (기본 표기 전략은 메소드 이름)

메소드 이름은 길어지면 snake case로 작성한다.

  • @DisplayNameGeneration: 메소드와 클래스 레퍼런스를 사용해서 테스트 이름을 표기하는 방법 설정하는 방법으로, 기본 구현체로 ReplaceUnderscores 제공 (ex.@DisplayNameGeneration(DisplayGenerator.ReplaceUnderscores.class)) 
  • @DisplayName: 어떤 테스트인지 테스트 이름을 보다 쉽게 표현할 수 있는 방법을 제공하는 애노테이션이다. @DisplayNameGeneration 보다 우선 순위가 높다.

 

한 테스트에서 여러 Assertion문이 있는 경우에는 앞에 있는 assert문이 실패할 경우 그 다음을 보지 않는데,

이 것을 한번에 실행해 주는 방법이 assertAll이다. 각 assert문을 람다식으로 묶어 주면 한번에 실행하여 결과를 알 수 있다.

assertAll(
	() -> assertNotNull(study),
    () -> assertEquals(StudyStatus.DRAFT, study.getStatus(), () -> "스터디를 처음 만들면" + StudyStatus.DRAFT + "상태이다."),
    () -> assertTrue(study.getLimit()>0, "스터디 최대 참석 가능 인원은 0명 이상이다.");
);

참고로, assert문이 실패했을 때 메세지를 supplier를 사용하여 나타내게 되면 테스트가 실패할 때만 연산이 일어나기 때문에 성능상 조금 더 이점이 있다.

이와 유사하게 테스트 태깅 등으로 그룹화를 할 수 있다.

  • @Tag: 테스트를 그룹화 ex) 테스트가 오래 걸려서 로컬에서 테스트하기 적절하지 않는지/테스트가 짧게 끝나는지 등
    • 실행 방법
      • intellij에서 edit contiguration > test kind를 Tags로 바꾸기 > tag expression 설정해 주기
      • maven 설정: maven-surefire-plugin의 그룹을 설정해주기

 

 

조건에 따라 테스트를 실행할 수도 있다. (특정한 OS, 특정한 java version, 환경 변수 등)

  • assumeTrue(조건): 예를 들어 테스트의 환경변수가 로컬일 경우에만 실행할 때, 아래와 같이 진행할 수 있다.
assumeTrue("LOCAL".equalsIgnoreCase(System.getenv("TEST_ENV")));

/*테스트*/
  • assumingThat(조건, 테스트)
assumingThat("LOCAL".equalsIgnoreCase(System.getenv("TEST_ENV")), /*테스트*/);

 

  • @EnabledXXX 와 @DisabledXXX
    • OnOS: OS 종류
    • OnJre: java 버전
    • IfSystemProperty
    • IfEnvironmentVariable: 환경변수
    • If

 

junit5에서 제공하는 애노테이션들은 메타애노테이션들을 사용할 수 있는데, 그렇기 때문에 composed 애노테이션을 만들어서 사용할 수 있다고 한다.

즉, 커스텀 애노테이션들을 만들 때, 그 애노테이션 위에 기존의 메타애노테이션들을 사용하게 되면, 기존의 기능이 적용된다고 할 수 있다.

이 외에도 파라미터를 여러 인자값을 주면서 테스트를 반복할 수 있다.

junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests

 

JUnit 5 User Guide

Although the JUnit Jupiter programming model and extension model will not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and cus

junit.org

 

기본적으로 junit이 테스트를 실행할 때, 클래스의 인스턴스를 만들 때 기본 전략은 테스트 메소드 마다 생성하게 된다.

테스트 순서는 예측할 수 없다. 기본적으로 메소드가 선언되어있는 순서대로 실행되긴 하지만,,, 꼭 그런건 아니라고 한다.

그래서 테스트 간 의존성을 없애기 위해서, 서로 공유하는 값을 바뀌지 않게 하기 위해서 각각 인스턴스를 만들어서 테스트를 실행하게 된다.

 

junit5부터는 이 기본 전략을 바꿀 수 있는 방법이 있다.

  • @TestInstance(Lifecycle.PER_CLASS)
    • 테스트 클래스당 인스턴스를 하나만 만들어 사용한다. => 이 경우 BeforeAll/AfterAll이 static 메소드일 필요가 없어진다
    • 경우에 따라, 테스트 간에 공유하는 모든 상태를 @BeforeEach 또는 @AfterEach에서 초기화 할 필요가 있다.
    • @BeforeAll과 @AfterAll을 인스턴스 메소드 또는 인터페이스에 정의한 default 메소드로 정의할 수도 있다. 

아까 말했듯이, 테스트 순서는 정해져 있지 았다. 그래서 테스트를 실행할 순서를 정의해서 상태 정보를 유지하며 usecase를 테스트할 수 있는 방법도 있다.

그렇게 하기 위해서는 위 방법처럼 TestInstance 애노테이션으로 의존성을 공유하는 것이 먼저이다.

그 다음 @TestMethodOrder 애노테이션을 클래스에 붙여준다. 기본 구현체는 아래 3가지를 제공해 준다.

  • Alphanumeric
  • OrderAnnoation: @Order(int), 낮은 값부터 실행시킨다
  • Random

 

마지막으로 junit5에서 extension을 사용하는 방법은 아래 공식문서를 보고 좀 더 공부해봐야겠다.

 

junit.org/junit5/docs/current/user-guide/#extensions

 

JUnit 5 User Guide

Although the JUnit Jupiter programming model and extension model will not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and cus

junit.org

 

+ Recent posts