자바 8용 로컬 단위 테스트

코드 작성 후 단위 테스트를 사용하면 코드의 품질을 확인할 수 있으며 개발을 진행하면서 개발 프로세스를 개선하는 데도 단위 테스트를 사용할 수 있습니다. 애플리케이션 개발을 마친 후에 테스트를 작성하는 대신 개발을 진행하면서 테스트를 작성하는 것이 좋습니다. 그러면 작고 유지관리 및 재사용이 가능한 코드 단위를 설계하는 데 도움이 됩니다. 또한 코드를 완벽하고 신속하게 테스트하기도 더 쉽습니다.

로컬 단위 테스트를 수행할 때는 원격 구성요소를 사용하지 않고 자체 개발 환경 내에서 테스트를 실행합니다. App Engine에서는 데이터 저장소와 기타 App Engine 서비스의 로컬 구현을 사용하는 테스트 유틸리티를 제공합니다. 따라서 코드를 App Engine에 배포하지 않고 서비스 스텁을 사용하여 코드에서 이러한 서비스를 사용하는 방식을 로컬에서 테스트해 볼 수 있습니다.

서비스 스텁은 서비스의 동작을 시뮬레이션하는 수단입니다. 예를 들어 Datastore 및 Memcache 테스트 작성에 표시된 데이터 저장소 서비스 스텁을 사용하면 실제 데이터 저장소에 어떠한 요청도 하지 않고 데이터 저장소 코드를 테스트할 수 있습니다. 데이터 저장소 단위 테스트 중에 저장된 모든 항목은 데이터 저장소가 아니라 메모리에 보관되었다가 테스트 실행 후에 삭제됩니다. 데이터 저장소 자체에 대한 어떠한 종속성도 없이 소규모 테스트를 빠르게 실행할 수 있습니다.

이 문서에서는 테스트 프레임워크 설정에 대한 정보를 제공한 다음, 여러 로컬 App Engine 서비스에 대한 단위 테스트를 작성하는 방법을 설명합니다.

테스트 프레임워크 설정

SDK의 테스트 유틸리티는 특정 프레임워크에 한정되어 있지 않지만 구체적이고 완전한 예시를 위해 이 가이드에서는 예시에서 JUnit을 사용합니다. 테스트 작성을 시작하기 전에 올바른 JUnit 4 JAR을 테스트 클래스 경로에 추가해야 합니다. 이를 완료한 후 간단한 JUnit 테스트를 작성할 수 있습니다.


import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class MyFirstTest {
  @Test
  public void testAddition() {
    assertEquals(4, 2 + 2);
  }
}

Eclipse를 실행 중인 경우 실행할 테스트의 소스 파일을 선택합니다. Run(실행) 메뉴 > Run As(다음으로 실행) > JUnit Test(JUnit 테스트)를 선택합니다. 테스트 결과는 Console 창에 나타납니다.

자바 8 테스트 유틸리티 소개

MyFirstTest는 가장 간단한 테스트 설정을 보여줍니다. App Engine API 또는 로컬 서비스 구현에 의존하지 않는 테스트의 경우 별도의 작업이 필요하지 않을 수도 있습니다. 그러나 테스트 또는 테스트 중인 코드에 이러한 종속 항목이 있는 경우 테스트 클래스 경로에 다음 JAR 파일을 추가합니다.

  • ${SDK_ROOT}/lib/impl/appengine-api.jar
  • ${SDK_ROOT}/lib/impl/appengine-api-stubs.jar
  • ${SDK_ROOT}/lib/appengine-tools-api.jar

이러한 JAR 파일은 런타임 API와 해당 API의 로컬 구현을 테스트에 사용할 수 있도록 합니다.

