本文轉自測試人社群,原文連結:jck28-lucio-junit5并行資料同步 - 學習筆記 - 測試人社群
Synchronization
- 共享資源的同步
- JUnit5 以 @ResourceLock注解的形式為我們提供了這樣的機制。
串行用例
- 可以看到單線程的時候每次測試用例斷言都通過,說明可以正确的拿到對應的值。
并行用例
- 如果換成多線程的時候,可以看到代碼不穩定性,有時斷言通過,有時斷言失敗。
- 這個時候如果想要進行對應的代碼健壯性應該怎樣修改呢??
@ResourceLock
- @ResourceLock相當于Java代碼中的 synchronized 、@Synchronized
- @ResourceLock注解為 測試類 和 測試方法 提供聲明式同步機制。
- @ResourceLock注解有兩個參數
- 一個是String指定唯一辨別共享資源的值
- 資源值可以是 預定義 的或 使用者定義 的
- 一個是ResourceAccessMode指定通路資源的模式
- 通路模式可以是 READ 「隻讀」和 READ_WRITE「讀和寫」
多線程報錯
package com.junit5.synch;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.*;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
@Execution(ExecutionMode.CONCURRENT)
public class ParallelResourceLockTest {
Properties properties;
@BeforeEach
void before(){
properties= new Properties(System.getProperties());
}
@Test
void test01(){
assertNull(System.getProperty("custom.property"));
}
@Test
void test02(){
System.setProperty("custom.property","juni5");
assertEquals("juni5",System.getProperty("custom.property"));
}
@Test
void test03(){
System.setProperty("custom.property","hogwarts");
assertEquals("hogwarts",System.getProperty("custom.property"));
}
}
加鎖
package com.junit5.synch;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.*;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
@Execution(ExecutionMode.CONCURRENT)
public class ParallelResourceLockTest {
Properties properties;
@BeforeEach
void before(){
properties= new Properties(System.getProperties());
}
@Test
@ResourceLock(value = Resources.SYSTEM_PROPERTIES,mode = ResourceAccessMode.READ)
void test01(){
assertNull(System.getProperty("custom.property"));
}
@Test
@ResourceLock(value = Resources.SYSTEM_PROPERTIES,mode = ResourceAccessMode.READ_WRITE)
void test02(){
System.setProperty("custom.property","juni5");
assertEquals("juni5",System.getProperty("custom.property"));
}
@Test
@ResourceLock(value = Resources.SYSTEM_PROPERTIES,mode = ResourceAccessMode.READ_WRITE)
void test03(){
System.setProperty("custom.property","hogwarts");
assertEquals("hogwarts",System.getProperty("custom.property"));
}
}
預定義資源
- Resources.SYSTEM_PROPERTIES
- 表示 Java 的系統屬性。
- Resources.SYSTEM_OUT
- 代表目前程序的标準輸出流。
- Resources.SYSTEM_ERR
- 表示目前程序的标準錯誤流。
- Resources.LOCALE
- 目前 JVM 執行個體的預設語言環境。
- Resources.TIMEZONE
- 目前 JVM 執行個體的預設時區。
自定義資源
- 全局使用者
- 如果是SAME_THREAD,運作測試用例的斷言結果都是正常,但是當使用了并發執行,對應的用例就會報錯。
- 解決:
- 在對應測試方法上添加相關的 @ResourceLock
自定義資源
- 1.自定義類的全限定類名為value值
- 2.如果是沒有值的寫入,對應 mode 為READ「隻可讀」;
- 如果有内容寫入自定義類,對應mode 為 READ_WRITE「既可讀又可寫」
package com.junit5;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class User {
static Map<Integer,String> global_user = new HashMap<>();
public static String get(int id){
return global_user.get(id);
}
public static void add(int id,String user){
global_user.put(id,user);
}
public static void update(int id,String user){
global_user.put(id,user);
}
public static void remove(int id){
global_user.remove(id);
}
public static void clear(){
global_user.clear();
}
public static Collection<String> getUser(){
return global_user.values();
}
}
package com.junit5.synch;
import com.junit5.User;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.*;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.*;
@Execution(ExecutionMode.CONCURRENT)
public class ParallelResourceLock02Test {
public static final String GOLOBAL_USER = "com.junit5.User.user";
@BeforeEach
void before(){
User.clear();
}
@Test
@ResourceLock(value = GOLOBAL_USER,mode = ResourceAccessMode.READ)
void test01(){
System.out.println("test01==>"+User.getUser());
assertTrue(User.getUser().isEmpty());
}
@Test
@ResourceLock(value = GOLOBAL_USER,mode = ResourceAccessMode.READ_WRITE)
void test02(){
User.add(1,"gaoyuanyuan");
System.out.println("test02==>"+User.getUser());
assertEquals("gaoyuanyuan",User.get(1));
}
@Test
@ResourceLock(value = GOLOBAL_USER,mode = ResourceAccessMode.READ_WRITE)
void test03(){
User.update(1,"liushishi");
System.out.println("test03==>"+User.getUser());
assertEquals("liushishi",User.get(1));
}
@Test
@ResourceLock(value = GOLOBAL_USER,mode = ResourceAccessMode.READ_WRITE)
void test04(){
User.add(2,"guanzhiling");
System.out.println("test04==>"+User.getUser());
User.remove(2);
System.out.println("test02==>remove==>"+User.getUser());
assertNull(User.get(2));
}
}
總結
并行測試對應共享資料可以通過 @ResourceLock 來進行資料的同步