programing

JUnit 5에서는 모든 테스트 전에 코드를 실행하는 방법

minecode 2022. 10. 31. 21:20
반응형

JUnit 5에서는 모든 테스트 전에 코드를 실행하는 방법

@BeforeAll주석은 클래스의 모든 테스트 전에 실행할 메서드를 표시합니다.

http://junit.org/junit5/docs/current/user-guide/ #기입설명서

하지만 모든 수업에서 모든 테스트 에 코드를 실행할 수 있는 방법이 있을까요?

테스트에서 특정 데이터베이스 접속 세트를 사용하는지 확인하고 테스트를 실행하기 전에 이들 접속의 글로벌한 일회성 설정을 수행해야 합니다.

JUnit5에서는 커스텀 Extension을 생성하여 셧다운 후크를 루트테스트 컨텍스트에 등록할 수 있게 되었습니다.

내선번호는 다음과 같습니다.

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;

public class YourExtension implements BeforeAllCallback, ExtensionContext.Store.CloseableResource {

    private static boolean started = false;

    @Override
    public void beforeAll(ExtensionContext context) {
        if (!started) {
            started = true;
            // Your "before all tests" startup logic goes here
            // The following line registers a callback hook when the root test context is shut down
            context.getRoot().getStore(GLOBAL).put("any unique name", this);
        }
    }

    @Override
    public void close() {
        // Your "after all tests" logic goes here
    }
}

그런 다음 이 작업을 한 번 이상 실행해야 하는 모든 테스트 클래스에 주석을 달 수 있습니다.

@ExtendWith({YourExtension.class})

여러 클래스에서 이 확장을 사용하는 경우 시작 및 종료 로직은 한 번만 호출됩니다.