App Engine 서비스는 실행 환경에서 여러 가지 일을 기대하며 이러한 것들을 설정하려면 상당한 양의 상용구 코드가 필요합니다. 이를 직접 설정하는 대신 com.google.appengine.tools.development.testing 패키지의 유틸리티를 사용할 수 있습니다. 이 패키지를 사용하려면 테스트 클래스 경로에 다음 JAR 파일을 추가합니다.

  • ${SDK_ROOT}/lib/testing/appengine-testing.jar

잠시 시간을 내어 com.google.appengine.tools.development.testing 패키지의 javadoc를 살펴보세요. 이 패키지의 가장 중요한 클래스는 LocalServiceTestHelper로, 필요한 모든 환경 설정을 처리하며 테스트에서 액세스하려는 모든 로컬 서비스에 대한 최상위 수준의 구성 지점을 제공합니다.

특정 로컬 서비스에 액세스하는 테스트를 작성하려면 다음을 수행합니다.

  • 특정 로컬 서비스에 대한 LocalServiceTestConfig 구현으로 LocalServiceTestHelper 인스턴스를 만듭니다.
  • 각 테스트 전에 LocalServiceTestHelper 인스턴스에서 setUp()을 호출하고 각 테스트 후에 tearDown()을 호출합니다.

Datastore 및 Memcache 테스트 작성

다음 예는 datastore 서비스의 사용을 테스트합니다.


import static com.google.appengine.api.datastore.FetchOptions.Builder.withLimit;
import static org.junit.Assert.assertEquals;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class LocalDatastoreTest {

  private final LocalServiceTestHelper helper =
      new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());

  @Before
  public void setUp() {
    helper.setUp();
  }

  @After
  public void tearDown() {
    helper.tearDown();
  }

  // Run this test twice to prove we're not leaking any state across tests.
  private void doTest() {
    DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
    assertEquals(0, ds.prepare(new Query("yam")).countEntities(withLimit(10)));
    ds.put(new Entity("yam"));
    ds.put(new Entity("yam"));
    assertEquals(2, ds.prepare(new Query("yam")).countEntities(withLimit(10)));
  }

  @Test
  public void testInsert1() {
    doTest();
  }

  @Test
  public void testInsert2() {
    doTest();
  }
}

이 예시에서 LocalServiceTestHelper는 모든 로컬 서비스에 공통된 실행 환경 부분을 설정 및 해제하며 LocalDatastoreServiceTestConfig는 로컬 Datastore 서비스에만 해당하는 실행 환경 부분을 설정 및 해제합니다. javadoc에서 설명한 대로 이를 위해서는 모든 데이터를 메모리에 유지하도록(정기적으로 디스크로 내보내는 대신) 로컬 Datastore 서비스를 구성하고 테스트가 끝날 때마다 메모리의 모든 데이터를 완전 삭제해야 합니다. 이는 Datastore 테스트의 기본 동작에 불과하며 원하지 않는 경우 변경할 수 있습니다.

Datastore 대신 Memcache에 액세스하도록 예시 변경

로컬 memcache 서비스에 액세스하는 테스트를 만들려면 위의 코드를 약간만 변경하면 됩니다.

Datastore와 관련된 클래스를 가져오는 대신 Memcache와 관련된 클래스를 가져오면 됩니다. LocalServiceTestHelper는 동일하게 가져와야 합니다.

생성 중인 클래스의 이름을 변경하고 Memcache와 관련되도록 LocalServiceTestHelper의 인스턴스를 변경합니다.

public class LocalMemcacheTest {

