[BPMN] Activiti สร้าง Unit Test

หลงจาก Blog ตอนที่แล้ว ที่ได้สร้าง Process ของการขอเบิกเงินไปแล้ว ตอนนี้เราต้องมา Proof ก่อนว่า Process ที่ทำนั้น มันใช้งานได้จริงๆ พร้อมที่นำไป Deploy ขึ้น Activiti Server ครับ โดยมีขั้นตอน ดังนี้

Step 1 : กำหนดโครงสร้างไฟล์ที่เกี่ยวข้องกันก่อน

<Project Name>
|-src
  |-main
  | |-java [1]
  | |-resources [2]
  |   |-diagrams
  |     |-Test.bpmn
  |-test
    |-java [3]
    | |-ProcessTestApproveBugetProcess.java
    |-resources [4]
      |-Test.bpmn
      |-activiti.cfg.xml

Step 2 : สร้างไฟล์ Config ที่เกี่ยวข้อง

  • สร้างไฟล์แปลง Activiti Project ให้เป็น Maven Project ซึ่งมีไฟล์ pom.xml เพื่อเก็บ Dependency ต่างๆไว้ โดยสำหรับการ Test BPMN ต้องใช้ Dependency ที่จำเป็น ดังนี้
    • activiti-engine : สำหรับ Execute BPMN
    • JUnit : สำหรับทำ Unit Test
    • MySQL Connector/J : ใช้เชื่อมต่อ Database ของ activiti-engine
    • spring framework [Optional] : ในกรณีที่ต้องการใช้ Activiti กับ Spring
    • และสุดท้าย หน้าตาของไฟล์ pom.xml มี Dependency ดังนี้ครับ
  • สร้างไฟล์ activiti.cfg.xml โดยมีการกำหนดค่า ดังนี้

Step 3 : เอาไฟล์ Process มาใส่

  • เอาไฟล์ Process ที่ทำไว้ จาก Blog ตอนที่แล้วจะเป็นไฟล์ Test.bpmn เอามาวางไว้ที่ Path .../src/test/resources/
  • หรือ อาจจะอ้างไปที่ Path ของ project เลยก็ได้ครับ เช่น
private static String filename = "D:\\BFMN\\Workspace\\LearnBPMN\\src\\main\\resources\\diagrams\\Test.bpmn";

Step 4 : สร้าง Unit Test ขึ้นครับ

ตั้งโครงใหม่ ให้มันเข้ากับ Activiti 6.0 และ JUnit 5 กันก่อนครับ โดยใช้ Test แบ่งเป็นช่วงๆ ดังนี้

📌 ส่วน @BeforeAll - ทำงานตอน Execute Test ครั้งแรก โดยสร้าง ActivitiRule ขึ้นมา เพื่อเตรียม Activiti-Engine ให้พร้อม

public static ActivitiRule activitiRule;
public static ProcessInstance processInstance;
	
@BeforeAll
public static void init(){
   System.out.println("Before All init() method called");
   ProcessEngine processEngine = ProcessEngineConfiguration.createStandaloneInMemProcessEngineConfiguration()
	    														.buildProcessEngine(); 
   activitiRule = new ActivitiRule(processEngine);
}

📌 ส่วน @BeforeEach และ @AfterEach เป็นส่วนที่ทำงานก่อน และหลัง Test Case แต่ละอัน

  • @BeforeEach - ในที่นี้เป็นการ Deploy Process ที่เพิ่งสร้าง ให้ Activiti-Engine รู้จัก
@BeforeEach
public void DeployProcess(){
    try {
            System.out.println("=======================================");
            System.out.println("Deploy Process");
            RepositoryService repositoryService = activitiRule.getRepositoryService();
            repositoryService.createDeployment().addInputStream("Test.bpmn", new FileInputStream(filename)).deploy();
            RuntimeService runtimeService = activitiRule.getRuntimeService();
            Map<String, Object> variableMap = new HashMap<String, Object>();
            variableMap.put("budgetAmount", 100000.00);
            variableMap.put("Approved", false);
            processInstance = runtimeService.startProcessInstanceByKey("ApproveBudgetProcess", variableMap);
    } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
	}
}
  • @AfterEach - ในที่นี้เป็นการ ลบ หรือ Clear Process ที่เพิ่ง Deploy ออกจาก Activiti-Engine รู้จัก
