πŸ“• Backend/DB Access

TestCode에 λ°μ΄ν„°λ² μ΄μŠ€ μ„œλ²„ 연동

Dongwoongkim 2023. 4. 15. 02:07

μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ„œλ²„μ—μ„œ μ‚¬μš©ν•˜λŠ” λ°μ΄ν„°λ² μ΄μŠ€ μ„œλ²„μ™€ ν…ŒμŠ€νŠΈμ—μ„œ μ‚¬μš©ν•˜λŠ” λ°μ΄ν„°λ² μ΄μŠ€ μ„œλ²„κ°€ λ™μΌν•˜λ©΄ ν…ŒμŠ€νŠΈμ— 영ν–₯을 끼칠 수 μžˆμŠ΅λ‹ˆλ‹€.

 

예λ₯Όλ“€λ©΄ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ„œλ²„μ—μ„œ μ‚¬μš©ν•˜λŠ” λ°μ΄ν„°λ² μ΄μŠ€ μ„œλ²„μ— Item ν…Œμ΄λΈ”μ— λͺ‡ 개의 데이터가 μ‘΄μž¬ν•œλ‹€κ³  ν•  λ•Œ, 

ν…ŒμŠ€νŠΈμ—μ„œ 이 λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό κ°€μ§€κ³  검색 λ©”μ†Œλ“œ findItems()λ₯Ό μ‹€ν–‰ν–ˆμ„ λ•Œ, 기쑴의 λ°μ΄ν„°λ“€λ‘œ 인해 ν…ŒμŠ€νŠΈμ— μ‹€νŒ¨ν•˜κ²Œ λ©λ‹ˆλ‹€.

@Test
void findItems() {
    //given
    Item item1 = new Item("itemA-1", 10000, 10);
    Item item2 = new Item("itemA-2", 20000, 20);
    Item item3 = new Item("itemB-1", 30000, 30);

    log.info("repository={}", itemRepository.getClass());
    itemRepository.save(item1);
    itemRepository.save(item2);
    itemRepository.save(item3);

    //λ‘˜ λ‹€ μ—†μŒ 검증
    test(null, null, item1, item2, item3);
    test("", null, item1, item2, item3);

    //itemName 검증
    test("itemA", null, item1, item2);
    test("temA", null, item1, item2);
    test("itemB", null, item3);

    //maxPrice 검증
    test(null, 10000, item1);

    //λ‘˜ λ‹€ 있음 검증
    test("itemA", 10000, item1);
}

void test(String itemName, Integer maxPrice, Item... items) {
    List<Item> result = itemRepository.findAll(new ItemSearchCond(itemName, maxPrice));
    assertThat(result).containsExactly(items);
}

 λ”°λΌμ„œ 이런 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄μ„œλŠ” ν…ŒμŠ€νŠΈλ₯Ό λ‹€λ₯Έ ν™˜κ²½κ³Ό μ² μ €ν•˜κ²Œ 뢄리해야 ν•©λ‹ˆλ‹€.

κ°€μž₯ κ°„λ‹¨ν•˜κ²ŒλŠ” ν…ŒμŠ€νŠΈ μ „μš© λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό λ³„λ„λ‘œ μš΄μ˜ν•˜λŠ” 방법이 μžˆμŠ΅λ‹ˆλ‹€.

  • jdbc:h2:tcp://localhost/~/test : μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ„œλ²„μ—μ„œ μ‚¬μš©ν•˜λŠ” λ°μ΄ν„°λ² μ΄μŠ€ μ„œλ²„ (local)
  • jdbc:h2:tcp://localhost/~/testcase : ν…ŒμŠ€νŠΈμ—μ„œ μ‚¬μš©ν•˜λŠ” μ „μš© λ°μ΄ν„°λ² μ΄μŠ€ μ„œλ²„ (test)

src/main/resources/application.properties 

spring.profiles.active=local
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa

src/test/resources/application.properties

spring.profiles.active=test
  spring.datasource.url=jdbc:h2:tcp://localhost/~/testcase
  spring.datasource.username=sa

 

ν…ŒμŠ€νŠΈμ™€ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ μ‚¬μš©ν•˜λŠ” λ°μ΄ν„°λ² μ΄μŠ€λ₯΄ λΆ„λ¦¬ν•΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ 끝이 μ•„λ‹™λ‹ˆλ‹€.

