From 57d3e8504ecad8009b6cfb4f2338731ffa6f051c Mon Sep 17 00:00:00 2001 From: taek2222 Date: Fri, 26 Dec 2025 21:21:28 +0900 Subject: [PATCH 1/7] =?UTF-8?q?test:=20=EB=8F=99=EC=8B=9C=EC=84=B1=20Helpe?= =?UTF-8?q?r=20=EC=8A=A4=EB=A0=88=EB=93=9C=20=EC=88=98=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=20=EB=8F=99=EC=9E=91=20=EB=B3=80=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AnnouncementControllerTest.java | 7 ++----- .../FestivalNotificationConcurrencyTest.java | 6 ++---- .../global/lock/ConcurrencyTestHelper.java | 13 +++++++++---- .../festabook/support/AcceptanceTestSupport.java | 7 ++++++- src/test/resources/application.yml | 5 +++++ 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java b/src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java index 071e0a9..6e79ce9 100644 --- a/src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java +++ b/src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java @@ -23,7 +23,6 @@ import com.daedan.festabook.festival.domain.Festival; import com.daedan.festabook.festival.domain.FestivalFixture; import com.daedan.festabook.festival.infrastructure.FestivalJpaRepository; -import com.daedan.festabook.global.lock.ConcurrencyTestHelper; import com.daedan.festabook.global.security.JwtTestHelper; import com.daedan.festabook.global.security.role.RoleType; import com.daedan.festabook.support.AcceptanceTestSupport; @@ -453,7 +452,6 @@ class ConcurrentTest { AnnouncementRequest request = AnnouncementRequestFixture.create(true); - int requestCount = 100; Runnable httpRequest = () -> { RestAssured .given() @@ -467,7 +465,7 @@ class ConcurrentTest { int expectedPinnedAnnouncementCount = 3; // when - ConcurrencyTestHelper.test(requestCount, httpRequest); + concurrencyTestHelper.execute(httpRequest); // then Long result = announcementJpaRepository.countByFestivalIdAndIsPinnedTrue(festival.getId()); @@ -488,7 +486,6 @@ class ConcurrentTest { AnnouncementRequest createAnnouncement = AnnouncementRequestFixture.create(true); AnnouncementPinUpdateRequest updateAnnouncement = AnnouncementPinUpdateRequestFixture.create(true); - int requestCount = 100; Runnable createAnnouncementRequest = () -> { RestAssured .given() @@ -512,7 +509,7 @@ class ConcurrentTest { int expectedPinnedAnnouncementCount = 3; // when - ConcurrencyTestHelper.test(requestCount, createAnnouncementRequest, updateAnnouncementRequest); + concurrencyTestHelper.execute(createAnnouncementRequest, updateAnnouncementRequest); // then Long result = announcementJpaRepository.countByFestivalIdAndIsPinnedTrue(festival.getId()); diff --git a/src/test/java/com/daedan/festabook/festival/service/FestivalNotificationConcurrencyTest.java b/src/test/java/com/daedan/festabook/festival/service/FestivalNotificationConcurrencyTest.java index 668a1d7..1f5c17f 100644 --- a/src/test/java/com/daedan/festabook/festival/service/FestivalNotificationConcurrencyTest.java +++ b/src/test/java/com/daedan/festabook/festival/service/FestivalNotificationConcurrencyTest.java @@ -11,7 +11,6 @@ import com.daedan.festabook.festival.dto.FestivalNotificationRequestFixture; import com.daedan.festabook.festival.infrastructure.FestivalJpaRepository; import com.daedan.festabook.festival.infrastructure.FestivalNotificationJpaRepository; -import com.daedan.festabook.global.lock.ConcurrencyTestHelper; import com.daedan.festabook.support.AcceptanceTestSupport; import io.restassured.RestAssured; import io.restassured.http.ContentType; @@ -47,7 +46,6 @@ class subscribeFestivalNotification { FestivalNotificationRequest request = FestivalNotificationRequestFixture.create(device.getId()); - int requestCount = 100; AtomicInteger duplicateErrorCount = new AtomicInteger(0); Runnable httpRequest = () -> { @@ -67,14 +65,14 @@ class subscribeFestivalNotification { }; // when - ConcurrencyTestHelper.test(requestCount, httpRequest); + concurrencyTestHelper.execute(httpRequest); // then Long result = festivalNotificationJpaRepository.countByFestivalIdAndDeviceId( festival.getId(), device.getId()); assertThat(result).isEqualTo(1); - assertThat(duplicateErrorCount.get()).isEqualTo(99); + assertThat(duplicateErrorCount.get()).isEqualTo(concurrencyTestHelper.getRequestCount() - 1); } } } diff --git a/src/test/java/com/daedan/festabook/global/lock/ConcurrencyTestHelper.java b/src/test/java/com/daedan/festabook/global/lock/ConcurrencyTestHelper.java index bb752ee..71e9d2d 100644 --- a/src/test/java/com/daedan/festabook/global/lock/ConcurrencyTestHelper.java +++ b/src/test/java/com/daedan/festabook/global/lock/ConcurrencyTestHelper.java @@ -3,13 +3,18 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +@Getter +@Component public class ConcurrencyTestHelper { - private ConcurrencyTestHelper() { - } + @Value("${server.tomcat.threads.max}") + private int requestCount; - public static void test(int requestCount, Runnable... requests) { + public void execute(Runnable... requests) { validateRequests(requests); try (ExecutorService threadPool = Executors.newFixedThreadPool(requestCount)) { @@ -38,7 +43,7 @@ public static void test(int requestCount, Runnable... requests) { } } - private static void validateRequests(Runnable[] requests) { + private void validateRequests(Runnable[] requests) { if (requests.length == 0) { throw new IllegalArgumentException("실행할 api 인자는 최소 1개 이상이어야 합니다."); } diff --git a/src/test/java/com/daedan/festabook/support/AcceptanceTestSupport.java b/src/test/java/com/daedan/festabook/support/AcceptanceTestSupport.java index 6a7fc8c..29d6b4f 100644 --- a/src/test/java/com/daedan/festabook/support/AcceptanceTestSupport.java +++ b/src/test/java/com/daedan/festabook/support/AcceptanceTestSupport.java @@ -3,22 +3,27 @@ import com.daedan.festabook.festival.domain.FestivalNotificationManager; import com.daedan.festabook.global.config.TestSecurityConfig; import com.daedan.festabook.global.infrastructure.ShuffleManager; +import com.daedan.festabook.global.lock.ConcurrencyTestHelper; import io.restassured.RestAssured; import java.time.Clock; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.context.annotation.Import; import org.springframework.test.context.bean.override.mockito.MockitoBean; -@Import(TestSecurityConfig.class) +@Import({TestSecurityConfig.class, ConcurrencyTestHelper.class}) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) public abstract class AcceptanceTestSupport { + @Autowired + protected ConcurrencyTestHelper concurrencyTestHelper; + @MockitoBean protected Clock clock; diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 14d8a13..fc3a580 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -31,3 +31,8 @@ secret: fcm: topic: festival-prefix: example-festival + +server: + tomcat: + threads: + max: 15 From 48e8f7b8dffcf5993480756c75e5fd097c73b366 Mon Sep 17 00:00:00 2001 From: taek2222 Date: Fri, 26 Dec 2025 23:03:10 +0900 Subject: [PATCH 2/7] =?UTF-8?q?test:=20=EB=8F=99=EC=8B=9C=EC=84=B1=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B2=B0=EA=B3=BC=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20Runnable=E2=86=92C?= =?UTF-8?q?allable=EB=A1=9C=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AnnouncementControllerTest.java | 52 +++++++++---------- .../FestivalNotificationConcurrencyTest.java | 36 +++++-------- .../global/lock/ConcurrencyTestHelper.java | 45 +++++++++------- .../support/ConcurrencyTestResult.java | 32 ++++++++++++ 4 files changed, 97 insertions(+), 68 deletions(-) create mode 100644 src/test/java/com/daedan/festabook/support/ConcurrencyTestResult.java diff --git a/src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java b/src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java index 6e79ce9..390634d 100644 --- a/src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java +++ b/src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java @@ -29,7 +29,9 @@ import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.http.Header; +import io.restassured.response.Response; import java.util.List; +import java.util.concurrent.Callable; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -452,15 +454,13 @@ class ConcurrentTest { AnnouncementRequest request = AnnouncementRequestFixture.create(true); - Runnable httpRequest = () -> { - RestAssured - .given() - .header(authorizationHeader) - .contentType(ContentType.JSON) - .body(request) - .when() - .post("/announcements"); - }; + Callable httpRequest = () -> RestAssured + .given() + .header(authorizationHeader) + .contentType(ContentType.JSON) + .body(request) + .when() + .post("/announcements"); int expectedPinnedAnnouncementCount = 3; @@ -486,25 +486,21 @@ class ConcurrentTest { AnnouncementRequest createAnnouncement = AnnouncementRequestFixture.create(true); AnnouncementPinUpdateRequest updateAnnouncement = AnnouncementPinUpdateRequestFixture.create(true); - Runnable createAnnouncementRequest = () -> { - RestAssured - .given() - .header(authorizationHeader) - .contentType(ContentType.JSON) - .body(createAnnouncement) - .when() - .post("/announcements"); - }; - - Runnable updateAnnouncementRequest = () -> { - RestAssured - .given() - .header(authorizationHeader) - .contentType(ContentType.JSON) - .body(updateAnnouncement) - .when() - .post("announcement/{announcementId}/pin", initAnnouncement.getId()); - }; + Callable createAnnouncementRequest = () -> RestAssured + .given() + .header(authorizationHeader) + .contentType(ContentType.JSON) + .body(createAnnouncement) + .when() + .post("/announcements"); + + Callable updateAnnouncementRequest = () -> RestAssured + .given() + .header(authorizationHeader) + .contentType(ContentType.JSON) + .body(updateAnnouncement) + .when() + .post("announcement/{announcementId}/pin", initAnnouncement.getId()); int expectedPinnedAnnouncementCount = 3; diff --git a/src/test/java/com/daedan/festabook/festival/service/FestivalNotificationConcurrencyTest.java b/src/test/java/com/daedan/festabook/festival/service/FestivalNotificationConcurrencyTest.java index 1f5c17f..69530d6 100644 --- a/src/test/java/com/daedan/festabook/festival/service/FestivalNotificationConcurrencyTest.java +++ b/src/test/java/com/daedan/festabook/festival/service/FestivalNotificationConcurrencyTest.java @@ -12,10 +12,11 @@ import com.daedan.festabook.festival.infrastructure.FestivalJpaRepository; import com.daedan.festabook.festival.infrastructure.FestivalNotificationJpaRepository; import com.daedan.festabook.support.AcceptanceTestSupport; +import com.daedan.festabook.support.ConcurrencyTestResult; import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.response.Response; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.Callable; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -46,33 +47,24 @@ class subscribeFestivalNotification { FestivalNotificationRequest request = FestivalNotificationRequestFixture.create(device.getId()); - AtomicInteger duplicateErrorCount = new AtomicInteger(0); - - Runnable httpRequest = () -> { - Response response = RestAssured - .given() - .contentType(ContentType.JSON) - .body(request) - .when() - .post("/festivals/{festivalId}/notifications", festival.getId()); - - if (response.getStatusCode() == HttpStatus.CONFLICT.value()) { - String responseBody = response.getBody().asString(); - if (responseBody.contains("FestivalNotification 데이터베이스에 이미 존재합니다.")) { - duplicateErrorCount.incrementAndGet(); - } - } - }; + Callable httpRequest = () -> RestAssured + .given() + .contentType(ContentType.JSON) + .body(request) + .when() + .post("/festivals/{festivalId}/notifications", festival.getId()); // when - concurrencyTestHelper.execute(httpRequest); + ConcurrencyTestResult result = concurrencyTestHelper.execute(httpRequest); // then - Long result = festivalNotificationJpaRepository.countByFestivalIdAndDeviceId( + Long notificationCount = festivalNotificationJpaRepository.countByFestivalIdAndDeviceId( festival.getId(), device.getId()); - assertThat(result).isEqualTo(1); + assertThat(notificationCount).isEqualTo(1); - assertThat(duplicateErrorCount.get()).isEqualTo(concurrencyTestHelper.getRequestCount() - 1); + assertThat(result.getSuccessCount()).isEqualTo(1); + assertThat(result.getStatusCodeCount(HttpStatus.CONFLICT)) + .isEqualTo(result.getRequestCount() - result.getSuccessCount()); } } } diff --git a/src/test/java/com/daedan/festabook/global/lock/ConcurrencyTestHelper.java b/src/test/java/com/daedan/festabook/global/lock/ConcurrencyTestHelper.java index 71e9d2d..7cd92a7 100644 --- a/src/test/java/com/daedan/festabook/global/lock/ConcurrencyTestHelper.java +++ b/src/test/java/com/daedan/festabook/global/lock/ConcurrencyTestHelper.java @@ -1,33 +1,43 @@ package com.daedan.festabook.global.lock; +import com.daedan.festabook.support.ConcurrencyTestResult; +import io.restassured.response.Response; +import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import lombok.Getter; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; -@Getter +@Slf4j @Component public class ConcurrencyTestHelper { @Value("${server.tomcat.threads.max}") - private int requestCount; + private int tomcatThreadCount; - public void execute(Runnable... requests) { + @SafeVarargs + public final ConcurrencyTestResult execute(Callable... requests) { validateRequests(requests); + ConcurrencyTestResult result = new ConcurrencyTestResult(); - try (ExecutorService threadPool = Executors.newFixedThreadPool(requestCount)) { + try (ExecutorService executorService = Executors.newFixedThreadPool(tomcatThreadCount)) { CountDownLatch startLatch = new CountDownLatch(1); - CountDownLatch endLatch = new CountDownLatch(requestCount); + CountDownLatch endLatch = new CountDownLatch(tomcatThreadCount); - for (int i = 0; i < requestCount; i++) { + for (int i = 0; i < tomcatThreadCount; i++) { int currentCount = i % requests.length; - threadPool.submit(() -> { + executorService.submit(() -> { try { startLatch.await(); - requests[currentCount].run(); - } catch (InterruptedException ignore) { + Response response = requests[currentCount].call(); + + HttpStatus httpStatus = HttpStatus.valueOf(response.getStatusCode()); + result.recordStatusCode(httpStatus); + } catch (Exception e) { + log.error("동시성 테스트 실행 중 예외 발생", e); } finally { endLatch.countDown(); } @@ -35,17 +45,16 @@ public void execute(Runnable... requests) { } startLatch.countDown(); - - try { - endLatch.await(); - } catch (InterruptedException ignore) { - } + endLatch.await(); + } catch (InterruptedException ignore) { } + return result; } - private void validateRequests(Runnable[] requests) { - if (requests.length == 0) { - throw new IllegalArgumentException("실행할 api 인자는 최소 1개 이상이어야 합니다."); + @SafeVarargs + private void validateRequests(Callable... requests) { + if (requests == null || requests.length == 0) { + throw new IllegalArgumentException("실행할 API 인자는 최소 1개 이상이어야 합니다."); } } } diff --git a/src/test/java/com/daedan/festabook/support/ConcurrencyTestResult.java b/src/test/java/com/daedan/festabook/support/ConcurrencyTestResult.java new file mode 100644 index 0000000..98a3148 --- /dev/null +++ b/src/test/java/com/daedan/festabook/support/ConcurrencyTestResult.java @@ -0,0 +1,32 @@ +package com.daedan.festabook.support; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; +import org.springframework.http.HttpStatus; + +public class ConcurrencyTestResult { + + private final Map statusCodeCounts = new ConcurrentHashMap<>(); + private final AtomicLong requestCount = new AtomicLong(); + + public void recordStatusCode(HttpStatus status) { + statusCodeCounts.merge(status, 1, Integer::sum); + requestCount.incrementAndGet(); + } + + public int getStatusCodeCount(HttpStatus status) { + return statusCodeCounts.getOrDefault(status, 0); + } + + public int getSuccessCount() { + return statusCodeCounts.entrySet().stream() + .filter(entry -> entry.getKey().is2xxSuccessful()) + .mapToInt(Map.Entry::getValue) + .sum(); + } + + public long getRequestCount() { + return requestCount.get(); + } +} From 635c9765f075e99aaaa3f4147859d56c9beab89a Mon Sep 17 00:00:00 2001 From: taek2222 Date: Fri, 26 Dec 2025 23:45:48 +0900 Subject: [PATCH 3/7] =?UTF-8?q?test:=20Exception=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EC=BD=94=EB=93=9C=20=EC=A6=9D=EA=B0=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/daedan/festabook/global/lock/ConcurrencyTestHelper.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/com/daedan/festabook/global/lock/ConcurrencyTestHelper.java b/src/test/java/com/daedan/festabook/global/lock/ConcurrencyTestHelper.java index 7cd92a7..791a72e 100644 --- a/src/test/java/com/daedan/festabook/global/lock/ConcurrencyTestHelper.java +++ b/src/test/java/com/daedan/festabook/global/lock/ConcurrencyTestHelper.java @@ -38,6 +38,7 @@ public final ConcurrencyTestResult execute(Callable... requests) { result.recordStatusCode(httpStatus); } catch (Exception e) { log.error("동시성 테스트 실행 중 예외 발생", e); + result.recordStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); } finally { endLatch.countDown(); } From edd84bc1678ed7a3444a2404085eac0c27fcb33d Mon Sep 17 00:00:00 2001 From: taek2222 Date: Fri, 26 Dec 2025 23:46:03 +0900 Subject: [PATCH 4/7] =?UTF-8?q?test:=20=EB=8F=99=EC=8B=9C=EC=84=B1=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=98=EB=AA=BB=EB=90=9C=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../announcement/controller/AnnouncementControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java b/src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java index 390634d..dd409db 100644 --- a/src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java +++ b/src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java @@ -500,7 +500,7 @@ class ConcurrentTest { .contentType(ContentType.JSON) .body(updateAnnouncement) .when() - .post("announcement/{announcementId}/pin", initAnnouncement.getId()); + .patch("/announcements/{announcementId}/pin", initAnnouncement.getId()); int expectedPinnedAnnouncementCount = 3; From 9be059f114b5ac9ac6351630b25891b9378e7975 Mon Sep 17 00:00:00 2001 From: taek2222 Date: Sat, 27 Dec 2025 00:17:31 +0900 Subject: [PATCH 5/7] =?UTF-8?q?test:=20=EB=8F=99=EC=8B=9C=EC=84=B1=20Helpe?= =?UTF-8?q?r=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=9C=84=EC=B9=98=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/daedan/festabook/support/AcceptanceTestSupport.java | 1 - .../{global/lock => support}/ConcurrencyTestHelper.java | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) rename src/test/java/com/daedan/festabook/{global/lock => support}/ConcurrencyTestHelper.java (95%) diff --git a/src/test/java/com/daedan/festabook/support/AcceptanceTestSupport.java b/src/test/java/com/daedan/festabook/support/AcceptanceTestSupport.java index 29d6b4f..3ec7456 100644 --- a/src/test/java/com/daedan/festabook/support/AcceptanceTestSupport.java +++ b/src/test/java/com/daedan/festabook/support/AcceptanceTestSupport.java @@ -3,7 +3,6 @@ import com.daedan.festabook.festival.domain.FestivalNotificationManager; import com.daedan.festabook.global.config.TestSecurityConfig; import com.daedan.festabook.global.infrastructure.ShuffleManager; -import com.daedan.festabook.global.lock.ConcurrencyTestHelper; import io.restassured.RestAssured; import java.time.Clock; import org.junit.jupiter.api.BeforeEach; diff --git a/src/test/java/com/daedan/festabook/global/lock/ConcurrencyTestHelper.java b/src/test/java/com/daedan/festabook/support/ConcurrencyTestHelper.java similarity index 95% rename from src/test/java/com/daedan/festabook/global/lock/ConcurrencyTestHelper.java rename to src/test/java/com/daedan/festabook/support/ConcurrencyTestHelper.java index 791a72e..5adb0bf 100644 --- a/src/test/java/com/daedan/festabook/global/lock/ConcurrencyTestHelper.java +++ b/src/test/java/com/daedan/festabook/support/ConcurrencyTestHelper.java @@ -1,6 +1,5 @@ -package com.daedan.festabook.global.lock; +package com.daedan.festabook.support; -import com.daedan.festabook.support.ConcurrencyTestResult; import io.restassured.response.Response; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; From 100b5668f34812dd81a876c51da11a3cb4d3dd65 Mon Sep 17 00:00:00 2001 From: taek2222 Date: Wed, 31 Dec 2025 22:45:03 +0900 Subject: [PATCH 6/7] =?UTF-8?q?test:=20Error=20Count=20=EB=B0=8F=20Cause?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../festabook/support/ConcurrencyTestHelper.java | 2 +- .../festabook/support/ConcurrencyTestResult.java | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/test/java/com/daedan/festabook/support/ConcurrencyTestHelper.java b/src/test/java/com/daedan/festabook/support/ConcurrencyTestHelper.java index 5adb0bf..020a854 100644 --- a/src/test/java/com/daedan/festabook/support/ConcurrencyTestHelper.java +++ b/src/test/java/com/daedan/festabook/support/ConcurrencyTestHelper.java @@ -37,7 +37,7 @@ public final ConcurrencyTestResult execute(Callable... requests) { result.recordStatusCode(httpStatus); } catch (Exception e) { log.error("동시성 테스트 실행 중 예외 발생", e); - result.recordStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); + result.recordError(e); } finally { endLatch.countDown(); } diff --git a/src/test/java/com/daedan/festabook/support/ConcurrencyTestResult.java b/src/test/java/com/daedan/festabook/support/ConcurrencyTestResult.java index 98a3148..3dd5be7 100644 --- a/src/test/java/com/daedan/festabook/support/ConcurrencyTestResult.java +++ b/src/test/java/com/daedan/festabook/support/ConcurrencyTestResult.java @@ -1,7 +1,9 @@ package com.daedan.festabook.support; import java.util.Map; +import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicLong; import org.springframework.http.HttpStatus; @@ -9,21 +11,21 @@ public class ConcurrencyTestResult { private final Map statusCodeCounts = new ConcurrentHashMap<>(); private final AtomicLong requestCount = new AtomicLong(); + private final AtomicLong errorCount = new AtomicLong(); + private final Queue errorCauses = new ConcurrentLinkedQueue<>(); public void recordStatusCode(HttpStatus status) { statusCodeCounts.merge(status, 1, Integer::sum); requestCount.incrementAndGet(); } - public int getStatusCodeCount(HttpStatus status) { - return statusCodeCounts.getOrDefault(status, 0); + public void recordError(Throwable e) { + errorCount.incrementAndGet(); + errorCauses.add(e); } - public int getSuccessCount() { - return statusCodeCounts.entrySet().stream() - .filter(entry -> entry.getKey().is2xxSuccessful()) - .mapToInt(Map.Entry::getValue) - .sum(); + public int getStatusCodeCount(HttpStatus status) { + return statusCodeCounts.getOrDefault(status, 0); } public long getRequestCount() { From 2eaae6c638ae92bf0586e5e1eeb32b7995aaed11 Mon Sep 17 00:00:00 2001 From: taek2222 Date: Wed, 31 Dec 2025 22:45:21 +0900 Subject: [PATCH 7/7] =?UTF-8?q?test:=20assertAll=20=ED=98=95=ED=83=9C?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FestivalNotificationConcurrencyTest.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/daedan/festabook/festival/service/FestivalNotificationConcurrencyTest.java b/src/test/java/com/daedan/festabook/festival/service/FestivalNotificationConcurrencyTest.java index 69530d6..8c596cf 100644 --- a/src/test/java/com/daedan/festabook/festival/service/FestivalNotificationConcurrencyTest.java +++ b/src/test/java/com/daedan/festabook/festival/service/FestivalNotificationConcurrencyTest.java @@ -1,6 +1,7 @@ package com.daedan.festabook.festival.service; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; import com.daedan.festabook.device.domain.Device; import com.daedan.festabook.device.domain.DeviceFixture; @@ -60,11 +61,15 @@ class subscribeFestivalNotification { // then Long notificationCount = festivalNotificationJpaRepository.countByFestivalIdAndDeviceId( festival.getId(), device.getId()); - assertThat(notificationCount).isEqualTo(1); - assertThat(result.getSuccessCount()).isEqualTo(1); - assertThat(result.getStatusCodeCount(HttpStatus.CONFLICT)) - .isEqualTo(result.getRequestCount() - result.getSuccessCount()); + int successCount = result.getStatusCodeCount(HttpStatus.CREATED); + + assertAll( + () -> assertThat(notificationCount).isEqualTo(1), + () -> assertThat(successCount).isEqualTo(1), + () -> assertThat(result.getStatusCodeCount(HttpStatus.CONFLICT)) + .isEqualTo(result.getRequestCount() - successCount) + ); } } }