

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

# テストの容易性と依存関係の挿入
<a name="test"></a>

**Topics**
+ [Spring との統合](#test.spring)
+ [JUnit との統合](#test.junit)

フレームワークは、制御の反転 (IoC) をフレンドリーに実現する設計になっています。アクティビティ/ワークフロー実装とフレームワーク提供のワーカー/コンテキストオブジェクトは、Spring などのコンテナを使用して設定しインスタンス化できます。追加設定なしで、フレームワークでは Spring フレームワークとの統合を利用できます。さらに、JUnit との統合を利用してワークフロー実装とアクティビティ実装の単体テストを行うこともできます。

## Spring との統合
<a name="test.spring"></a>

com.amazonaws.services.simpleworkflow.flow.spring パッケージに含まれているクラスを使うと、Spring フレームワークをアプリケーションで簡単に使用できます。たとえば、カスタムスコープ、Spring 対応のアクティビティワーカーとワークフローワーカーとして `WorkflowScope`、`SpringWorkflowWorker`、`SpringActivityWorker` が含まれています。これらのクラスでは、Spring を通じてワークフロー/アクティビティ実装、ワークフロー/アクティビティワーカー全体を設定できます。

### WorkflowScope
<a name="test.workflowscope"></a>

`WorkflowScope` は、フレームワークが提供するカスタム Spring スコープです。このスコープで Spring コンテナに作成されるオブジェクトの有効期間は、決定タスクの有効期間にスコープ指定されます。このスコープの Bean は、新しい決定タスクがワーカーで受信されるたびにインスタンス化されます。このスコープをワークフロー実装の Bean とそれが依存する他のすべての Bean に使用する必要があります。ワークフロー実装の Bean には、Spring 提供のシングルトンスコープとプロトタイプスコープを使用しません。これらを使用すると、決定タスクごとに新しい Bean を作成することをフレームワークから要求されます。作成できないと、予期しない動作が発生します。

次の例で示す Spring 設定のスニペットでは、`WorkflowScope` を登録し、これを使用してワークフロー実装の Bean とアクティビティクライアントの Bean を設定します。

```
<!-- register AWS Flow Framework for Java WorkflowScope -->
   <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
      <property name="scopes">
       <map>
         <entry key="workflow">
          <bean class="com.amazonaws.services.simpleworkflow.flow.spring.WorkflowScope" />
         </entry>
       </map>
      </property>
   </bean>

   <!-- activities client -->
   <bean id="activitiesClient" class="aws.flow.sample.MyActivitiesClientImpl" scope="workflow">
   </bean>

   <!-- workflow implementation -->
   <bean id="workflowImpl" class="aws.flow.sample.MyWorkflowImpl" scope="workflow">
      <property name="client" ref="activitiesClient"/>
      <aop:scoped-proxy proxy-target-class="false" />
   </bean>
```

`workflowImpl` Bean の設定で使用されている設定行 `<aop:scoped-proxy proxy-target-class="false" />` は必須です。`WorkflowScope` では、CGLIB を使用したプロキシがサポートされないためです。この設定を、別のスコープの別の Bean にワイヤリングされている `WorkflowScope` のすべての Bean に使用する必要があります。この場合、`workflowImpl` Bean をシングルトンスコープのワークフローワーカー Bean にワイヤリングする必要があります (以下の詳細な例を参照)。

カスタムスコープの詳細については、Spring フレームワークのドキュメントを参照してください。

### Spring 対応のワーカー
<a name="test.springworkers"></a>

Spring を使用する場合は、フレームワークが提供する Spring 対応のワーカークラス (`SpringWorkflowWorker` と `SpringActivityWorker`) を使用してください。これらのワーカーは、次の例に示すように、Spring を使用してアプリケーションに挿入できます。Spring 対応のワーカーは、Spring の `SmartLifecycle` インターフェイスを実装し、Spring コンテキストが初期化されると、デフォルトでタスクのポーリングを自動的に開始します。この機能を無効にするには、ワーカーの `disableAutoStartup` プロパティを `true` に設定します。

次の例は、ディサイダーの設定方法を示しています。この例では、インターフェイスとして `MyActivities` と `MyWorkflow` (非表示)、対応する実装として `MyActivitiesImpl` と `MyWorkflowImpl` を使用しています。生成されるクライアントインターフェイスと実装は、`MyWorkflowClient`/`MyWorkflowClientImpl` と `MyActivitiesClient`/`MyActivitiesClientImpl` (非表示) です。

アクティビティクライアントは、Spring の自動ワイヤリング機能を使用してワークフロー実装に挿入されます。

```
public class MyWorkflowImpl implements MyWorkflow {
   @Autowired
   public MyActivitiesClient client;

   @Override
   public void start() {
      client.activity1();
   }
}
```

ディサイダーの Spring 設定は以下のとおりです。

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:aop="http://www.springframework.org/schema/aop"
   xmlns:context="http://www.springframework.org/schema/context"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
   http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
   http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd">

   <!-- register custom workflow scope -->
   <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
      <property name="scopes">
       <map>
         <entry key="workflow">
          <bean class="com.amazonaws.services.simpleworkflow.flow.spring.WorkflowScope" />
         </entry>
       </map>
      </property>
   </bean>
   <context:annotation-config/>

   <bean id="accesskeys" class="com.amazonaws.auth.BasicAWSCredentials">
      <constructor-arg value="{AWS.Access.ID}"/>
      <constructor-arg value="{AWS.Secret.Key}"/>
   </bean>

   <bean id="clientConfiguration" class="com.amazonaws.ClientConfiguration">
      <property name="socketTimeout" value="70000" />
   </bean>

   <!-- Amazon SWF client -->
   <bean id="swfClient"
      class="com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClient">
      <constructor-arg ref="accesskeys" />
      <constructor-arg ref="clientConfiguration" />
      <property name="endpoint" value="{service.url}" />
   </bean>

   <!-- activities client -->
   <bean id="activitiesClient" class="aws.flow.sample.MyActivitiesClientImpl" scope="workflow">
   </bean>

   <!-- workflow implementation -->
   <bean id="workflowImpl" class="aws.flow.sample.MyWorkflowImpl" scope="workflow">
      <property name="client" ref="activitiesClient"/>
      <aop:scoped-proxy proxy-target-class="false" />
   </bean>

   <!-- workflow worker -->
   <bean id="workflowWorker"
      class="com.amazonaws.services.simpleworkflow.flow.spring.SpringWorkflowWorker">
      <constructor-arg ref="swfClient" />
      <constructor-arg value="domain1" />
      <constructor-arg value="tasklist1" />
      <property name="registerDomain" value="true" />
      <property name="domainRetentionPeriodInDays" value="1" />
      <property name="workflowImplementations">
         <list>
            <ref bean="workflowImpl" />
         </list>
      </property>
   </bean>
</beans>
```

`SpringWorkflowWorker` は Spring で完全に設定され、Spring コンテキストが初期化されると自動的にポーリングを開始するため、ディサイダーのホストプロセスはシンプルです。

```
public class WorkflowHost {
   public static void main(String[] args){
      ApplicationContext context
          = new FileSystemXmlApplicationContext("resources/spring/WorkflowHostBean.xml");
      System.out.println("Workflow worker started");
   }
}
```

同様に、アクティビティワーカーは以下のように設定できます。

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:aop="http://www.springframework.org/schema/aop"
   xmlns:context="http://www.springframework.org/schema/context"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
   http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
   http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd">

   <!-- register custom scope -->
   <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
      <property name="scopes">
         <map>
            <entry key="workflow">
               <bean
                  class="com.amazonaws.services.simpleworkflow.flow.spring.WorkflowScope" />
            </entry>
         </map>
      </property>
   </bean>

   <bean id="accesskeys" class="com.amazonaws.auth.BasicAWSCredentials">
      <constructor-arg value="{AWS.Access.ID}"/>
      <constructor-arg value="{AWS.Secret.Key}"/>
   </bean>

   <bean id="clientConfiguration" class="com.amazonaws.ClientConfiguration">
      <property name="socketTimeout" value="70000" />
   </bean>

   <!-- Amazon SWF client -->
   <bean id="swfClient"
      class="com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClient">
      <constructor-arg ref="accesskeys" />
      <constructor-arg ref="clientConfiguration" />
      <property name="endpoint" value="{service.url}" />
   </bean>

   <!-- activities impl -->
   <bean name="activitiesImpl" class="asadj.spring.test.MyActivitiesImpl">
   </bean>

   <!-- activity worker -->
   <bean id="activityWorker"
      class="com.amazonaws.services.simpleworkflow.flow.spring.SpringActivityWorker">
      <constructor-arg ref="swfClient" />
      <constructor-arg value="domain1" />
      <constructor-arg value="tasklist1" />
      <property name="registerDomain" value="true" />
      <property name="domainRetentionPeriodInDays" value="1" />
      <property name="activitiesImplementations">
         <list>
            <ref bean="activitiesImpl" />
         </list>
      </property>
   </bean>
</beans>
```

アクティビティワーカーのホストプロセスはディサイダーに似ています。

```
public class ActivityHost {
   public static void main(String[] args) {
      ApplicationContext context = new FileSystemXmlApplicationContext(
      "resources/spring/ActivityHostBean.xml");
      System.out.println("Activity worker started");
   }
}
```

### 決定コンテキストの挿入
<a name="test.injectdecision"></a>

ワークフロー実装がコンテキストオブジェクトに依存する場合、これらも Spring を通じて簡単に挿入できます。フレームワークでは、コンテキスト関連の Bean を Spring コンテナに自動的に登録します。たとえば、次のスニペットでは、さまざまなコンテキストオブジェクトが自動ワイヤリングされています。コンテキストオブジェクトの他の Spring 設定は不要です。

```
public class MyWorkflowImpl implements MyWorkflow {
   @Autowired
   public MyActivitiesClient client;
   @Autowired
   public WorkflowClock clock;
   @Autowired
   public DecisionContext dcContext;
   @Autowired
   public GenericActivityClient activityClient;
   @Autowired
   public GenericWorkflowClient workflowClient;
   @Autowired
   public WorkflowContext wfContext;
   @Override
   public void start() {
      client.activity1();
   }
}
```

Spring XML 設定を通じてワークフロー実装でコンテキストオブジェクトを設定する場合は、com.amazonaws.services.simpleworkflow.flow.spring パッケージの `WorkflowScopeBeanNames` クラスに宣言されている Bean 名を使用します。例:

```
<!-- workflow implementation -->
<bean id="workflowImpl" class="asadj.spring.test.MyWorkflowImpl" scope="workflow">
   <property name="client" ref="activitiesClient"/>
   <property name="clock" ref="workflowClock"/>
   <property name="activityClient" ref="genericActivityClient"/>
   <property name="dcContext" ref="decisionContext"/>
   <property name="workflowClient" ref="genericWorkflowClient"/>
   <property name="wfContext" ref="workflowContext"/>
   <aop:scoped-proxy proxy-target-class="false" />
</bean>
```

または、`DecisionContextProvider` をワークフロー実装の Bean に挿入し、これを使用してコンテキストを作成することもできます。これは、プロバイダーとコンテキストのカスタム実装を提供する場合に便利です。

### アクティビティへのリソースの挿入
<a name="test.injectresource"></a>

制御の反転 (IoC) コンテナを使用してアクティビティ実装をインスタンス化して設定し、データベース接続などのリソースを簡単に挿入できます。そのためには、アクティビティ実装クラスのプロパティとしてリソースを宣言します。通常、このようなリソースはシングルトンとしてスコープ指定されます。アクティビティ実装は、複数のスレッドでアクティビティワーカーから呼び出されることに注意してください。そのため、共有リソースへのアクセスは同期する必要があります。

## JUnit との統合
<a name="test.junit"></a>

フレームワークでは、JUnit 拡張とコンテキストオブジェクトのテスト実装 (テストクロックなど) を提供します。これらを使用して JUnit で単体テストを記述して実行できます。これらの拡張を使用して、ワークフロー実装のインラインテストをローカルで実行できます。

### シンプルな単体テストの記述
<a name="test.junit.simple"></a>

 ワークフローのテストを記述するには、com.amazonaws.services.simpleworkflow.flow.junit パッケージの `WorkflowTest` クラスを使用します。このクラスは、フレームワーク固有の JUnit `MethodRule` 実装であり、ワークフローコードをローカルで実行し、Amazon SWF を経ることなく、アクティビティをインラインで呼び出します。これにより、料金を発生させることなく、テストを必要なだけ何回でも実行できます。

このクラスを使用するには、`WorkflowTest` 型のフィールドを宣言し、これに `@Rule` 注釈を設定します。テストを実行する前に、新しい `WorkflowTest` オブジェクトを作成し、これにアクティビティ実装とワークフロー実装を追加します。次に、生成されたワークフロークライアントファクトリを使用してクライアントを作成し、ワークフローの実行を開始できます。フレームワークに用意されているカスタム JUnit ランナーの `FlowBlockJUnit4ClassRunner` もワークフローテストに使用する必要があります。例: 

```
@RunWith(FlowBlockJUnit4ClassRunner.class)
public class BookingWorkflowTest {

    @Rule
    public WorkflowTest workflowTest = new WorkflowTest();

    List<String> trace;

    private BookingWorkflowClientFactory workflowFactory
         = new BookingWorkflowClientFactoryImpl();

    @Before
    public void setUp() throws Exception {
        trace = new ArrayList<String>();
        // Register activity implementation to be used during test run
        BookingActivities activities = new BookingActivitiesImpl(trace);
        workflowTest.addActivitiesImplementation(activities);
        workflowTest.addWorkflowImplementationType(BookingWorkflowImpl.class);
    }

    @After
    public void tearDown() throws Exception {
        trace = null;
    }

    @Test
    public void testReserveBoth() {
        BookingWorkflowClient workflow = workflowFactory.getClient();
        Promise<Void> booked = workflow.makeBooking(123, 345, true, true);
        List<String> expected = new ArrayList<String>();
        expected.add("reserveCar-123");
        expected.add("reserveAirline-123");
        expected.add("sendConfirmation-345");
        AsyncAssert.assertEqualsWaitFor("invalid booking", expected, trace, booked);
    }
}
```

また、`WorkflowTest` に追加するアクティビティ実装ごとに個別のタスクリストを指定することもできます。たとえば、ワークフロー実装でアクティビティをホスト固有のタスクリストにスケジュールする場合、アクティビティを各ホストのタスクリストに登録できます。

```
for (int i = 0; i < 10; i++) {
    String hostname = "host" + i;
    workflowTest.addActivitiesImplementation(hostname,
                                             new ImageProcessingActivities(hostname));
}
```

`@Test` のコードは非同期であることに注意してください。したがって、実行を開始するには非同期ワークフロークライアントを使用する必要があります。テストの結果を検証するために、`AsyncAssert` ヘルプクラスも用意されています。このクラスでは、Promise が準備完了になるまで待った上で、結果を検証できます。この例では、ワークフロー実行の結果が準備完了になるまで待った上で、テストの出力を検証します。

Spring を使用している場合は、`WorkflowTest` クラスの代わりに `SpringWorkflowTest` クラスを使用できます。`SpringWorkflowTest`​ では、Spring 構成を介してアクティビティとワークフローを簡単に実装できるプロパティを提供します。Spring 対応のワーカーと同様に、`WorkflowScope` を使用してワークフロー実装の Bean を設定する必要があります。これにより、決定タスクごとに新しいワークフロー実装 Bean が作成されます。これらの Bean を設定する場合は、scoped-proxy proxy-target-class 設定を必ず `false` に設定してください。詳細については、「Spring Integration」(Spring との統合) セクションを参照してください。「Spring との統合」セクションに示されている Spring 設定例を変更し、`SpringWorkflowTest` を使用してワークフローをテストできます。

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans ht
tp://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframe
work.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">

  <!-- register custom workflow scope -->
  <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
      <map>
        <entry key="workflow">
          <bean
            class="com.amazonaws.services.simpleworkflow.flow.spring.WorkflowScope" />
        </entry>
      </map>
    </property>
  </bean>
  <context:annotation-config />
  <bean id="accesskeys" class="com.amazonaws.auth.BasicAWSCredentials">
    <constructor-arg value="{AWS.Access.ID}" />
    <constructor-arg value="{AWS.Secret.Key}" />
  </bean>
  <bean id="clientConfiguration" class="com.amazonaws.ClientConfiguration">
    <property name="socketTimeout" value="70000" />
  </bean>

  <!-- Amazon SWF client -->
  <bean id="swfClient"
    class="com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClient">
    <constructor-arg ref="accesskeys" />
    <constructor-arg ref="clientConfiguration" />
    <property name="endpoint" value="{service.url}" />
  </bean>

  <!-- activities client -->
  <bean id="activitiesClient" class="aws.flow.sample.MyActivitiesClientImpl"
    scope="workflow">
  </bean>

  <!-- workflow implementation -->
  <bean id="workflowImpl" class="aws.flow.sample.MyWorkflowImpl"
    scope="workflow">
    <property name="client" ref="activitiesClient" />
    <aop:scoped-proxy proxy-target-class="false" />
  </bean>

  <!-- WorkflowTest -->
  <bean id="workflowTest"
    class="com.amazonaws.services.simpleworkflow.flow.junit.spring.SpringWorkflowTest">
    <property name="workflowImplementations">
      <list>
        <ref bean="workflowImpl" />
      </list>
    </property>
    <property name="taskListActivitiesImplementationMap">
      <map>
        <entry>
          <key>
            <value>list1</value>
          </key>
          <ref bean="activitiesImplHost1" />
        </entry>
      </map>
    </property>
  </bean>
</beans>
```

#### アクティビティ実装のモッキング
<a name="test.junit.mockactivity"></a>

テストでは実際のアクティビティ実装を使用できますが、ワークフローロジックの単体テストのみを行う場合は、モックアクティビティを使用してください。これを行うには、アクティビティインターフェイスのモック実装を `WorkflowTest` クラスに提供します。例: 

```
@RunWith(FlowBlockJUnit4ClassRunner.class)
public class BookingWorkflowTest {

    @Rule
    public WorkflowTest workflowTest = new WorkflowTest();

    List<String> trace;

    private BookingWorkflowClientFactory workflowFactory
         = new BookingWorkflowClientFactoryImpl();

    @Before
    public void setUp() throws Exception {
        trace = new ArrayList<String>();
        // Create and register mock activity implementation to be used during test run
        BookingActivities activities = new BookingActivities() {

            @Override
            public void sendConfirmationActivity(int customerId) {
                trace.add("sendConfirmation-" + customerId);
            }

            @Override
            public void reserveCar(int requestId) {
                trace.add("reserveCar-" + requestId);
            }

            @Override
            public void reserveAirline(int requestId) {
                trace.add("reserveAirline-" + requestId);
            }
        };
        workflowTest.addActivitiesImplementation(activities);
        workflowTest.addWorkflowImplementationType(BookingWorkflowImpl.class);
    }

    @After
    public void tearDown() throws Exception {
        trace = null;
    }

    @Test
    public void testReserveBoth() {
        BookingWorkflowClient workflow = workflowFactory.getClient();
        Promise<Void> booked = workflow.makeBooking(123, 345, true, true);
        List<String> expected = new ArrayList<String>();
        expected.add("reserveCar-123");
        expected.add("reserveAirline-123");
        expected.add("sendConfirmation-345");
        AsyncAssert.assertEqualsWaitFor("invalid booking", expected, trace, booked);
    }
}
```

または、アクティビティクライアントのモック実装を提供し、これをワークフロー実装に挿入することもできます。

### テストコンテキストオブジェクト
<a name="test.junit.objects"></a>

ワークフロー実装がフレームワークのコンテキストオブジェクト (`DecisionContext` など) に依存している場合、このようなワークフローをテストするには特に何も行う必要がありません。`WorkflowTest` を通じてテストを実行すると、テストコンテキストオブジェクトが自動的に挿入されます。ワークフロー実装がコンテキストオブジェクト (`DecisionContextProviderImpl` を使用するなど) にアクセスすると、テスト実装が取得されます。これらのテストコンテキストオブジェクトをテストコード (`@Test` メソッド) で操作して有益なテストケースを作成できます。例えば、ワークフローでタイマーを作成する場合、`WorkflowTest` クラスで `clockAdvanceSeconds` メソッドを呼び出すことでタイマーを始動し、クロックの時間を進めることができます。また、`WorkflowTest` の `ClockAccelerationCoefficient` プロパティを使用してクロックを加速させ、通常よりも早くタイマーを始動することもできます。たとえば、ワークフローで 1 時間のタイマーを作成する場合、`ClockAccelerationCoefficient` を 60 に設定してタイマーを 1 分で始動できます。`ClockAccelerationCoefficient` は、デフォルトで「1」に設定されます。

com.amazonaws.services.simpleworkflow.flow.test パッケージと com.amazonaws.services.simpleworkflow.flow.junit パッケージの詳細については、 AWS SDK for Java のドキュメントを参照してください。