@Phillip Gayret에서 이미 제공된 답변에 병렬로 JUnit을 테스트할 때 문제가 발생했습니다(즉,junit.jupiter.execution.parallel.enabled = true를 참조해 주세요.

따라서 솔루션을 다음과 같이 수정했습니다.

import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class BeforeAllTestsExtension extends BasicTestClass
        implements BeforeAllCallback, ExtensionContext.Store.CloseableResource {

    /** Gate keeper to prevent multiple Threads within the same routine */
    private static final Lock LOCK = new ReentrantLock();
    /** volatile boolean to tell other threads, when unblocked, whether they should try attempt start-up.  Alternatively, could use AtomicBoolean. */
    private static volatile boolean started = false;
    
    @Override
    public void beforeAll(final ExtensionContext context) throws Exception {
        // lock the access so only one Thread has access to it
        LOCK.lock();
        try {
            if (!started) {
                started = true;
                // Your "before all tests" startup logic goes here
                // The following line registers a callback hook when the root test context is
                // shut down
                context.getRoot().getStore(GLOBAL).put("any unique name", this);

                // do your work - which might take some time - 
                // or just uses more time than the simple check of a boolean
            }
        finally {
            // free the access
            LOCK.unlock();
        }
    }

    @Override
    public void close() {
        // Your "after all tests" logic goes here
    }
}

아래 JUnit5는 자동 내선번호 등록을 제공합니다.그러기 위해서는 를 추가합니다.src/test/resources/라는 전화번호부/META-INF/services.org.junit.jupiter.api.extension.Extension 파일에 를 들어 이 파일에 클래스의 완전한 기밀 이름을 추가합니다.

at.myPackage.BeforeAllTestsExtension

동일한 Junit 구성 파일에서 다음 활성화

junit.jupiter.extensions.autodetection.enabled=true

이것에 의해, 확장이 모든 테스트에 자동적으로 첨부됩니다.

@Phillip의 제안에 따라 보다 완전한 코드 스니펫을 소개합니다.

import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;    
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public abstract class BaseSetupExtension
    implements BeforeAllCallback, ExtensionContext.Store.CloseableResource {

  @Override
  public void beforeAll(ExtensionContext context) throws Exception {
    // We need to use a unique key here, across all usages of this particular extension.
    String uniqueKey = this.getClass().getName();
    Object value = context.getRoot().getStore(GLOBAL).get(uniqueKey);
    if (value == null) {
      // First test container invocation.
      context.getRoot().getStore(GLOBAL).put(uniqueKey, this);
      setup();
    }
  }

  // Callback that is invoked <em>exactly once</em> 
  // before the start of <em>all</em> test containers.
  abstract void setup();

  // Callback that is invoked <em>exactly once</em> 
  // after the end of <em>all</em> test containers.
  // Inherited from {@code CloseableResource}
  public abstract void close() throws Throwable;
}

사용방법:

public class DemoSetupExtension extends BaseSetupExtension {
  @Override
  void setup() {}

  @Override
  public void close() throws Throwable {}
}  

@ExtendWith(DemoSetupExtension.class)
public class TestOne {
   @BeforeAll
   public void beforeAllTestOne { ... }

   @Test
   public void testOne { ... }
}

@ExtendWith(DemoSetupExtension.class)
public class TestTwo {
   @BeforeAll
   public void beforeAllTestTwo { ... }

   @Test
   public void testTwo { ... }
}

테스트 실행 순서는 다음과 같습니다.

  DemoSetupExtension.setup (*)
  TestOne.beforeAllTestOne
  TestOne.testOne
  TestOne.afterAllTestOne
  TestTwo.beforeAllTestTwo
  TestTwo.testTwo
  TestTwo.afterAllTestTwo
  DemoSetupExtension.close (*)

...이것은 단일 @Test(예: TestOne.testOne) 또는 전체 테스트 클래스(TestOne) 또는 다중/모든 테스트를 실행하도록 선택한 경우에도 마찬가지입니다.

각는, 「」를 할 수 .static BeforeAll(미국의) §:

interface UsesDatabase {
    @BeforeAll
    static void initializeDatabaseConnections() {
        // initialize database connections
    }
}

이 메서드는 구현 클래스별로1회 호출되므로 접속을 1회만 초기화하고 다른 콜에 대해서는 아무것도 하지 않는 방법을 정의해야 합니다.

나는 그것을 하는 방법을 알지 못한다.

@BeforeAll의 모든 코드가 특정 싱글톤을 호출하여 init이 동작하도록 하는 것(아마도 반복을 피하기 위한 게으른 방법으로)을 확인하는 것 뿐입니다.

아마 편리하지 않을 거야...기타 옵션으로는 특정 JVM 작업 내에서 테스트가 실행된다고 가정합니다.에이전트를 JVM 실행에 후크할 수 있습니다.그렇게 하면,

그 이상: 두 제안 모두 왠지 내게는 해킹처럼 들린다. 눈에는 진정한 답이 있습니다. 한 발짝 물러서서 환경에 의존하는지 꼼꼼히 살펴보세요.그런 다음 테스트가 시작되고 "올바른 일"이 자동으로 수행되도록 환경을 준비할 방법을 찾습니다.즉, 이 문제의 원인이 된 아키텍처를 조사하는 것을 검토해 주십시오.

위의 조언은 에게 효과가 없기 때문에 나는 이 문제를 다음과 같이 해결했다.

기본 추상 클래스(즉, setUpDriver() 메서드에서 드라이버를 초기화하는 추상 클래스)에 코드의 다음 부분을 추가합니다.

private static boolean started = false;
static{
    if (!started) {
        started = true;
        try {
            setUpDriver();  //method where you initialize your driver
        } catch (MalformedURLException e) {
        }
    }
}

테스트 클래스가 Base Abstract class -> setUpDriver() 메서드에서 확장되는 경우 첫 번째 @Test는 프로젝트 실행당 한 번만 실행됩니다.

@Mihnea Giurgea의 뒤를 이어 @Phillip Gayret의 매우 좋은 답변에 대한 나의 POC 개선점을 소개합니다.

서브 질문:공유 싱글톤리소스에 접속하려면 어떻게 해야 하나요? (아마도 궁금하실 거예요...)

사이드바:실험을 하는 동안, 여러 개를 사용하는 것이@ExtendWith(...)제대로 둥지를 튼 것 같아요.그렇다고 해도, 어느 시점에서는 그렇게 동작하지 않았던 것이 기억되기 때문에, 사용 케이스가 올바르게 동작하고 있는 것을 확인해 주세요.

아마 바쁘기 때문에 디저트가 먼저입니다.다음은 "폴더 내의 모든 테스트"를 실행한 결과입니다.

NestedSingleton::beforeAll (setting resource)
Singleton::Start-Once
Base::beforeAll
Colors::blue - resource=Something nice to share!
Colors::gold - resource=Something nice to share!
Base::afterAll
Base::beforeAll
Numbers::one - resource=Something nice to share!
Numbers::tre - resource=Something nice to share!
Numbers::two - resource=Something nice to share!
Base::afterAll
Singleton::Finish-Once
NestedSingleton::close (clearing resource)

물론 단일 테스트 클래스를 실행하는 것만으로 얻을 수 있는 이점은 다음과 같습니다.

NestedSingleton::beforeAll (setting resource)
Singleton::Start-Once
Base::beforeAll
Numbers::one - resource=Something nice to share!
Numbers::tre - resource=Something nice to share!
Numbers::two - resource=Something nice to share!
Base::afterAll
Singleton::Finish-Once
NestedSingleton::close (clearing resource)

그리고 이제 예상할 수 있는 대로 특정 테스트:

NestedSingleton::beforeAll (setting resource)
Singleton::Start-Once
Base::beforeAll
Colors::gold - resource=Something nice to share!
Base::afterAll
Singleton::Finish-Once
NestedSingleton::close (clearing resource)

내 말 듣고 있어?그럼 실제 코드를 보는 걸 즐기실 수 있을 거야

======================================================
junitsingletonresource/Base.java
======================================================
package junitsingletonresource;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith({Singleton.class})
public abstract class Base extends BaseNestedSingleton
{
    @BeforeAll public static void beforeAll() { System.out.println("Base::beforeAll"); }
    @AfterAll  public static void afterAll () { System.out.println("Base::afterAll" ); }
}

======================================================
junitsingletonresource/Colors.java
======================================================
package junitsingletonresource;

import org.junit.jupiter.api.Test;

public class Colors extends Base
{
    @Test public void blue() { System.out.println("Colors::blue - resource=" + getResource()); }
    @Test public void gold() { System.out.println("Colors::gold - resource=" + getResource()); }
}

======================================================
junitsingletonresource/Numbers.java
======================================================
package junitsingletonresource;

import org.junit.jupiter.api.Test;

public class Numbers extends Base
{
   @Test public void one() { System.out.println("Numbers::one - resource=" + getResource()); }
   @Test public void two() { System.out.println("Numbers::two - resource=" + getResource()); }
   @Test public void tre() { System.out.println("Numbers::tre - resource=" + getResource()); }
}

======================================================
junitsingletonresource/BaseNestedSingleton.java
======================================================
package junitsingletonresource;

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;

import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;

/**
 * My riff on Phillip Gayret's solution from: https://stackoverflow.com/a/51556718/5957643
 */
@ExtendWith({BaseNestedSingleton.NestedSingleton.class})
public abstract class BaseNestedSingleton
{
    protected String getResource() { return NestedSingleton.getResource(); }

    static /*pkg*/ class NestedSingleton implements BeforeAllCallback, ExtensionContext.Store.CloseableResource
    {
        private static boolean initialized = false;
        private static String  resource    = "Tests should never see this value (e.g. could be null)";

        private static String getResource() { return resource; }

        @Override
        public void beforeAll(ExtensionContext context)
        {
            if (!initialized) {
                initialized = true;

                // The following line registers a callback hook when the root test context is shut down

                context.getRoot().getStore(GLOBAL).put(this.getClass().getCanonicalName(), this);

                // Your "before all tests" startup logic goes here, e.g. making connections,
                // loading in-memory DB, waiting for external resources to "warm up", etc.

                System.out.println("NestedSingleton::beforeAll (setting resource)");
                resource    = "Something nice to share!";
           }
        }

        @Override
        public void close() {
            if (!initialized) { throw new RuntimeException("Oops - this should never happen"); }

            // Cleanup the resource if needed, e.g. flush files, gracefully end connections, bury any corpses, etc.

            System.out.println("NestedSingleton::close (clearing resource)");
            resource = null;
        }
    }
}

======================================================
junitsingletonresource/Singleton.java
======================================================
package junitsingletonresource;

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;

/**
 * This is pretty much what Phillip Gayret provided, but with some printing for traceability
 */
public class Singleton implements BeforeAllCallback, ExtensionContext.Store.CloseableResource
{
    private static boolean started = false;

    @Override
    public void beforeAll(ExtensionContext context)
    {
        if (!started) {
            started = true;
            System.out.println("Singleton::Start-Once");
            context.getRoot().getStore(GLOBAL).put("any unique name", this);
        }
    }

    @Override
    public void close() { System.out.println("Singleton::Finish-Once"); }
}

언급URL : https://stackoverflow.com/questions/43282798/in-junit-5-how-to-run-code-before-all-tests

반응형