Testabilitas dan Injeksi Ketergantungan - AWS Flow Framework untuk Java

Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.

Testabilitas dan Injeksi Ketergantungan

Kerangka kerja ini dirancang agar ramah Inversion of Control (IoC). Implementasi aktivitas dan alur kerja serta kerangka kerja yang disediakan pekerja dan objek konteks dapat dikonfigurasi dan dipakai menggunakan kontainer seperti Spring. Di luar kotak, kerangka kerja menyediakan integrasi dengan Kerangka Kerja Spring. Selain itu, integrasi dengan JUnit telah disediakan untuk alur kerja pengujian unit dan implementasi aktivitas.

Integrasi Spring

Paket com.amazonaws.services.simpleworkflow.flow.spring berisi kelas-kelas yang memudahkan penggunaan kerangka kerja Spring dalam aplikasi Anda. Ini mencakup aktivitas dan pekerja alur kerja yang sadar Scope dan Spring kustom: WorkflowScope, SpringWorkflowWorker dan SpringActivityWorker. Kelas-kelas ini memungkinkan Anda untuk mengonfigurasi implementasi alur kerja dan aktivitas Anda serta pekerja sepenuhnya melalui Spring.

WorkflowScope

WorkflowScope adalah implementasi Lingkup Musim Semi khusus yang disediakan oleh kerangka kerja. Lingkup ini memungkinkan Anda membuat objek di kontainer Spring yang masa pakainya dicakup dengan tugas keputusan. Bean dalam lingkup ini dipakai setiap kali tugas keputusan baru diterima oleh pekerja. Anda harus menggunakan lingkup ini untuk bean implementasi alur kerja dan bean lain yang bergantung padanya. Lingkup tunggal dan prototipe yang disediakan Spring tidak boleh digunakan untuk implementasi alur kerja karena kerangka kerja mengharuskan bean baru dibuat untuk setiap tugas keputusan. Kegagalan untuk melakukannya akan menghasilkan perilaku yang tidak terduga.

Contoh berikut menunjukkan cuplikan konfigurasi Spring yang mendaftarkan WorkflowScope lalu menggunakannya untuk mengonfigurasi bean implementasi alur kerja dan bean klien aktivitas.

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

Baris konfigurasi: <aop:scoped-proxy proxy-target-class="false" />, digunakan dalam konfigurasi bean workflowImpl, diperlukan karena WorkflowScope tidak mendukung proksi menggunakan CGLIB. Anda harus menggunakan konfigurasi ini untuk bean apa pun di WorkflowScope yang disambungkan ke bean lain dalam lingkup yang berbeda. Dalam hal ini, bean workflowImpl perlu disambungkan ke bean pekerja alur kerja dalam lingkup tunggal (lihat contoh lengkap di bawah).

Anda dapat mempelajari selengkapnya tentang menggunakan lingkup kustom di dokumentasi Kerangka Kerja Spring.

Pekerja Sadar Spring

Saat menggunakan Spring, Anda harus menggunakan kelas pekerja sadar Spring yang disediakan oleh kerangka kerja: SpringWorkflowWorker dan SpringActivityWorker. Pekerja ini dapat disuntikkan dalam aplikasi Anda menggunakan Spring seperti yang ditunjukkan pada contoh berikutnya. Pekerja sadar Spring menerapkan antarmuka SmartLifecycle Spring dan, secara default, secara otomatis memulai polling untuk tugas ketika konteks Spring diinisialisasi. Anda dapat menonaktifkan fungsi ini dengan menyetel properti disableAutoStartup pekerja ke true.

Contoh berikut menunjukkan cara mengonfigurasi penentu. Contoh ini menggunakan antarmuka MyActivities dan MyWorkflow (tidak ditampilkan di sini) dan implementasi yang sesuai, MyActivitiesImpl dan MyWorkflowImpl. Antarmuka dan implementasi klien yang dihasilkan adalah MyWorkflowClient/MyWorkflowClientImpl and MyActivitiesClient/MyActivitiesClientImpl (juga tidak ditampilkan di sini).