  private final LocalServiceTestHelper helper =
      new LocalServiceTestHelper(new LocalMemcacheServiceTestConfig());

마지막으로 Memcache와 관련되도록 테스트의 실제 실행 방식을 변경합니다.

private void doTest() {
  MemcacheService ms = MemcacheServiceFactory.getMemcacheService();
  assertFalse(ms.contains("yar"));
  ms.put("yar", "foo");
  assertTrue(ms.contains("yar"));
}

Datastore 예시와 같이 LocalServiceTestHelper와 서비스별 LocalServiceTestConfig(이 경우 LocalMemcacheServiceTestConfig)는 실행 환경을 관리합니다.

Cloud Datastore 테스트 작성

앱이 Cloud Datastore를 사용하는 경우 eventual consistency에 직면하여 애플리케이션의 동작을 확인하는 테스트를 작성할 수 있습니다. LocalDatastoreServiceTestConfig는 이를 쉽게 하는 옵션을 제공합니다.


import static com.google.appengine.api.datastore.FetchOptions.Builder.withLimit;
import static org.junit.Assert.assertEquals;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class LocalHighRepDatastoreTest {

  // Maximum eventual consistency.
  private final LocalServiceTestHelper helper =
      new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig()
          .setDefaultHighRepJobPolicyUnappliedJobPercentage(100));

  @Before
  public void setUp() {
    helper.setUp();
  }

  @After
  public void tearDown() {
    helper.tearDown();
  }

  @Test
  public void testEventuallyConsistentGlobalQueryResult() {
    DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
    Key ancestor = KeyFactory.createKey("foo", 3);
    ds.put(new Entity("yam", ancestor));
    ds.put(new Entity("yam", ancestor));
    // Global query doesn't see the data.
    assertEquals(0, ds.prepare(new Query("yam")).countEntities(withLimit(10)));
    // Ancestor query does see the data.
    assertEquals(2, ds.prepare(new Query("yam", ancestor)).countEntities(withLimit(10)));
  }
}

적용되지 않은 작업 비율을 100으로 설정하면 로컬 데이터 저장소가 최대한의 최종 일관성 상태에서 작동하게 됩니다. 최대한의 최종 일관성 상태에서는 쓰기가 커밋되지만 항상 적용에 실패하므로 전역(비상위) 쿼리가 지속적으로 변경사항을 확인하지 못하게 됩니다. 물론 이것이 프로덕션 환경에서 실행 시 애플리케이션에서 실제로 확인되는 eventual consistency 수준을 나타내지는 않지만 테스트 목적으로는 로컬 Datastore를 매번 이런 식으로 작동하도록 구성하는 것이 매우 유용합니다.

적용할 수 없는 트랜잭션을 보다 세밀하게 제어하려면 자체 HighRepJobPolicy를 등록하세요.


import static com.google.appengine.api.datastore.FetchOptions.Builder.withLimit;
import static org.junit.Assert.assertEquals;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.dev.HighRepJobPolicy;
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class LocalCustomPolicyHighRepDatastoreTest {
  private static final class CustomHighRepJobPolicy implements HighRepJobPolicy {
    static int newJobCounter = 0;
    static int existingJobCounter = 0;

    @Override
    public boolean shouldApplyNewJob(Key entityGroup) {
      // Every other new job fails to apply.
      return newJobCounter++ % 2 == 0;
    }

    @Override
    public boolean shouldRollForwardExistingJob(Key entityGroup) {
      // Every other existing job fails to apply.
      return existingJobCounter++ % 2 == 0;
    }
  }

  private final LocalServiceTestHelper helper =
      new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig()
          .setAlternateHighRepJobPolicyClass(CustomHighRepJobPolicy.class));

  @Before
  public void setUp() {
    helper.setUp();
  }

  @After
  public void tearDown() {
    helper.tearDown();
  }

  @Test
  public void testEventuallyConsistentGlobalQueryResult() {
    DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
    ds.put(new Entity("yam")); // applies
    ds.put(new Entity("yam")); // does not apply
    // First global query only sees the first Entity.
    assertEquals(1, ds.prepare(new Query("yam")).countEntities(withLimit(10)));
    // Second global query sees both Entities because we "groom" (attempt to
    // apply unapplied jobs) after every query.
    assertEquals(2, ds.prepare(new Query("yam")).countEntities(withLimit(10)));
  }
}

