Dieses Mal möchte ich Ihnen vorstellen, wie Sie sicherstellen können, dass die Testbedingungen für eine optimistische Sperre für Spring Boot festgelegt sind.
Schauen wir uns den Test für optimistischen Sperrcode wie folgt an.
package com.example.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.atomic.AtomicInteger;
@SpringBootApplication
public class OptimisticLockDemoApplication {
public static void main(String[] args) {
SpringApplication.run(OptimisticLockDemoApplication.class, args);
}
@RestController
static class MyController {
private static final Logger logger = LoggerFactory.getLogger(MyController.class);
private final MyRepository repository;
public MyController(MyRepository repository) {
this.repository = repository;
}
@PostMapping("/version/increment")
boolean incrementVersion() {
int currentVersion = repository.getVersion(); // (1)Holen Sie sich die Versionsnummer für eine optimistische Sperre
logger.info("current version : {}", currentVersion);
boolean result = repository.updateVersion(currentVersion); // (2)Aktualisiert mit optimistischer Sperre
logger.info("updating result : {}", result);
return result;
}
}
@Repository
static class MyRepository {
private final AtomicInteger version = new AtomicInteger(1);
public int getVersion() {
return version.get();
}
public boolean updateVersion(int currentVersion) {
return version.compareAndSet(currentVersion, currentVersion + 1);
}
}
}
Wird das optimistische Sperren korrekt durchgeführt? Beim Testen (= der Fall, in dem das Ergebnis von (2) "falsch" ist) muss die Operation ausgeführt werden, damit dieselbe Thread-Nummer von mehreren Threads erfasst wird. Im obigen Beispielcode gibt es keine Verarbeitung zwischen (1) und (2), so dass es für eine Person ziemlich schwierig ist, das Ergebnis von (2) während des Betriebs auf "falsch" zu setzen.
Schreiben wir also Testcode, der JUnit verwendet, um Anforderungen ungefähr zur gleichen Zeit zu senden.
package com.example.demo;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.concurrent.atomic.AtomicInteger;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class OptimisticLockDemoApplicationTests {
@Autowired TestRestTemplate restTemplate;
@Test
public void contextLoads() throws InterruptedException {
AtomicInteger successCounter = new AtomicInteger(0);
Thread client1 = new Thread(runnable(successCounter));
Thread client2 = new Thread(runnable(successCounter));
client1.start();
client2.start();
client1.join(10000);
client2.join(10000);
Assertions.assertThat(successCounter.get()).isEqualTo(1);
}
private Runnable runnable(AtomicInteger successCounter) {
return () -> {
boolean result = restTemplate.postForObject("/version/increment", null, boolean.class);
if (result) {
successCounter.incrementAndGet();
}
};
}
}
Als ich diesen Testcode ausführte, bestand eine gute Chance, dass der Test erfolgreich war (tatsächlich 100% erfolgreich auf meinem Computer). Also ist das okay? Trotzdem besteht immer noch die Möglichkeit, dass der Test in diesem Testcode fehlschlägt.
Dies liegt daran, dass ... ich es so implementiert habe, dass Anforderungen fast gleichzeitig mit zwei Threads an den Server gesendet werden, aber es gibt keine Garantie dafür, dass der tatsächliche Zeitpunkt, zu dem die Anforderungen den Server erreichen, der gleiche ist.
Als Versuch ... Wenn Sie versuchen, das Timing zu verschieben, wenn die Anfrage pseudo ankommt ...
@Test
public void contextLoads() throws InterruptedException {
AtomicInteger successCounter = new AtomicInteger(0);
Thread client1 = new Thread(runnable(successCounter));
Thread client2 = new Thread(runnable(successCounter));
client1.start();
Thread.sleep(200); //Verschieben Sie absichtlich den Zeitpunkt der Ankunft der Anfrage
client2.start();
client1.join(10000);
client2.join(10000);
Assertions.assertThat(successCounter.get()).isEqualTo(1);
}
Der Test schlägt fehl. Also ... der obige Testcode kann je nach Zeitpunkt fehlschlagen.
Was können wir also tun, um sicherzustellen, dass die Testbedingungen erfüllt sind? Apropos ... Der Aktualisierungsvorgang sollte erst durchgeführt werden, wenn zwei Anforderungen dieselbe Versionsnummer erhalten.
Cyclic Barrier
Um diese Testbedingung zu erfüllen, ohne den zu testenden Quellcode zu ändern, möchte ich diesmal Spring AOP und CyclicBarrier
verwenden.
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId> <!--AOP-Starter hinzugefügt-->
</dependency>
package com.example.demo;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestComponent;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
@EnableAspectJAutoProxy //Aktivieren Sie die AOP-Funktion mithilfe von AspectJ-Anmerkungen
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class OptimisticLockDemoApplicationTests {
@Autowired TestRestTemplate restTemplate;
@Test
public void contextLoads() throws InterruptedException {
AtomicInteger successCounter = new AtomicInteger(0);
Thread client1 = new Thread(runnable(successCounter));
Thread client2 = new Thread(runnable(successCounter));
client1.start();
Thread.sleep(1000); //Stellen Sie die Ruhezeit auf 0 ein, um eine zeitliche Verschiebung sicherzustellen.Wechseln Sie von 2 Sekunden auf 1 Sekunde
client2.start();
client1.join(10000);
client2.join(10000);
Assertions.assertThat(successCounter.get()).isEqualTo(1);
}
private Runnable runnable(AtomicInteger successCounter) {
return () -> {
boolean result = restTemplate.postForObject("/version/increment", null, boolean.class);
if (result) {
successCounter.incrementAndGet();
}
};
}
//Warten Sie, bis die Aktualisierungsmethode ausgeführt wird, bis die Testbedingungen erfüllt sind
//Test-Bedingungen:Rufen Sie die Aktualisierungsmethode auf, nachdem zwei Threads auf dieselbe Versionsnummer verweisen
@TestComponent
@Aspect
static class UpdateAwaitAspect {
private final CyclicBarrier barrier = new CyclicBarrier(2);
@Before("execution(* com.example.demo.OptimisticLockDemoApplication.MyRepository.updateVersion(..))")
public void awaitUpdating() throws BrokenBarrierException, InterruptedException, TimeoutException {
barrier.await(10, TimeUnit.SECONDS); //Zeitüberschreitung, um unendliches Warten zu vermeiden, wenn die zweite Anforderung nicht eingeht(10 Sekunden im Beispiel)Einrichten
}
}
}
Mit Spring AOP + CyclicBarrier
können Sie sicher sein, die Testbedingungen für Tests festzulegen, die vom Zeitpunkt der Ausführung abhängen (z. B. optimistischer Sperrtest). In diesem Eintrag wird eine Anforderung an den integrierten Tomcat gestellt und getestet. Wenn jedoch derselbe Mechanismus auf den Test angewendet wird (Komponentenintegrationstest?), Der mit "Serviceklasse ⇄ Repository" verbunden ist, hängt dies vom Mechanismus des Spring Boot ab. Sie können einen ähnlichen Test durchführen, ohne ihn durchzuführen.
Hören wir auf, gleichzeitig Anfragen mit mehreren Personen zu senden und ~ zu testen oder einen Haltepunkt in der IDE festzulegen ~ und so weiter: wink:
Recommended Posts