๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๊ฐœ๋ฐœ/์šฐ์•„ํ•œํ…Œํฌ์ฝ”์Šค

์„œ๋น„์Šค ํ…Œ์ŠคํŠธ ๊ฐœ์„ ํ•˜๊ธฐ

๊ธฐ์กด์˜ ์„œ๋น„์Šค ์ฝ”๋“œ

@DataJpaTest
@Import(ReservationConfig.class)
class ReservationApplicationServiceTest {

		// ... ํ•„์š”ํ•œ ์ฃผ์ž… ๋ฐ ํ…Œ์ŠคํŠธ ...

    static class ReservationConfig {

        // @Bean
        // ... ํ•„์š”ํ•œ ์‹ค์ œ ๊ฐ์ฒด ์ฃผ์ž… ...
    }
}

 

๋ฌธ์ œ์ 

1. @DataJpaTest๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ @DataJpaTest ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ Repository ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ๊ฒƒ์ด๋‹ค! ๊ฒŒ๋‹ค๊ฐ€ Service ํ…Œ์ŠคํŠธ์—์„œ ์กฐ์‹ฌํ•˜๊ฒŒ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” @Transactional ์• ๋…ธํ…Œ์ด์…˜์ด ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ถ™์–ด์žˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ์— ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์ฐพ์•„์•ผ ํ•œ๋‹ค.

 

2. inner class๋กœ ReservationConfig๋ฅผ ํ†ตํ•ด ๋นˆ์„ ์ง์ ‘ ๋“ฑ๋กํ•˜๊ณ  ์žˆ๋‹ค.

์ˆ˜ ๋งŽ์€ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ์–ด๋–ป๊ฒŒ ๋‹ค ๊ธฐ์–ตํ•˜์ง€?

 

์ด๋ ‡๊ฒŒ๊นŒ์ง€ ๋นˆ์„ ๋“ฑ๋กํ•˜๋ฉฐ ์„œ๋น„์Šค๋ฅผ ํ…Œ์ŠคํŠธ ํ•˜๋Š” ๊ฒƒ์€ ์›์น˜ ์•Š๋Š”๋‹ค.

 

๊ฐœ์„ ํ•ด๋ณด์ž.

๋จผ์ € @DataJpaTest ์• ๋…ธํ…Œ์ด์…˜์„ ์ œ๊ฑฐํ•ด๋ณด์ž.

 

์Šคํ”„๋ง์—๋Š” @WebMvcTest, @EnableJpaRepositories ๋“ฑ… ๋งŽ์€ ์• ๋…ธํ…Œ์ด์…˜์ด ์žˆ๋‹ค. ํ•˜์ง€๋งŒ, ์„œ๋น„์Šค ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด์„œ๋Š” Jpa ๊ด€๋ จ ๋นˆ๊ณผ Service, Repository ๊ตฌํ˜„์ฒด์— ๋Œ€ํ•œ ๋นˆ๋“ค๋„ ํ•„์š”ํ•˜๋‹ค. ์œ„์˜ ์• ๋…ธํ…Œ์ด์…˜๋“ค์„ ํ™œ์šฉํ•œ๋‹ค๊ณ  ํ•ด๋„ ๋‹จํŽธ์ ์œผ๋กœ ๋นˆ์„ ๋“ฑ๋กํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ๊ฒฐ๊ตญ ์ด์ „์ฒ˜๋Ÿผ ๋นˆ์„ ๋“ฑ๋กํ•ด์ฃผ๋Š” Config inner class๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์Šคํ”„๋ง ์ปจํ…์ŠคํŠธ๋ฅผ ๋งŒ๋“ค๊ณ  ์ „์ฒด์ ์œผ๋กœ ๋นˆ์„ ๋งŒ๋“ค์–ด์ฃผ๋Š” ์• ๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ•  ์ˆ˜ ๋ฐ–์— ์—†์ง€ ์•Š์„๊นŒ? ๊ทธ ์—ญํ• ์„ ํ•˜๋Š” ์• ๋…ธํ…Œ์ด์…˜์ด ๋ฐ”๋กœ @SpringBootTest ์ด๋‹ค.

 

ํ•˜์ง€๋งŒ @SpringBootTest ๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๊ธฐ์—” ๋„ˆ๋ฌด๋‚˜๋„ ๋А๋ฆฌ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์„œ๋น„์Šค ๊ณ„์ธต์„ ๋„˜์–ด ์ปจํŠธ๋กค๋Ÿฌ์™€ ๊ธฐํƒ€ ์›น ๊ด€๋ จ ๋นˆ๋“ค๋„ ์ƒ์„ฑํ•˜๊ธฐ์— ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ์—์„œ๋‚˜ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์ง€, ์„œ๋น„์Šค์˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์—๋Š” ์ ํ•ฉํ•˜์ง€ ์•Š๋‹ค. ๊ทธ๋ž˜์„œ ์ด๋ฅผ ์–ด๋А์ •๋„ ์ œํ•œํ•˜๋Š” ์˜ต์…˜์„ ํ™œ์šฉํ–ˆ๋‹ค.

 