Testing API는 eventual consistency에 직면하여 애플리케이션이 제대로 작동하는지 확인하는 데 유용하지만, 로컬 고성능 복제 읽기 일관성 모델은 프로덕션 고성능 복제 읽기 일관성 모델의 정확한 복제본이 아니라 근사치임에 유의해야 합니다. 로컬 환경에서 적용되지 않은 쓰기가 있는 항목 그룹에 속하는 Entity에 대해 get()을 수행하면 후속 전역 쿼리에서는 적용되지 않은 쓰기의 결과가 항상 표시됩니다. 이는 프로덕션 환경에서는 해당되지 않습니다.

작업 대기열 테스트 작성

로컬 작업 대기열을 사용하는 테스트는 데이터 저장소 및 memcache와 달리 작업 대기열 API가 서비스 상태를 검사하는 기능을 제공하지 않으므로 좀 더 복잡합니다. 예상한 매개변수로 태스크가 예약되었는지 확인하려면 로컬 태스크 큐 자체에 액세스해야 합니다. 이를 위해 com.google.appengine.api.taskqueue.dev.LocalTaskQueue가 필요합니다.


import static org.junit.Assert.assertEquals;

import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.taskqueue.TaskOptions;
import com.google.appengine.api.taskqueue.dev.LocalTaskQueue;
import com.google.appengine.api.taskqueue.dev.QueueStateInfo;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.google.appengine.tools.development.testing.LocalTaskQueueTestConfig;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class TaskQueueTest {

  private final LocalServiceTestHelper helper =
      new LocalServiceTestHelper(new LocalTaskQueueTestConfig());

  @Before
  public void setUp() {
    helper.setUp();
  }

  @After
  public void tearDown() {
    helper.tearDown();
  }

  // Run this test twice to demonstrate we're not leaking state across tests.
  // If we _are_ leaking state across tests we'll get an exception on the
  // second test because there will already be a task with the given name.
  private void doTest() throws InterruptedException {
    QueueFactory.getDefaultQueue().add(TaskOptions.Builder.withTaskName("task29"));
    // Give the task time to execute if tasks are actually enabled (which they
    // aren't, but that's part of the test).
    Thread.sleep(1000);
    LocalTaskQueue ltq = LocalTaskQueueTestConfig.getLocalTaskQueue();
    QueueStateInfo qsi = ltq.getQueueStateInfo().get(QueueFactory.getDefaultQueue().getQueueName());
    assertEquals(1, qsi.getTaskInfo().size());
    assertEquals("task29", qsi.getTaskInfo().get(0).getTaskName());
  }

  @Test
  public void testTaskGetsScheduled1() throws InterruptedException {
    doTest();
  }

  @Test
  public void testTaskGetsScheduled2() throws InterruptedException {
    doTest();
  }
}

LocalTaskqueueTestConfig에 로컬 서비스 인스턴스에 대한 핸들을 요청한 다음 로컬 서비스 자체를 조사하여 태스크가 예상대로 예약되었는지 확인합니다. 모든 LocalServiceTestConfig 구현은 비슷한 메서드를 제공합니다. 항상 필요한 것은 아니지만 필요한 순간에 유용하게 사용할 수 있습니다.

queue.xml 구성 파일 설정

태스크 큐 테스트 라이브러리를 사용하면 LocalTaskQueueTestConfig.setQueueXmlPath 메서드를 통해 LocalServiceTestHelper별로 queue.xml 구성을 원하는 만큼 지정할 수 있습니다. 현재 로컬 개발 서버는 모든 큐의 비율 한도 설정을 무시합니다. 로컬에서는 태스크를 동시에 실행할 수 없습니다.

예를 들어 App Engine 애플리케이션에서 업로드하여 사용할 queue.xml 파일을 프로젝트에서 테스트해야 할 수 있습니다. queue.xml 파일이 표준 위치에 있다고 가정하면 위의 샘플 코드를 다음과 같이 수정하여 src/main/webapp/WEB-INF/queue.xml 파일에 지정된 큐에 대한 테스트 액세스 권한을 부여할 수 있습니다.