ν…ŒμŠ€νŠΈκ°€ λλ‚˜λ©΄ ν…ŒμŠ€νŠΈμ—μ„œ μƒμ„±ν•œ 데이터듀을 μ‚­μ œ ν•΄μ£Όμ–΄μ•Ό λ‹€μŒ ν…ŒμŠ€νŠΈμ— 영ν–₯을 λΌμΉ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

μ–΄λ–»κ²Œ ν•΄μ•Ό ν• κΉŒμš”?


Solution1. νŠΈλžœμž­μ…˜κ³Ό λ‘€λ°± μ „λž΅ 

이 λ•Œ 도움이 λ˜λŠ” 것이 λ°”λ‘œ νŠΈλžœμž­μ…˜μž…λ‹ˆλ‹€.

ν…ŒμŠ€νŠΈκ°€ λλ‚˜κ³  λ‚œ ν›„ νŠΈλžœμž­μ…˜μ„ κ°•μ œλ‘œ 둀백해버리면 데이터가 κΉ”λ”ν•˜κ²Œ μ§€μ›Œμ§‘λ‹ˆλ‹€.

λ§Œμ•½ ν…ŒμŠ€νŠΈ 싀행도쀑 쀑간에 ν…ŒμŠ€νŠΈκ°€ μ‹€νŒ¨ν•΄μ„œ 둀백을 ν˜ΈμΆœν•˜μ§€ λͺ»ν•΄λ„ νŠΈλžœμž­μ…˜ λͺ¨λ“œλ‘œ μ „ν™˜ν–ˆκΈ° λ•Œλ¬Έμ— μ»€λ°‹ν•˜μ§€ μ•Šμ€ 이상 λ°μ΄ν„°λ² μ΄μŠ€μ— λ°˜μ˜λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 

 

ν…ŒμŠ€νŠΈκ°€ μ‹€ν–‰ν•˜κΈ° μ „ μ΄λ ‡κ²Œ @BeforeEach둜 νŠΈλžœμž­μ…˜μ„ μ‹œμž‘ν•΄μ£Όκ³ ,

@Slf4j
@SpringBootTest
class ItemRepositoryTest {
    @Autowired
    ItemRepository itemRepository;

    @Autowired
    PlatformTransactionManager transactionManager;

    TransactionStatus status;

    @BeforeEach
    void init()
    {
        status = transactionManager.getTransaction(new DefaultTransactionDefinition());
    }

ν…ŒμŠ€νŠΈκ°€ λλ‚˜λ©΄ μ΄λ ‡κ²Œ @AfterEach둜 λ‘€λ°±ν•΄μ£Όλ©΄ λ©λ‹ˆλ‹€.

@AfterEach
void afterEach() {
    //MemoryItemRepository 의 경우 μ œν•œμ μœΌλ‘œ μ‚¬μš©
    if (itemRepository instanceof MemoryItemRepository) {
        ((MemoryItemRepository) itemRepository).clearStore();
    }

    transactionManager.rollback(status);
}
참고둜 νŠΈλžœμž­μ…˜ λ§€λ‹ˆμ €λŠ” μŠ€ν”„λ§ λΆ€νŠΈκ°€ application.properties에 λ“±λ‘λœ 정보λ₯Ό 톡해 DataSource와 λ”λΆˆμ–΄ μžλ™μœΌλ‘œ μƒμ„±λ˜κΈ° λ•Œλ¬Έμ— λ³„λ„λ‘œ λ“±λ‘ν•˜μ§€ μ•Šμ•„λ„ μž‘λ™ν•©λ‹ˆλ‹€.

Solution2. @Transactional

 νŠΈλžœμž­μ…˜ λ§€λ‹ˆμ €λ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠμœΌλ©΄μ„œ, νŠΈλžœμž­μ…˜μ„ μ μš©ν•˜λŠ” 방법이 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. λ°”λ‘œ AOP @Transactional 방식이죠.

ν…ŒμŠ€νŠΈ μ½”λ“œμ—λ„ λ§ˆμ°¬κ°€μ§€λ‘œ @Transactional을 μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 

@Transactional
@Slf4j
@SpringBootTest
class ItemRepositoryTest {
    @Autowired
    ItemRepository itemRepository;

//    @Autowired
//    PlatformTransactionManager transactionManager;
//
//    TransactionStatus status;
//
//    @BeforeEach
//    void init()
//    {
//        status = transactionManager.getTransaction(new DefaultTransactionDefinition());
//    }