@AfterEach
public void RemoveProcess(){
	try {
		RepositoryService repositoryService = activitiRule.getRepositoryService();
		String deploymentID = repositoryService.createDeploymentQuery().singleResult().getId();
	
		repositoryService.deleteDeployment(deploymentID, true);
		Deployment deployment = repositoryService.createDeploymentQuery()
												 .singleResult();
		assertNull(deployment);
		ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
															   .singleResult();
		assertNull(processDefinition);
			
		RuntimeService runtimeService = activitiRule.getRuntimeService();
		processInstance = runtimeService.createProcessInstanceQuery()
										.singleResult();
		assertNull(processInstance);
		
		System.out.println("Remove Process");
		System.out.println("=======================================");
	} catch (Exception e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
}

📌 ส่วน @Test เป็น Test Case ที่เตรียมไว้ ซึ่งมี 5 Test Case ดังนี้

  • TC1 test1ProcessIsStart - บอกว่า Process ที่ Deploy ไปพร้อมใช้งาน หรือไม่ ?
@Test
public void test1ProcessIsStart() throws Exception {
	System.out.println("Execute - test1ProcessIsStart");
	assertNotNull(processInstance.getId());
	System.out.println("id " + processInstance.getId() + " " + processInstance.getProcessDefinitionId());
}
  • TC2 test2AssignValueInVariableBudgetAmount - ตอนที่สร้าง Process ขึ้นมา เราได้ยัดค่าตัวแปร BudgetAmount ลงไป โดยใน Test Case ดูจะดูว่า มันสามารถดึงค่าที่ได้ Pass เข้าไปได้ หรือไม่
@Test
public void test2AssignValueInVariableBudgetAmount() throws Exception {
	System.out.println("Execute - test2AssignValueInVariableBudgetAmount");
	String processInstanceID = processInstance.getId();
	
	RuntimeService runtimeService = activitiRule.getRuntimeService();
	Double budgetAmount = (Double) runtimeService.getVariable(processInstanceID, "budgetAmount");
	assertNotNull(budgetAmount);
	assertEquals(Double.valueOf(100000.00), budgetAmount);
	System.out.println("Actual Value of budgetAmount: " + budgetAmount);
}
  • TC3 test3AssignValueInVariableApproved - ตอนที่สร้าง Process ขึ้นมา เราได้ยัดค่าตัวแปร Approved ลงไป โดยใน Test Case ดูจะดูว่า มันสามารถดึงค่าที่ได้ Pass เข้าไปได้ หรือไม่
@Test 
public void test3AssignValueInVariableApproved() throws Exception{
	System.out.println("Execute - test3AssignValueInVariableApproved");
	String processInstanceID = processInstance.getId();
	
	RuntimeService runtimeService = activitiRule.getRuntimeService();
	Boolean Approved = (boolean)runtimeService.getVariable(processInstanceID, "Approved");
	assertNotNull(Approved);
	assertEquals(false, Approved);
}
  • TC4 test4WhenEmployeeNeedBudgetMoreThan1K - ทดสอบตาม Scenario ที่ได้กำหนดไว้
    โดย Code สำหรับทดสอบสามารถเขียนได้ ดังนี้
test4WhenEmployeeNeedBudgetMoreThan1K flow
@Test
public void test4WhenEmployeeNeedBudgetMoreThan1K()
{
	System.out.println("Execute - test4WhenEmployeeNeedBudgetMoreThan1K");
	ProcessDefinition definition = activitiRule.getRepositoryService().createProcessDefinitionQuery().processDefinitionKey("ApproveBudgetProcess").singleResult();
	assertNotNull(definition);
	
	/*
	 * ===============================================
	 * User Task : "Employee Request Budget"
	 * ===============================================
	 */
	TaskService taskService = activitiRule.getTaskService();
	//https://community.alfresco.com/thread/217375-current-task-of-process
	Task task =taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
	assertNotNull(task);
	assertEquals("InputBudget", task.getTaskDefinitionKey());
	System.out.println("InputBudget: " + task.getTaskDefinitionKey());
	
	FormService formService = activitiRule.getFormService();
	//List formList = formService.getStartFormData(definition.getId()).getFormProperties();
	List formList = formService.getTaskFormData(task.getId()).getFormProperties();
	assertEquals(1, formList.size());
	System.out.println("Form Count: " + formList.size());
	
	//ใส่ค่าลงใน Form
	Map<String, String> formProperties = new HashMap<String, String>();
	formProperties.put("budgetAmount", "10000.00");
	
	//https://community.alfresco.com/thread/224087-exclusive-gateway-and-error-while-evaluating-expression-errors
	formService.submitTaskFormData(task.getId(), formProperties);
	
	/*
	 * ===============================================
	 * User Task : "Manager Review / Approve"
	 * ===============================================
	 */
	//Next Task รอ Approve
	task =taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
	assertNotNull(task);
	assertEquals("MangerReviewApproved", task.getTaskDefinitionKey());
	
	//Manager OK
	formList = formService.getTaskFormData(task.getId()).getFormProperties();
	assertEquals(1, formList.size());
	System.out.println("Form Count: " + formList.size());
	
	formProperties = new HashMap<String, String>();
	formProperties.put("Approved", "true");
	
	formService.submitTaskFormData(task.getId(), formProperties);
	
	/*
	 * ===============================================
	 * User Task : "Accountant Acknowledge"
	 * ===============================================
	 */
	task =taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
	assertNotNull(task);
	assertEquals("AccountantAck", task.getTaskDefinitionKey());
}
  • TC5 test5WhenEmployeeNeedBudgetLessThanOrEqual1K - ทดสอบตาม Scenario ที่ได้กำหนดไว้ โดย Code สำหรับทดสอบสามารถเขียนได้ ดังนี้
@Test
public void test5WhenEmployeeNeedBudgetLessThanOrEqual1K()
{
	System.out.println("Execute - test5WhenEmployeeNeedBudgetLessThanOrEqual1K");
	ProcessDefinition definition = activitiRule.getRepositoryService().createProcessDefinitionQuery().processDefinitionKey("ApproveBudgetProcess").singleResult();
	assertNotNull(definition);
		
	/*
	 * ===============================================
	 * User Task : "Employee Request Budget"
	 * ===============================================
	 */
	TaskService taskService = activitiRule.getTaskService();
	//https://community.alfresco.com/thread/217375-current-task-of-process
	Task task =taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
	assertNotNull(task);
	assertEquals("InputBudget", task.getTaskDefinitionKey());
	System.out.println("InputBudget: " + task.getTaskDefinitionKey());
	
	FormService formService = activitiRule.getFormService();
	List formList = formService.getTaskFormData(task.getId()).getFormProperties();
	assertEquals(1, formList.size());
	System.out.println("Form Count: " + formList.size());
	
	//ใส่ค่าลงใน Form
	Map<String, String> formProperties = new HashMap<String, String>();
	formProperties.put("budgetAmount", "992.50");
	
	formService.submitTaskFormData(task.getId(), formProperties);
	
	/*
	 * ===============================================
	 * User Task : "Accountant Acknowledge"
	 * ===============================================
	 */
	task =taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
    assertNotNull(task);
	assertEquals("AccountantAck", task.getTaskDefinitionKey());
}

Step 5 : ทดสอบ Run Unit Test

  • คลิกขวาที่ UnitTest เลย ชื้อไฟล์ จากนั้น Run As >> JUnit ซึ่งทำเขียน Test ถูกต้อง ได้ผลลัพธ์ ดังนี้

ปัญหาที่เกิด

  • ทดสอบ Run Test แล้วเจอ Error java.lang.NoClassDefFoundError สามารถแก้ไขโดยเพิ่ม JUnit ที่ Java Build Path ดังรูป
  • ตอนนี้ตัว Activiti Framework ยังไม่รองรับ JUnit 5 นะครับ วิธีที่ผมแก้ มันแก้แบบลูกทุ่งไปก่อน เพราะ ตัว Class AcitiviRule มันช่วยทำให้ Test ได้ง่ายขึ้น
  • ลำดับของการ Execute Test ของ JUnit มันมั่วมากครับ


Discover more from naiwaen@DebuggingSoft

Subscribe to get the latest posts sent to your email.