private final LocalServiceTestHelper helper =
    new LocalServiceTestHelper(new LocalTaskQueueTestConfig()
        .setQueueXmlPath("src/main/webapp/WEB-INF/queue.xml"));

프로젝트의 파일 구조에 맞게 queue.xml 파일 경로를 수정합니다.

QueueFactory.getQueue 메서드를 사용하여 이름으로 큐에 액세스합니다.

QueueFactory.getQueue("my-queue-name").add(TaskOptions.Builder.withTaskName("task29"));

지연된 태스크 테스트 작성

애플리케이션 코드에서 지연된 작업을 사용하는 경우 자바 테스트 유틸리티를 사용하면 이러한 작업의 결과를 확인하는 통합 테스트를 쉽게 작성할 수 있습니다.


import static org.junit.Assert.assertTrue;

import com.google.appengine.api.taskqueue.DeferredTask;
import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.taskqueue.TaskOptions;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.google.appengine.tools.development.testing.LocalTaskQueueTestConfig;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class DeferredTaskTest {

  // Unlike CountDownLatch, TaskCountDownlatch lets us reset.
  private final LocalTaskQueueTestConfig.TaskCountDownLatch latch =
      new LocalTaskQueueTestConfig.TaskCountDownLatch(1);

  private final LocalServiceTestHelper helper =
      new LocalServiceTestHelper(new LocalTaskQueueTestConfig()
          .setDisableAutoTaskExecution(false)
          .setCallbackClass(LocalTaskQueueTestConfig.DeferredTaskCallback.class)
          .setTaskExecutionLatch(latch));

  private static class MyTask implements DeferredTask {
    private static boolean taskRan = false;

    @Override
    public void run() {
      taskRan = true;
    }
  }

  @Before
  public void setUp() {
    helper.setUp();
  }

  @After
  public void tearDown() {
    MyTask.taskRan = false;
    latch.reset();
    helper.tearDown();
  }

  @Test
  public void testTaskGetsRun() throws InterruptedException {
    QueueFactory.getDefaultQueue().add(
        TaskOptions.Builder.withPayload(new MyTask()));
    assertTrue(latch.await(5, TimeUnit.SECONDS));
    assertTrue(MyTask.taskRan);
  }
}

첫 번째 로컬 태스크 큐 예시와 마찬가지로 LocalTaskqueueTestConfig를 사용하되 이번에는 태스크가 예약되었는지 여부뿐만 아니라 실행되었는지 여부도 손쉽게 확인할 수 있는 몇 가지 추가 인수로 이를 초기화합니다. 여기서는 setDisableAutoTaskExecution(false)을 호출하여 자동으로 태스크를 실행하도록 로컬 태스크 큐에 알립니다. setCallbackClass(LocalTaskQueueTestConfig.DeferredTaskCallback.class)를 호출하여 지연된 태스크를 실행하는 방법을 이해하는 콜백을 사용하도록 로컬 태스크 큐에 알립니다. 마지막으로 setTaskExecutionLatch(latch)를 호출하여 각 태스크가 실행된 후 래치를 감소하도록 로컬 태스크 큐에 알립니다. 이러한 구성을 통해 지연된 태스크를 큐에 추가하고 해당 태스크가 실행될 때까지 기다린 다음 태스크가 실행될 때 예상대로 작동하는지 확인하는 테스트를 작성할 수 있습니다.

로컬 서비스 기능 테스트 작성

기능 테스트는 데이터 저장소, blobstore, memcache 등과 같은 일부 서비스의 상태를 변경하고 해당 서비스에 대해 애플리케이션을 실행하여 애플리케이션이 다른 조건에서 예상대로 응답하는지 확인합니다. 기능 상태는 LocalCapabilitiesServiceTestConfig 클래스를 사용하여 변경할 수 있습니다.

