Capacidad de realización de pruebas e inserción de dependencias - AWS Flow Framework para Java

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

Capacidad de realización de pruebas e inserción de dependencias

El marco se ha diseñado para que sea fácil de utilizar con Inversion of Control (IoC). Las implementaciones de actividades y flujos de trabajo, así como los trabajos y los objetos de contexto que proporciona el marco, se pueden configurar usando contenedores como Spring, y también se pueden crear instancias de ellos. De manera predeterminada, el marco permite integrar el marco Spring. Asimismo, se puede integrar con JUnit para realizar pruebas unitarias de implementaciones de flujos de trabajo y actividades.

Integración con Spring

El paquete com.amazonaws.services.simpleworkflow.flow.spring contiene clases que facilitan el uso del marco Spring en sus aplicaciones. Estas incluyen un ámbito personalizado y trabajos de actividades y flujos de trabajo específicos de Spring: WorkflowScope, SpringWorkflowWorker y SpringActivityWorker. Estas clases le permiten configurar sus implementaciones de flujos de trabajo y actividades, así como trabajos, íntegramente a través de Spring.

WorkflowScope

WorkflowScope es una implementación personalizada del ámbito de Spring que proporciona el marco. Este ámbito le permite crear objetos en el contenedor Spring cuya vida útil está determinada por la de una tarea de decisión. Siempre que el trabajo recibe una nueva tarea de decisión, se crean instancias de los bean en este ámbito. Debe utilizar este ámbito para los bean de implementación de flujos de trabajo, así como cualquier otro bean del que dependa. Los ámbitos singleton y prototype que proporciona Spring no se deben utilizar para los bean de implementación de flujos de trabajo, ya que el marco obliga a crear un nuevo bean para cada tarea de decisión. No hacerlo dará lugar a un comportamiento inesperado.

En el siguiente ejemplo se muestra un fragmento de código de la configuración de Spring que registra WorkflowScope y, a continuación, lo utiliza para configurar un bean de implementación de flujo de trabajo y un bean de cliente de actividad.

<!-- 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>

La línea de configuración <aop:scoped-proxy proxy-target-class="false" />, que se utiliza en la configuración del bean workflowImpl, es necesaria, ya que WorkflowScope no permite usar un proxy mediante CGLIB. Deberá utilizar esta configuración para cualquier bean de WorkflowScope que esté conectado a otro bean de un ámbito distinto. En este caso, el bean workflowImpl necesita estar conectado a un bean de proceso de trabajo de un flujo de trabajo que esté en un ámbito singleton (a continuación podrá ver el ejemplo completo).

Puede obtener más información acerca del uso de ámbitos personalizados en la documentación del marco Spring.

Trabajos específicos de Spring

Cuando use Spring deberá usar las clases de trabajo específicas de Spring que proporciona el marco: SpringWorkflowWorker y SpringActivityWorker. Estos trabajos se pueden insertar en su aplicación usando Spring, tal como se muestra en el siguiente ejemplo. Los trabajos preparados para Spring implementan la interfaz SmartLifecycle de Spring y, de manera predeterminada, comienzan automáticamente a sondear tareas cuando se inicia el contexto de Spring. Puede desactivar esta funcionalidad estableciendo la propiedad disableAutoStartup del trabajo en true.

En el siguiente ejemplo se muestra cómo configurar un decisor. En este ejemplo se utilizan las interfaces MyActivities y MyWorkflow (que no se muestran aquí) y las correspondientes implementaciones (MyActivitiesImpl y MyWorkflowImpl). Las interfaces de cliente generadas y las implementaciones son MyWorkflowClient/MyWorkflowClientImpl y MyActivitiesClient/MyActivitiesClientImpl (que tampoco se muestran aquí).

El cliente de actividades se inserta en la implementación del flujo de trabajo usando la característica de autoconexión de Spring:

public class MyWorkflowImpl implements MyWorkflow { @Autowired public MyActivitiesClient client; @Override public void start() { client.activity1(); } }

La configuración del decisor de Spring es la siguiente:

<?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>

Puesto que la interfaz SpringWorkflowWorker está íntegramente configurada en Spring y comienza automáticamente a sondear cuando se inicia el contexto de Spring, el proceso de host del decisor es sencillo:

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

Asimismo, el proceso de trabajo de la actividad se puede configurar del siguiente modo:

<?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>

El proceso de host del trabajo de la actividad es similar al del decisor:

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

Inserción del contexto de decisión

Si su implementación del flujo de trabajo depende de los objetos de contexto, estos también se pueden insertar fácilmente a través de Spring. El marco registra automáticamente los bean relacionados con el contexto en el contenedor de Spring. Por ejemplo, en el siguiente fragmento de código, los distintos objetos de contexto se han conectado automáticamente. No es necesario realizar ninguna otra configuración de objetos de contexto de 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(); } }

Si desea configurar los objetos de contexto en la implementación del flujo de trabajo mediante la configuración XML de Spring, utilice los nombres de bean declarados en la clase WorkflowScopeBeanNames del paquete com.amazonaws.services.simpleworkflow.flow.spring. Por ejemplo:

<!-- 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>

También puede insertar un DecisionContextProvider en el bean de implementación de flujo de trabajo y utilizarlo para crear el contexto. Esto puede resultar útil si desea proporcionar implementaciones personalizadas del proveedor y contexto.

Inserción de recursos en las actividades

Puede crear instancias de implementaciones de actividades y configurarlas usando un contenedor Inversion of Control (IoC), y también puede insertar fácilmente recursos, como conexiones de bases de datos, si se declaran como propiedades de la clase de implementación de actividad. Estos recursos suelen ir en el ámbito de singleton. Tenga en cuenta que el trabajo de la actividad llama a sus implementaciones en varios subprocesos. Por tanto, debe sincronizarse el acceso a los recursos compartidos.

Integración con JUnit

El marco proporciona extensiones de JUnit e implementaciones de prueba de los objetos de contexto (como, por ejemplo, un reloj de prueba) que se pueden utilizar para escribir y realizar pruebas unitarias con JUnit. Con estas extensiones, puede probar la implementación de su flujo de trabajo insertada y localmente.

Escritura de una prueba unitaria simple

Para escribir pruebas para su flujo de trabajo, utilice la clase WorkflowTest del paquete com.amazonaws.services.simpleworkflow.flow.junit. Esta clase es una implementación MethodRule de JUnit específica del marco de trabajo que ejecuta el código del flujo de trabajo localmente; para ello, llama a las actividades insertadas, en lugar de ir a través de Amazon SWF. Esto le ofrece la flexibilidad de realizar pruebas con la frecuencia que desee, sin generar cargos.

Para utilizar esta clase, solo tiene que declarar un campo de tipo WorkflowTest y anotarlo con @Rule. Antes de realizar las pruebas, cree un nuevo objeto WorkflowTest y añádale sus implementaciones de actividades y flujos de trabajo. Después podrá utilizar la fábrica de clientes de flujo de trabajo generada para crear un cliente y comenzar a ejecutar el flujo de trabajo. El marco también proporciona un ejecutor personalizado de JUnit, FlowBlockJUnit4ClassRunner, que deberá usar para probar su flujo de trabajo. Por ejemplo:

@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); } }

Asimismo, puede especificar una lista de tareas independiente para cada implementación de actividades que añada a WorkflowTest. Por ejemplo, si tiene una implementación de un flujo de trabajo que programa las actividades en listas de tareas específicas para cada host, podrá registrar la actividad en la lista de tareas de cada host:

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

Tenga en cuenta que el código en @Test es asíncrono. Por tanto, deberá utilizar el cliente de flujo de trabajo asíncrono para comenzar una ejecución. Con objeto de verificar los resultados de su prueba, también se proporciona una clase de ayuda AsyncAssert. Esta clase le permite esperar a que las promesas estén disponibles antes de comprobar los resultados. En este ejemplo, esperamos a que esté disponible el resultado de la ejecución del flujo de trabajo antes de comprobar el resultado de la prueba.

Si utiliza Spring, es posible usar la clase SpringWorkflowTest en lugar de la clase WorkflowTest. SpringWorkflowTest proporciona propiedades para configurar fácilmente implementaciones de actividades y flujos de trabajo a través de la configuración de Spring. Al igual que los trabajos específicos de Spring, debe utilizar WorkflowScope para configurar los bean de implementación de flujos de trabajo. Esto garantiza que se cree un nuevo bean de implementación de flujo de trabajo para cada tarea de decisión. Asegúrese de configurar estos bean estableciendo scoped-proxy proxy-target-class en false. Para obtener más información, consulte la sección Integración con Spring. Es posible cambiar el ejemplo de configuración de Spring que se muestra en la sección Integración con Spring para comprobar el flujo de trabajo usando 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>

Simulación de implementaciones de actividades

Puede utilizar implementaciones de actividades reales durante las pruebas, pero si desea realizar una prueba unitaria solo de la lógica del flujo de trabajo, debe hacer una simulación de las actividades. Puede hacerlo proporcionando una implementación simulada de la interfaz de actividades en la clase WorkflowTest. Por ejemplo:

@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); } }

También puede proporcionar una implementación simulada del cliente de actividades e insertarla en la implementación de su flujo de trabajo.

Objetos de contexto de prueba

Si la implementación del flujo de trabajo depende de los objetos de contexto del marco de trabajo (por ejemplo, DecisionContext), no tiene que hacer nada especial para probar dichos flujos de trabajo. Cuando se realiza una prueba con WorkflowTest, este inserta automáticamente objetos de contexto de prueba. Cuando la implementación del flujo de trabajo acceda a los objetos de contexto, por ejemplo, mediante DecisionContextProviderImpl, se obtendrá la implementación de prueba. Puede manipular estos objetos de contexto de prueba en el código de prueba (método @Test) para crear casos de prueba interesantes. Por ejemplo, si su flujo de trabajo crea un temporizador, puede hacer que se active llamando al método clockAdvanceSeconds en la clase WorkflowTest para hacer adelantar el reloj. También puede adelantar el reloj para que los temporizadores se activen antes de lo normal usando la propiedad ClockAccelerationCoefficient en WorkflowTest. Por ejemplo, si su flujo de trabajo crea un temporizador para una hora, puede establecer ClockAccelerationCoefficient en 60 para hacer que se active al cabo de un minuto. De forma predeterminada, ClockAccelerationCoefficient está establecido en 1.

Para obtener más información acerca de los paquetes com.amazonaws.services.simpleworkflow.flow.test y com.amazonaws.services.simpleworkflow.flow.junit, consulte la documentación de AWS SDK for Java.