This article is transferred from the tester community, the original link: jck28-lucio-junit5 parallel data synchronization - study notes - tester community
Synchronization
- Synchronization of shared resources
- JUnit5 provides us with such a mechanism in the form of @ResourceLock annotations.
Serial use cases
- It can be seen that when a single thread is used, the assertion of each test case is passed, indicating that the corresponding value can be obtained correctly.
Parallel use cases
- If you switch to multithreading, you can see that the code is unstable, sometimes assertion passes, sometimes assertions fail.
- At this time, if you want to make the corresponding code robustness, how should you modify it?
@ResourceLock
- @ResourceLock equivalent to synchronized and @Synchronized in Java code
- @ResourceLock annotations provide a declarative synchronization mechanism for test classes and test methods.
- @ResourceLock annotation has two parameters
- One is that the String specifies a value that uniquely identifies the shared resource
- Resource values can be predefined or user-defined
- One is ResourceAccessMode, which specifies the mode in which resources are accessed
- Access modes can be READ and READ_WRITE Read & Write
Multi-threading errors
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"));
}
}
Locking
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"));
}
}
Predefined resources
- Resources.SYSTEM_PROPERTIES
- Represents the system properties of Java.
- Resources.SYSTEM_OUT
- A standard output stream that represents the current process.
- Resources.SYSTEM_ERR
- Represents the standard error flow of the current process.
- Resources.LOCALE
- The default locale for the current JVM instance.
- Resources.TIMEZONE
- The default time zone for the current JVM instance.
Custom resources
- Global users
- If it is SAME_THREAD, the assertion result of running the test case is normal, but when concurrent execution is used, the corresponding case will report an error.
- Settle:
- Add the relevant @ResourceLock on the corresponding test method
Custom resources
- 1. The fully qualified class name of the custom class is value
- 2. If it is a write without a value, the corresponding mode is READ "read-only";
- If something is written to a custom class, the corresponding mode is READ_WRITE "both readable and writable"
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));
}
}
summary
Parallel test corresponding to shared data can be synchronized through @ResourceLock