Klien aktivitas disuntikkan dalam implementasi alur kerja menggunakan fitur kabel otomatis Spring:

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

Konfigurasi Spring untuk penentu adalah sebagai berikut:

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

Karena SpringWorkflowWorker sepenuhnya dikonfigurasi di Spring dan secara otomatis memulai polling ketika konteks Spring diinisialisasi, proses host untuk penentu sederhana:

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

Demikian pula, pekerja aktivitas dapat dikonfigurasi sebagai berikut:

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

Proses host pekerja aktivitas mirip dengan penentu:

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

Menyuntikkan Konteks Keputusan

Jika implementasi alur kerja Anda bergantung pada objek konteks, maka Anda dapat dengan mudah menyuntikkannya melalui Spring juga. Kerangka kerja secara otomatis mendaftarkan bean terkait konteks di kontainer Spring. Misalnya, dalam cuplikan berikut, berbagai objek konteks telah disambungkan secara otomatis. Tidak ada konfigurasi Spring lain dari objek konteks yang diperlukan.

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

Jika Anda ingin mengonfigurasi objek konteks dalam implementasi alur kerja melalui konfigurasi Spring XML, gunakan nama bean yang dideklarasikan di kelas WorkflowScopeBeanNames dalam paket com.amazonaws.services.simpleworkflow.flow.spring. Sebagai contoh:

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

Atau, Anda dapat menyuntikkan DecisionContextProvider dalam bean implementasi alur kerja dan menggunakannya untuk membuat konteks. Ini dapat berguna jika Anda ingin memberikan implementasi kustom dari penyedia dan konteks.

Menyuntikkan Sumber Daya dalam Aktivitas

Anda dapat membuat instans dan mengonfigurasi implementasi aktivitas menggunakan kontainer Inversion of Control (IoC) dan dengan mudah menyuntikkan sumber daya seperti koneksi basis data dengan mendeklarasikannya sebagai properti dari kelas implementasi aktivitas. Sumber daya tersebut biasanya akan dicakup sebagai tunggal. Perhatikan bahwa implementasi aktivitas dipanggil oleh pekerja aktivitas di beberapa utas. Oleh karena itu, akses ke sumber daya bersama harus disinkronkan.

Integrasi JUnit

Kerangka kerja ini menyediakan ekstensi JUnit serta implementasi pengujian objek konteks, seperti jam pengujian, yang dapat Anda gunakan untuk menulis dan menjalankan pengujian unit dengan JUnit. Dengan ekstensi ini, Anda dapat menguji implementasi alur kerja Anda secara lokal di barisan.

Menulis Pengujian Unit Sederhana

Untuk menulis pengujian untuk alur kerja Anda, gunakan kelas WorkflowTest dalam paket com.amazonaws.services.simpleworkflow.flow.junit. Kelas ini adalah implementasi MethodRule JUnit khusus kerangka kerja dan menjalankan kode alur kerja Anda secara lokal, memanggil aktivitas di barisan alih-alih melalui Amazon SWF. Ini memberi Anda fleksibilitas untuk menjalankan pengujian sesering yang Anda inginkan tanpa dikenakan biaya apa pun.

Untuk menggunakan kelas ini, cukup deklarasikan bidang jenis WorkflowTest dan beri anotasi dengan anotasi @Rule. Sebelum menjalankan pengujian Anda, buat objek WorkflowTest baru dan tambahkan implementasi aktivitas serta alur kerja Anda ke dalamnya. Anda kemudian dapat menggunakan pabrik klien alur kerja yang dihasilkan untuk membuat klien dan memulai eksekusi alur kerja. Kerangka kerja ini juga menyediakan runner JUnit kustom, FlowBlockJUnit4ClassRunner, yang harus Anda gunakan untuk pengujian alur kerja Anda. Sebagai contoh:

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

Anda juga dapat menentukan daftar tugas terpisah untuk setiap implementasi aktivitas yang Anda tambahkan ke WorkflowTest. Misalnya, jika Anda memiliki implementasi alur kerja yang menjadwalkan aktivitas dalam daftar tugas khusus host, Anda dapat mendaftarkan aktivitas dalam daftar tugas setiap host:

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

Perhatikan bahwa kode dalam @Test asinkron. Oleh karena itu, Anda harus menggunakan klien alur kerja asinkron untuk memulai eksekusi. Untuk memverifikasi hasil tes Anda, kelas bantuan AsyncAssert juga disediakan. Kelas ini memungkinkan Anda menunggu hingga janji siap sebelum memverifikasi hasil. Dalam contoh ini, kami menunggu hasil eksekusi alur kerja siap sebelum memverifikasi hasil pengujian.

Jika Anda menggunakan Spring, maka kelas SpringWorkflowTest dapat digunakan sebagai pengganti kelas WorkflowTest. SpringWorkflowTest menyediakan properti yang dapat Anda gunakan untuk mengonfigurasi aktivitas dan implementasi alur kerja dengan mudah melalui konfigurasi Spring. Sama seperti pekerja sadar Spring, Anda harus menggunakan WorkflowScope untuk mengonfigurasi bean implementasi alur kerja. Ini memastikan bahwa bean implementasi alur kerja baru dibuat untuk setiap tugas keputusan. Pastikan untuk mengonfigurasi bean ini dengan pengaturan kelas target proksi proksi tercakup disetel ke false. Lihat bagian Integrasi Spring untuk lebih detail. Contoh konfigurasi Spring yang ditampilkan di bagian Integrasi Spring dapat diubah untuk menguji alur kerja menggunakan 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>

Meniru Implementasi Aktivitas

Anda dapat menggunakan implementasi aktivitas nyata selama pengujian, tetapi jika Anda ingin pengujian unit hanya logika alur kerja, Anda harus meniru aktivitas tersebut. Ini dapat dilakukan dengan menyediakan implementasi tiruan dari antarmuka aktivitas ke kelas WorkflowTest. Sebagai contoh:

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

Atau, Anda dapat memberikan implementasi tiruan dari klien aktivitas dan menyuntikkannya ke dalam implementasi alur kerja Anda.

Objek konteks pengujian

Jika implementasi alur kerja Anda bergantung pada objek konteks kerangka kerja—misalnya, DecisionContext—Anda tidak perlu melakukan sesuatu yang khusus untuk menguji alur kerja tersebut. Saat pengujian dijalankan melalui WorkflowTest, ia secara otomatis menyuntikkan objek konteks pengujian. Saat implementasi alur kerja Anda mengakses objek konteks—misalnya, menggunakan DecisionContextProviderImpl—ia akan mendapatkan implementasi pengujian. Anda dapat memanipulasi objek konteks pengujian ini dalam kode pengujian (metode @Test) Anda untuk membuat kasus pengujian yang menarik. Misalnya, jika alur kerja Anda membuat penghitung waktu, Anda bisa mengaktifkan penghitung dengan memanggil metode clockAdvanceSeconds di kelas WorkflowTest untuk menggerakkan jam maju tepat waktu. Anda juga dapat mempercepat jam untuk mengaktifkan penghitung lebih awal dari biasanya menggunakan properti ClockAccelerationCoefficient di WorkflowTest. Misalnya, jika alur kerja Anda membuat penghitung waktu selama satu jam, Anda dapat menyetel ClockAccelerationCoefficient ke 60 untuk mengaktifkan penghitung waktu dalam satu menit. Secara default, ClockAccelerationCoefficient disetel ke 1.

Untuk detail selengkapnya tentang paket com.amazonaws.services.simpleworkflow.flow.test dan com.amazonaws.services.simpleworkflow.flow.junit, lihat dokumentasi AWS SDK for Java.