Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,8 @@ curl -X GET http://localhost:8080/api/v1/movies
- [캐싱 성능 테스트 보고서](https://gusty-football-62b.notion.site/17f81b29f03680718163fe0b7798383e)
- [분산락 테스트 보고서](https://gusty-football-62b.notion.site/18781b29f03680049de7db34240a6733)

### jacoco 리포트

| movie-api | booking-api | application | infrastructure | domain |
|----------------------------| ----------- |----------------------------|----------------------------|-----------------------------|
| ![j_m](etc/readme/j_m.png) | ![j_b](etc/readme/j_b.png) | ![j_a](etc/readme/j_a.png) | ![j_i](etc/readme/j_i.png) | 로직이란게 없고 객체만 있어. 일단 스킵했습니다. |
12 changes: 10 additions & 2 deletions application/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@ dependencies {
implementation project(':domain')

testImplementation project(':infrastructure')
testRuntimeOnly 'com.h2database:h2'
}

bootJar {
enabled = false
}

tasks.named('test') {
test {
useJUnitPlatform()
finalizedBy jacocoTestReport
}

jacocoTestReport {
dependsOn test

reports {
html.required = true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package com.example.app.booking.service;

import com.example.app.booking.domain.Booking;
import com.example.app.booking.dto.CreateBookingCommand;
import com.example.app.booking.out.persistence.adapter.BookingPersistenceAdapter;
import com.example.app.booking.out.persistence.adapter.SeatPersistenceAdapter;
import com.example.app.common.exception.APIException;
import com.example.app.common.function.DistributedLockService;
import com.example.app.config.EmbeddedRedisConfig;
import com.example.app.movie.type.TheaterSeat;
import com.navercorp.fixturemonkey.FixtureMonkey;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;

import java.time.LocalDate;
import java.util.Set;
import java.util.function.Supplier;

import static com.example.app.booking.exception.BookingErrorMessage.SEAT_ROW_NOT_IN_SEQUENCE;
import static com.navercorp.fixturemonkey.api.instantiator.Instantiator.constructor;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;

@SpringBootTest(classes = EmbeddedRedisConfig.class)
@TestPropertySource(properties = "spring.config.location = classpath:application-test.yml")
public class CreateBookingServiceTest {

@Mock
private DistributedLockService distributedLockService;

@Mock
private BookingPersistenceAdapter bookingPersistenceAdapter;

@Mock
private SeatPersistenceAdapter seatPersistenceAdapter;

@InjectMocks
private CreateBookingService createBookingService;
Copy link
Contributor

@BAEKJungHo BAEKJungHo Feb 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트 대상을 SUT(System Under Test) 이라고 부르는데, 실무에서는 테스트 대상을 코드 내에서 빠르게 식별하기 위해서 sut 이라는 네이밍도 자주 사용합니다.


@Test
public void 예약_테스트() {
var key = FixtureMonkey.create().giveMeOne(String.class);

var bookingCommand = FixtureMonkey.create()
.giveMeBuilder(CreateBookingCommand.class)
.instantiate(constructor()
.parameter(long.class)
.parameter(long.class)
.parameter(long.class)
.parameter(long.class)
.parameter(LocalDate.class)
.parameter(Set.class))
.set("seats", Set.of(TheaterSeat.A3, TheaterSeat.A4, TheaterSeat.A5))
.sample();

var booking = FixtureMonkey.create()
.giveMeBuilder(Booking.class)
.instantiate(constructor()
.parameter(long.class)
.parameter(long.class)
.parameter(long.class)
.parameter(long.class)
.parameter(long.class)
.parameter(int.class)
.parameter(LocalDate.class))
.set("totalSeats", 3)
.sample();

when(distributedLockService.executeWithLockAndReturn(any(Supplier.class), any(String.class), any(Long.class), any(Long.class)))
.thenReturn(booking);

var result = createBookingService.createBooking(key, bookingCommand);

assertEquals(booking, result);
}

@Test
public void 예약_불가_테스트() {
var key = FixtureMonkey.create().giveMeOne(String.class);

var bookingCommand = FixtureMonkey.create()
.giveMeBuilder(CreateBookingCommand.class)
.instantiate(constructor()
.parameter(long.class)
.parameter(long.class)
.parameter(long.class)
.parameter(long.class)
.parameter(LocalDate.class)
.parameter(Set.class))
.set("seats", Set.of(TheaterSeat.B1, TheaterSeat.C1, TheaterSeat.D1))
.sample();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set.of(TheaterSeat.B1, TheaterSeat.C1, TheaterSeat.D1) 부분을 불연속적인 좌석 을 의미하는 변수로 추출하여 전달하면 유지보수성 및 가독성이 더 좋아질 것 같습니다.


var exception = assertThrows(APIException.class, () -> createBookingService.createBooking(key, bookingCommand));
assertEquals(exception.getMessage(), SEAT_ROW_NOT_IN_SEQUENCE.getMessage());
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.example.app.config;

import jakarta.annotation.PreDestroy;
import jakarta.annotation.PostConstruct;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.test.context.TestConfiguration;
import redis.embedded.RedisServer;

import java.io.IOException;

@TestConfiguration
public class EmbeddedRedisConfig {

private final RedisServer redisServer;

public EmbeddedRedisConfig(RedisProperties redisProperties) throws IOException {
this.redisServer = new RedisServer(redisProperties.getPort());
}

@PostConstruct
public void postConstruct() throws IOException {
redisServer.start();
}

@PreDestroy
public void preDestroy() throws IOException {
redisServer.stop();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.example.app.movie.service;

import com.example.app.config.EmbeddedRedisConfig;
import com.example.app.movie.domain.Movie;
import com.example.app.movie.domain.Showtime;
import com.example.app.movie.domain.Theater;
import com.example.app.movie.dto.SearchMovieCommand;
import com.example.app.movie.out.persistence.adapter.MoviePersistenceAdapter;
import com.example.app.movie.type.MovieGenre;
import com.example.app.movie.type.MovieRating;
import com.example.app.movie.type.MovieStatus;
import com.navercorp.fixturemonkey.FixtureMonkey;
import com.navercorp.fixturemonkey.api.type.TypeReference;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;

import java.time.LocalDate;
import java.util.List;

import static com.navercorp.fixturemonkey.api.instantiator.Instantiator.constructor;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@SpringBootTest(classes = EmbeddedRedisConfig.class)
@TestPropertySource(properties = "spring.config.location = classpath:application-test.yml")
public class SearchMovieServiceTest {

@Mock
private MoviePersistenceAdapter moviePersistenceAdapter;

@InjectMocks
private SearchMovieService searchMovieService;

@Test
public void 영화_검색() {
var searchCommand = FixtureMonkey.create()
.giveMeBuilder(SearchMovieCommand.class)
.instantiate(constructor()
.parameter(String.class)
.parameter(MovieGenre.class))
.sample();

var movies = FixtureMonkey.create()
.giveMeBuilder(Movie.class)
.instantiate(constructor()
.parameter(long.class)
.parameter(String.class)
.parameter(String.class)
.parameter(MovieStatus.class)
.parameter(MovieRating.class)
.parameter(MovieGenre.class)
.parameter(String.class)
.parameter(int.class)
.parameter(LocalDate.class)
.parameter(new TypeReference<List<Showtime>>(){})
.parameter(new TypeReference<List<Theater>>(){}))
.sampleList(10);

when(moviePersistenceAdapter.loadAllMovies(any(SearchMovieCommand.class))).thenReturn(movies);

var result = searchMovieService.searchMovies(searchCommand);

assertEquals(10, result.size());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10이라는 매직넘버를 상수로 관리하거나 지역 변수로 관리하면 중복을 없앨 수 있을 것 같습니다.

}
}
Loading