๋ฐ”๋กœ webEnvironment = SpringBootTest.WebEnvironment.NONE ์ด๋‹ค. ์ด ์˜ต์…˜์„ ๋„ฃ์œผ๋ฉด ์ด์ „์ฒ˜๋Ÿผ ์Šคํ”„๋ง ์ปจํ…์ŠคํŠธ๋ฅผ ๋งŒ๋“ค๊ณ  ๋นˆ๋“ค์„ ์ƒ์„ฑํ•˜์ง€๋งŒ ๊ทธ ์ด์ƒ์˜ ์›น ์š”์ฒญ/์‘๋‹ต ํ๋ฆ„์˜ ๊ด€๋ จ ๋นˆ๋“ค์€ ์ƒ์„ฑํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ฆ‰, ๋””์ŠคํŒจ์ฒ˜ ์„œ๋ธ”๋ฆฟ, ๋ฆฌ์กธ๋ฒ„, ํ•ธ๋“ค๋Ÿฌ ๋“ฑ์˜ ๋นˆ์ด ์ƒ์„ฑ๋˜์ง€ ์•Š๋Š”๋‹ค! ์„œ๋น„์Šค ํ…Œ์ŠคํŠธ์— ์ ํ•ฉํ•œ ๊ฒƒ ๊ฐ™๋‹ค. ์ฝ”๋“œ๋กœ ํ‘œํ˜„ํ•œ๋‹ค๋ฉด ๋‹ค์Œ์ฒ˜๋Ÿผ ๋  ๊ฒƒ์ด๋‹ค.

 

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
class ReservationApplicationServiceTest {

		// ... ํ•„์š”ํ•œ ์ฃผ์ž… ๋ฐ ํ…Œ์ŠคํŠธ ...
}

 

์ด๋กœ์จ ๋ฌธ์ œ์  1๋ฒˆ๊ณผ 2๋ฒˆ์„ ๋ชจ๋‘ ํ•ด๊ฒฐํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ, ๊ฐ„๊ณผํ•œ ๊ฒƒ์ด ํ•˜๋‚˜ ์žˆ๋‹ค. ๋ฐ”๋กœ ๊ณต์œ  ์˜์กด์„ฑ์ด๋‹ค.

 

๊ณต์œ  ์˜์กด์„ฑ์€ ํ…Œ์ŠคํŠธ ๊ฐ„์— ์„œ๋กœ ๊ณต์œ ๋˜๋Š” ์˜์กด์„ฑ์œผ๋กœ ํ•˜๋‚˜์˜ ํ…Œ์ŠคํŠธ์—์„œ ์ˆ˜ํ–‰ํ•œ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ์—๋„ ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š” ๊ฒƒ์ด๋‹ค. ๋Œ€ํ‘œ์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์žˆ๋‹ค. @DataJpaTest๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ๋Š” ๋‚ด๋ถ€์— @Transactional์ด ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ๋งˆ๋‹ค ๋กค๋ฐฑ๋˜์–ด DB๊ฐ€ ๊ณต์œ  ์˜์กด์„ฑ์ด ์•„๋‹ˆ๊ฒŒ ๋˜์—ˆ์ง€๋งŒ ์ง€๊ธˆ์˜ ํ•ด๊ฒฐ๋ฐฉ์‹์€ ๋‚ด๋ถ€์ ์œผ๋กœ๋‚˜ ์™ธ๋ถ€์ ์œผ๋กœ @Transactional์ด ์—†์–ด์„œ ๊ณต์œ  ์˜์กด์„ฑ์ด ๋˜์—ˆ๋‹ค.

 

๊ทธ๋ ‡๋‹ค๊ณ  ์„œ๋น„์Šค ํ…Œ์ŠคํŠธ์— @Transactional์„ ๋ถ™์ด๋Š” ๊ฒƒ์€ ์œ„ํ—˜ํ•˜๋‹ค. ์™œ๋ƒ๋ฉด, ์„œ๋น„์Šค ๋กœ์ง ์ƒ ํŠธ๋žœ์žญ์…˜์„ ์˜๋„ํ•˜์ง€ ์•Š์•˜์Œ์—๋„ ํ…Œ์ŠคํŠธ์—๋Š” ํŠธ๋žœ์žญ์…˜์ด ๋ถ™๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฑฐ์ง“ ์–‘์„ฑ์ด ๋  ํ™•๋ฅ ์ด ๋†’์•„์ง€๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ์‚ฌ์‹ค ๋ฐฉ๋ฒ•์€ ํ•˜๋‚˜ ๋ฟ์ด๋‹ค. ๋ฐ”๋กœ ์ง์ ‘ DB๋ฅผ ๋กค๋ฐฑํ•˜๋Š” ๊ฒƒ!!

 