다음 코드 스니펫은 데이터 저장소 서비스의 기능 상태를 사용 중지로 변경한 다음 데이터 저장소 서비스에 대한 테스트를 실행합니다. 필요에 따라 데이터 저장소를 다른 서비스로 대체할 수 있습니다.


import static org.junit.Assert.assertEquals;

import com.google.appengine.api.capabilities.Capability;
import com.google.appengine.api.capabilities.CapabilityStatus;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.tools.development.testing.LocalCapabilitiesServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.google.apphosting.api.ApiProxy;
import org.junit.After;
import org.junit.Test;

public class ShortTest {
  private LocalServiceTestHelper helper;

  @After
  public void tearDown() {
    helper.tearDown();
  }

  @Test(expected = ApiProxy.CapabilityDisabledException.class)
  public void testDisabledDatastore() {
    Capability testOne = new Capability("datastore_v3");
    CapabilityStatus testStatus = CapabilityStatus.DISABLED;
    // Initialize the test configuration.
    LocalCapabilitiesServiceTestConfig config =
        new LocalCapabilitiesServiceTestConfig().setCapabilityStatus(testOne, testStatus);
    helper = new LocalServiceTestHelper(config);
    helper.setUp();
    FetchOptions fo = FetchOptions.Builder.withLimit(10);
    DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
    assertEquals(0, ds.prepare(new Query("yam")).countEntities(fo));
  }
}

샘플 테스트는 먼저 Datastore로 초기화된 Capability 객체를 만든 다음 사용 중지로 설정된 CapabilityStatus 객체를 생성합니다. LocalCapabilitiesServiceTestConfig는 앞서 생성된 CapabilityCapabilityStatus 객체를 사용하여 기능 및 상태 집합으로 생성됩니다.

그런 다음 LocalCapabilitiesServiceTestConfig 객체를 사용하여 LocalServiceHelper가 생성됩니다. 테스트가 설정되었으니 이제 DatastoreService가 만들어지며 테스트가 예상된 결과(이 경우 CapabilityDisabledException)를 생성하는지 확인하기 위해 쿼리가 전송됩니다.

다른 서비스에 대한 테스트 작성

blobstore와 기타 App Engine 서비스에서 테스트 유틸리티를 사용할 수 있습니다. 테스트용 로컬 구현이 있는 모든 서비스 목록은 LocalServiceTestConfig 문서를 참조하세요.

인증 기대치가 있는 테스트 작성

이 예시에서는 UserService를 사용하여 사용자가 로그온했는지 또는 관리자 권한이 있는지 확인하는 논리를 검증하는 테스트를 작성하는 방법을 보여줍니다. 기본 역할인 뷰어, 편집자 또는 소유자이거나 사전 정의된 역할인 App Engine 앱 관리자에 해당하는 모든 사용자는 관리자 권한을 갖습니다.


import static org.junit.Assert.assertTrue;

import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.google.appengine.tools.development.testing.LocalUserServiceTestConfig;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class AuthenticationTest {

  private final LocalServiceTestHelper helper =
      new LocalServiceTestHelper(new LocalUserServiceTestConfig())
          .setEnvIsAdmin(true).setEnvIsLoggedIn(true);

  @Before
  public void setUp() {
    helper.setUp();
  }

  @After
  public void tearDown() {
    helper.tearDown();
  }

  @Test
  public void testIsAdmin() {
    UserService userService = UserServiceFactory.getUserService();
    assertTrue(userService.isUserAdmin());
  }
}

이 예시에서는 LocalUserServiceTestConfigLocalServiceTestHelper를 구성하므로 테스트에서 UserService를 사용할 수 있지만 LocalServiceTestHelper 자체에도 일부 인증 관련 환경 데이터를 구성합니다.

이 예시에서는 OAuthService를 사용할 수 있도록 LocalUserServiceTestConfigLocalServiceTestHelper를 구성합니다.