    @AfterEach
    void afterEach() {
        //MemoryItemRepository 의 경우 μ œν•œμ μœΌλ‘œ μ‚¬μš©
        if (itemRepository instanceof MemoryItemRepository) {
            ((MemoryItemRepository) itemRepository).clearStore();
        }

//        transactionManager.rollback(status);
    }

 @Transactional을 ν…ŒμŠ€νŠΈμ—μ„œ μ‚¬μš©ν•˜λ©΄ 쑰금 νŠΉλ³„ν•˜κ²Œ λ™μž‘ν•©λ‹ˆλ‹€.

 

@Transactional이 적용된 ν…ŒμŠ€νŠΈ λ™μž‘ 방식 

TestCode에 @Transactional을 μ μš©ν–ˆμ„ λ•Œ λ™μž‘ 방식

@Transactional이 ν…ŒμŠ€νŠΈμ— 있으면 μŠ€ν”„λ§μ€ ν…ŒμŠ€νŠΈλ₯Ό νŠΈλžœμž­μ…˜ μ•ˆμ—μ„œ μ‹€ν–‰ν•˜κ³ , ν…ŒμŠ€νŠΈκ°€ λλ‚˜λ©΄ μžλ™μœΌλ‘œ 둀백을 μ‹œμΌœλ²„λ¦½λ‹ˆλ‹€!

λ”°λΌμ„œ ν…ŒμŠ€νŠΈκ°€ λλ‚œ ν›„ κ°œλ°œμžλŠ” λ”°λ‘œ 직접 데이터λ₯Ό μ‚­μ œν•˜μ§€ μ•Šμ•„λ„ λ©λ‹ˆλ‹€.

 

Q. λ§Œμ•½ ν…ŒμŠ€νŠΈ μ½”λ“œμ˜ ν…ŒμŠ€νŠΈ μ‹€ν–‰ λ‹¨κ³„μ—μ„œ μ„œλΉ„μŠ€λ‚˜ 리포지토리λ₯Ό ν˜ΈμΆœν•΄μ„œ μ‚¬μš©ν•˜λŠ”λ° μ„œλΉ„μŠ€λ‚˜ 리포지토리 계측에 @Transactional이 λΆ™μ–΄μžˆμœΌλ©΄ μ–΄λ–»κ²Œ μ²˜λ¦¬λ˜λŠ”κ°€? 

A. μ„œλΉ„μŠ€λ‚˜ 리포지토리 κ³„μΈ΅μ˜ νŠΈλžœμž­μ…˜λ„ ν…ŒμŠ€νŠΈμ—μ„œ μ‹œμž‘ν•œ νŠΈλžœμž­μ…˜μ— μ°Έμ—¬ν•˜κ²Œ λ˜μ–΄, ν…ŒμŠ€νŠΈμ˜ μ‹€ν–‰λΆ€ν„° λκΉŒμ§€ λ‚΄λΆ€ μ½”λ“œκ°€ 같은 νŠΈλžœμž­μ…˜μ„ μ‚¬μš©ν•©λ‹ˆλ‹€. 
Q. ν…ŒμŠ€νŠΈκ°€ 잘 μž‘λ™ν–ˆλŠ”μ§€ λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ ν™•μΈν•˜κ³  μ‹Άμ–΄ μ»€λ°‹ν•˜κ³  싢은 κ²½μš°μ—λŠ” μ–΄λ–»κ²Œ ν•˜λ©΄ λ˜λŠ”κ°€?

A. ν…ŒμŠ€νŠΈ λ©”μ†Œλ“œμœ„μ— @Commitμ΄λ‚˜ , @Rollback(false)둜 μ–΄λ…Έν…Œμ΄μ…˜μ„ λΆ™ν˜€μ£Όλ©΄ λ‘€λ°±ν•˜μ§€ μ•Šκ³  νŠΈλžœμž­μ…˜μ΄ μ»€λ°‹λ©λ‹ˆλ‹€.

Solution3. μž„λ² λ””λ“œ λͺ¨λ“œ DB

ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ₯Ό μ‹€ν–‰ν•˜κΈ° μœ„ν•΄μ„œ λ³„λ„μ˜ λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό μ„€μΉ˜ν•˜κ³ , μš΄μ˜ν•˜λŠ” 것은 μƒλ‹Ήνžˆ λ²ˆμž‘ν•œ μž‘μ—…μž…λ‹ˆλ‹€.

 λ‹¨μˆœνžˆ ν…ŒμŠ€νŠΈλ₯Ό 검증할 μš©λ„λ‘œλ§Œ μ‚¬μš©ν•˜κΈ° λ•Œλ¬Έμ— ν…ŒμŠ€νŠΈκ°€ λλ‚˜λ©΄ λ°μ΄ν„°λ² μ΄μŠ€μ˜ 데이터λ₯Ό λͺ¨λ‘ μ‚­μ œν•΄λ„ λœλ‹€. 더 λ‚˜μ•„κ°€μ„œ ν…ŒμŠ€νŠΈκ°€ λλ‚˜λ©΄ λ°μ΄ν„°λ² μ΄μŠ€ 자체λ₯Ό μ œκ±°ν•΄λ„ λ©λ‹ˆλ‹€. 이럴 λ•Œ μž„λ² λ””λ“œ λͺ¨λ“œλ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μž„λ² λ””λ“œ λͺ¨λ“œλŠ” λ³„λ„μ˜ λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό μ„€μΉ˜ν•˜μ§€ μ•Šκ³ , μ‚¬μš©ν•˜κ³  싢은 λ°μ΄ν„°λ² μ΄μŠ€ μ„œλ²„λ₯Ό JVM μ•ˆμ—μ„œ λ©”λͺ¨λ¦¬ λͺ¨λ“œλ‘œ λ™μž‘ν•˜κ²Œ ν•˜λŠ” λ°©μ‹μž…λ‹ˆλ‹€. μš°λ¦¬κ°€ μ§€κΈˆκΉŒμ§€ μ‚¬μš©ν•œ H2 DBλŠ” μžλ°”λ‘œ κ°œλ°œλ˜μ–΄ 있고, JVM μ•ˆμ—μ„œ λ©”λͺ¨λ¦¬ λͺ¨λ“œλ‘œ λ™μž‘ν•  수 μžˆλ„λ‘ μ§€μ›ν•©λ‹ˆλ‹€.

DBλ₯Ό μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ— λ‚΄μž₯ν•΄μ„œ ν•¨κ»˜ μ‹€ν–‰ν•œλ‹€κ³  ν•΄μ„œ μž„λ² λ””λ“œ λͺ¨λ“œλΌκ³  ν•©λ‹ˆλ‹€.

 

μž„λ² λ””λ“œ λͺ¨λ“œ 직접 μ‚¬μš©ν•˜κΈ°

 

μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ‹€ν–‰ μ½”λ“œμ— DataSourceλ₯Ό μˆ˜λ™μœΌλ‘œ λ©”λͺ¨λ¦¬λͺ¨λ“œλ‘œ 빈 등둝 

@Bean
@Profile("test")
public DataSource dataSource()
{
   log.info("λ©”λͺ¨λ¦¬ λ°μ΄ν„°λ² μ΄μŠ€ μ΄ˆκΈ°ν™”");
   DriverManagerDataSource dataSource = new DriverManagerDataSource();
   dataSource.setDriverClassName("org.h2.Driver");
   dataSource.setUrl("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1");
   dataSource.setUsername("sa");
   dataSource.setPassword("");
   return dataSource;
}
  • @Profile("test") : ν”„λ‘œν•„μ΄ test인 κ²½μš°μ—λ§Œ 적용. 즉 ν…ŒμŠ€νŠΈ μ½”λ“œμ—λ§Œ μ μš©ν•˜κ² λ‹€λŠ” 의미
  • DataSource 등둝할 λ•Œ jdbc:h2:mem:db 둜 μž‘μ„±ν•˜λ©΄ μž„λ² λ””λ“œ λͺ¨λ“œ(λ©”λͺ¨λ¦¬ λͺ¨λ“œ)둜 λ™μž‘ν•˜λŠ” H2 λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • DB_CLOSE_DELAY=-1 : μž„λ² λ””λ“œ λͺ¨λ“œμ—μ„œλŠ” λ°μ΄ν„°λ² μ΄μŠ€ 컀λ„₯μ…˜μ΄ 연결이 λͺ¨λ‘ λŠμ–΄μ§€λ©΄ λ°μ΄ν„°λ² μ΄μŠ€λ„ μ’…λ£Œλ˜λŠ” 데 -1둜 μ„€μ •ν•˜λ©΄ 이것을 λ°©μ§€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

사싀 μŠ€ν”„λ§λΆ€νŠΈλŠ” μž„λ² λ””λ“œ λ°μ΄ν„°λ² μ΄μŠ€μ— λŒ€ν•œ 섀정도 기본으둜 μ œκ³΅ν•˜λŠ” 덕뢄에

//  @Bean
// @Profile("test")
// public DataSource dataSource()
// {
//    log.info("λ©”λͺ¨λ¦¬ λ°μ΄ν„°λ² μ΄μŠ€ μ΄ˆκΈ°ν™”");
//    DriverManagerDataSource dataSource = new DriverManagerDataSource();
//    dataSource.setDriverClassName("org.h2.Driver");
//    dataSource.setUrl("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1");
//    dataSource.setUsername("sa");
//    dataSource.setPassword("");
//    return dataSource;
// }
spring.profiles.active=test
#spring.datasource.url=jdbc:h2:tcp://localhost/~/testcase
#spring.datasource.username=sa

 μ΄λ ‡κ²Œ λ°μ΄ν„°λ² μ΄μŠ€μ— μ ‘κ·Όν•˜λŠ” λͺ¨λ“  섀정정보λ₯Ό μ§€μš°κ²Œ 되면 μŠ€ν”„λ§ λΆ€νŠΈλŠ” μžλ™μœΌλ‘œ μž„λ² λ””λ“œ λͺ¨λ“œλ‘œ μ ‘κ·Όν•˜λŠ” λ°μ΄ν„°μ†ŒμŠ€λ₯Ό λ§Œλ“€μ–΄μ„œ μ œκ³΅ν•©λ‹ˆλ‹€.


<정리>

μ΄λ ‡κ²Œ ν…ŒμŠ€νŠΈμ½”λ“œμ—μ„œ λ°μ΄ν„°λ² μ΄μŠ€ μ„œλ²„μ— μ ‘κ·Όν•  λ•Œ 생길 수 μžˆλŠ” 문제 ( λ°μ΄ν„°λ² μ΄μŠ€μ˜ 데이터가 μœ μ§€λ˜μ–΄ ν…ŒμŠ€νŠΈμ— 영ν–₯을 λΌμΉ˜λŠ” 문제)λ₯Ό 톡해 ν•΄κ²°ν•΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€.

  • 첫 번째, μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ„œλ²„μ™€ ν…ŒμŠ€νŠΈ μ„œλ²„μ˜ λ°μ΄ν„°λ² μ΄μŠ€ 뢄리.
  • 두 번째, νŠΈλžœμž­μ…˜μ„ μ΄μš©ν•΄ ν…ŒμŠ€νŠΈκ°€ λλ‚˜λ©΄ ν…ŒμŠ€νŠΈ μ„œλ²„μ˜ λ°μ΄ν„°λ² μ΄μŠ€ λ‘€λ°±
  • μ„Έ 번째, TestCodeμ—μ„œ νŠΉλ³„ν•˜κ²Œ λ™μž‘ν•˜λŠ” @Transactional을 μ΄μš©ν•œ ν…ŒμŠ€νŠΈ ν›„ λ°μ΄ν„°λ² μ΄μŠ€μ˜ 데이터 λ‘€λ°±
  • λ„€ 번째, μž„λ² λ””λ“œ λͺ¨λ“œλ₯Ό 톡해 ν…ŒμŠ€νŠΈ ν›„ λ°μ΄ν„°λ² μ΄μŠ€ 제거.

<참고자료>

 

μŠ€ν”„λ§ DB 2편 - 데이터 μ ‘κ·Ό ν™œμš© 기술 - μΈν”„λŸ° | κ°•μ˜

λ°±μ—”λ“œ κ°œλ°œμ— ν•„μš”ν•œ DB 데이터 μ ‘κ·Ό κΈ°μˆ μ„ ν™œμš©ν•˜κ³ , μ™„μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μŠ€ν”„λ§ DB μ ‘κ·Ό 기술의 원리와 ꡬ쑰λ₯Ό μ΄ν•΄ν•˜κ³ , 더 κΉŠμ΄μžˆλŠ” λ°±μ—”λ“œ 개발자둜 μ„±μž₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€., - κ°•μ˜ μ†Œκ°œ | 인

www.inflearn.com