Dışarıdaki tüm Bahar kullanıcıları için, günümüzde uyumsuzluğun dahil olduğu entegrasyon testlerimi şu şekilde yaparım:
Zaman uyumsuz bir görev (G / Ç çağrısı gibi) bittiğinde, üretim kodunda bir uygulama olayını başlatın. Çoğu zaman bu olay, üretimdeki asenkron işlemin yanıtını ele almak için yine de gereklidir.
Bu etkinlik gerçekleştikten sonra, test durumunuzda aşağıdaki stratejiyi kullanabilirsiniz:
- Test edilen sistemi çalıştırın
- Etkinliği dinleyin ve etkinliğin tetiklendiğinden emin olun
- İddialarınızı yapın
Bunu kırmak için, önce tetiklenecek bir tür etki alanı etkinliğine ihtiyacınız olacak. Tamamlanan görevi tanımlamak için burada bir UUID kullanıyorum, ancak elbette benzersiz olduğu sürece başka bir şey kullanmakta özgürsünüz.
(Aşağıdaki kod snippet'lerinin , kazan plakası kodundan kurtulmak için Lombok ek açıklamalarını da kullandığını unutmayın )
@RequiredArgsConstructor
class TaskCompletedEvent() {
private final UUID taskId;
// add more fields containing the result of the task if required
}
Üretim kodunun kendisi genellikle şöyle görünür:
@Component
@RequiredArgsConstructor
class Production {
private final ApplicationEventPublisher eventPublisher;
void doSomeTask(UUID taskId) {
// do something like calling a REST endpoint asynchronously
eventPublisher.publishEvent(new TaskCompletedEvent(taskId));
}
}
Daha sonra @EventListener
test kodunda yayınlanan olayı yakalamak için bir Bahar kullanabilirsiniz . Olay dinleyicisi biraz daha karmaşıktır, çünkü iki vakayı iş parçacığıyla güvenli bir şekilde ele almak zorundadır:
- Üretim kodu test senaryosundan daha hızlıdır ve test senaryosu olayı kontrol etmeden önce olay tetiklenmiştir veya
- Test durumu üretim kodundan daha hızlıdır ve test durumu etkinliği beklemek zorundadır.
CountDownLatch
Buradaki diğer cevaplarda belirtildiği gibi A ikinci durum için kullanılır. Ayrıca @Order
, olay işleyici yöntemindeki ek açıklamanın, bu olay işleyici yönteminin üretimde kullanılan diğer olay dinleyicilerinden sonra çağrıldığından emin olduğunu unutmayın .
@Component
class TaskCompletionEventListener {
private Map<UUID, CountDownLatch> waitLatches = new ConcurrentHashMap<>();
private List<UUID> eventsReceived = new ArrayList<>();
void waitForCompletion(UUID taskId) {
synchronized (this) {
if (eventAlreadyReceived(taskId)) {
return;
}
checkNobodyIsWaiting(taskId);
createLatch(taskId);
}
waitForEvent(taskId);
}
private void checkNobodyIsWaiting(UUID taskId) {
if (waitLatches.containsKey(taskId)) {
throw new IllegalArgumentException("Only one waiting test per task ID supported, but another test is already waiting for " + taskId + " to complete.");
}
}
private boolean eventAlreadyReceived(UUID taskId) {
return eventsReceived.remove(taskId);
}
private void createLatch(UUID taskId) {
waitLatches.put(taskId, new CountDownLatch(1));
}
@SneakyThrows
private void waitForEvent(UUID taskId) {
var latch = waitLatches.get(taskId);
latch.await();
}
@EventListener
@Order
void eventReceived(TaskCompletedEvent event) {
var taskId = event.getTaskId();
synchronized (this) {
if (isSomebodyWaiting(taskId)) {
notifyWaitingTest(taskId);
} else {
eventsReceived.add(taskId);
}
}
}
private boolean isSomebodyWaiting(UUID taskId) {
return waitLatches.containsKey(taskId);
}
private void notifyWaitingTest(UUID taskId) {
var latch = waitLatches.remove(taskId);
latch.countDown();
}
}
Son adım, test edilen sistemi bir test durumunda yürütmektir. Burada JUnit 5 ile bir SpringBoot testi kullanıyorum, ama bu bir Spring bağlamı kullanan tüm testler için aynı çalışmalıdır.
@SpringBootTest
class ProductionIntegrationTest {
@Autowired
private Production sut;
@Autowired
private TaskCompletionEventListener listener;
@Test
void thatTaskCompletesSuccessfully() {
var taskId = UUID.randomUUID();
sut.doSomeTask(taskId);
listener.waitForCompletion(taskId);
// do some assertions like looking into the DB if value was stored successfully
}
}
Buradaki diğer yanıtların aksine, testlerinizi paralel olarak çalıştırırsanız ve birden çok iş parçacığı aynı zamanda eşzamansız kod uygularsa bu çözümün de işe yarayacağını unutmayın.