[JAVA] Nutzen Sie Spring AOP + Cyclic Barrier, um optimistische Verriegelungstestbedingungen in der Spring Boot-App sicherzustellen

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.

Betrieb bestätigte Version

Beispiel-App zum Testen

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.

Getestet mit JUnit

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.

Getestet mit Spring AOP + 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
		}
	}

}

Zusammenfassung

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

Nutzen Sie Spring AOP + Cyclic Barrier, um optimistische Verriegelungstestbedingungen in der Spring Boot-App sicherzustellen
Stellen Sie das Spring Boot-Projekt in XAMPP für Tomcat bereit
Einführung in Spring Boot ② ~ AOP ~
[Java] Stellen Sie die Spring Boot-Anwendung für den Azure App Service bereit
Melden Sie sich bei Spring Boot-Webanwendungen auf der Microsoft ID-Plattform an
Die Geschichte der Erhöhung der Spring Boot 1.5-Serie auf die 2.1-Serie
SSH-Login beim App-Server mit Heroku
Stimmt die Anmerkungen auf der Schnittstelle mit Spring AOP überein
[Spring Boot] So verweisen Sie auf die Eigenschaftendatei
05. Ich habe versucht, die Quelle von Spring Boot zu löschen
Ich habe versucht, die Kapazität von Spring Boot zu reduzieren