@SpringBootTest(webEnvironment = WebEnvironment.NONE)
public abstract class BaseServiceTest {

    @Autowired
    private DatabaseCleaner databaseCleaner;

    @BeforeEach
    void setUp() {
        databaseCleaner.clean();
    }
}
@Component
public class DatabaseCleaner {

    private static final String FOREIGN_KEY_SET_ON = "SET REFERENTIAL_INTEGRITY TRUE";
    private static final String FOREIGN_KEY_SET_OFF = "SET REFERENTIAL_INTEGRITY FALSE";
    private static final String TRUNCATE_TABLE_QUERY = "TRUNCATE TABLE %s RESTART IDENTITY";

    @PersistenceContext
    private EntityManager entityManager;
    private List<String> tableNames;

    @PostConstruct
    void setTableNames() {
        tableNames = entityManager.getMetamodel().getEntities().stream()
            .filter(entityType -> entityType.getJavaType().getAnnotation(Entity.class) != null)
            .map(entityType -> CaseFormat.convertCamelToSnake(entityType.getName()).toLowerCase())
            .filter(tableName -> !(tableName.equals("member") || tableName.equals("admin")))
            .toList();
    }

    @Transactional
    void clean() {
        entityManager.createNativeQuery(FOREIGN_KEY_SET_OFF).executeUpdate();
        for (String tableName : tableNames) {
            entityManager.createNativeQuery(String.format(TRUNCATE_TABLE_QUERY, tableName)).executeUpdate();
        }
        entityManager.createNativeQuery(FOREIGN_KEY_SET_ON).executeUpdate();

        entityManager.flush();
        entityManager.clear();
    }
}

 

๊ฒฐ๊ณผ๋ถ€ํ„ฐ ๋ณด์ž๋ฉด ๋‚˜๋Š” ์ด๋ ‡๊ฒŒ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๊ฐœ์„ ํ–ˆ๋‹ค. ๋ชจ๋“  ์„œ๋น„์Šค ํ…Œ์ŠคํŠธ๊ฐ€ ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ๊ฐ€ ์‹œ์ž‘ํ•˜๊ธฐ ์ „ @BeforeEach๋กœ DB๋ฅผ ๋กค๋ฐฑ์‹œํ‚ฌํ…Œ๊ณ , @SpringBootTest(webEnvironment = WebEnvironment.NONE) ์• ๋…ธํ…Œ์ด์…˜์ด ๋ถ™์„ํ…Œ๋‹ˆ๊นŒ ์ด๋ฅผ ํ•˜๋‚˜์˜ ํด๋ž˜์Šค๋กœ ๋ฌถ์–ด์คฌ๋‹ค. ๊ทธ๋ฆฌ๊ณ , ์„œ๋น„์Šค ํ…Œ์ŠคํŠธ๊ฐ€ ์ด๋ฅผ ์ƒ์†ํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ๊ตฌํ˜„ํ–ˆ๋‹ค.

 

์ด๋กœ์จ ์„œ๋น„์Šค ํ…Œ์ŠคํŠธ๋Š” BaseServiceTest๋งŒ ์ƒ์† ๋ฐ›์œผ๋ฉด ์ง€์ €๋ถ„ํ•œ Config ํŒŒ์ผ์ด ํ•„์š”์—†๋‹ค๋Š” ์ ๊ณผ @DataJpaTest ์• ๋…ธํ…Œ์ด์…˜ ์ œ๊ฑฐ๋ฅผ ์ด๋ค„๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

 

@DataJpaTest
@SpringBootTest

 

์•„์‰ฝ๊ฒŒ๋„ ์‹œ๊ฐ„ ์ž์ฒด๋Š” ์ข€ ๋Š˜์–ด๋‚ฌ์ง€๋งŒ, ์„œ๋น„์Šค ํ…Œ์ŠคํŠธ๋ผ๋Š” ๋ชฉ์ ์„ฑ์„ ๋‹ฌ์„ฑํ•  ์ˆ˜ ์žˆ์—ˆ์œผ๋‹ˆ ์˜๋ฏธ์žˆ๋Š” ๊ฐœ์„ ์ด๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค.