[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 สำหรับทดสอบสามารถเขียนได้ ดังนี้

      @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.