ํ•ด๋‹น ํฌ์ŠคํŒ…์€ ํ† ๋น„์˜ ์Šคํ”„๋ง 3.1์„ ์ฝ๊ณ  ์ฑ… ๋‚ด์šฉ๊ณผ ์‹ค์Šต ์ฝ”๋“œ ์ •๋ฆฌ ๋ฐ ์Šคํ„ฐ๋””์—์„œ ๋‚˜์˜จ ์˜๊ฒฌ์„ ์ •๋ฆฌํ•œ ํฌ์ŠคํŒ…์ด๋‹ค.

2์žฅ ํ…Œ์ŠคํŠธ

์Šคํ”„๋ง์„ ๊ตญ๋น„ํ•™์›์—์„œ ์ฒ˜์Œ ๋ฐฐ์šด ๋‚˜๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ ์กด์žฌํ•˜๋Š” ์ค„๋„ ๋ชฐ๋ž๋‹ค. ์ž์ž˜ํ•œ ์ฝ”๋“œ ํ•˜๋‚˜ ๊ณ ์น˜๋Š” ๋ฐ๋„ ๋Œ€์†Œ๋™์ด ์ผ์–ด๋‚ฌ๋‹ค. ํšŒ์›๊ฐ€์ž… ๊ธฐ๋Šฅ์„ ๋งŒ๋“œ๋Š”๋ฐ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ œํ•œ์ด ์ž˜ ์•ˆ๋˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค๋ฉด

  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ข…๋ฃŒํ•œ๋‹ค
  • ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜๊ณ  break point๋ฅผ ๊ฑธ๊ฑฐ๋‚˜ ์ถœ๋ ฅ๋ฌธ์„ ์จ๋‘”๋‹ค
  • ๋‹ค์‹œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ‚จ๋‹ค
  • ํฌ๋กฌ ์‹œํฌ๋ฆฟ + ๊ฐ•๋ ฅ ์ƒˆ๋กœ๊ณ ์นจ์œผ๋กœ ํŽ˜์ด์ง€์— ๋“ค์–ด๊ฐ„๋‹ค
  • ํšŒ์›๊ฐ€์ž…์„ ์‹œ๋„ํ•˜๊ณ  ๋‹ค์‹œ ํ™•์ธํ•œ๋‹ค

์ด๋Ÿฐ ๊ณผ์ •์„ ๊ฑฐ์ณ์•ผ ํ–ˆ๋‹ค.

ํ…Œ์ŠคํŠธ๊ฐ€ ์ผ์ƒ์ด ๋œ ์ง€๊ธˆ์€ ํ…Œ์ŠคํŠธ๊ฐ€ ์—†์œผ๋ฉด ๋ถˆ์•ˆํ•˜๋‹ค. (๋ ˆ๋ฒจ4 ๋ฏธ์…˜์„ ํ•˜๋ฉด์„œ ์†Œํ™€ํ•ด์ง€๊ธด ํ–ˆ์ง€๋งŒโ€ฆ) ํ…Œ์ŠคํŠธ๋ฅผ ๋ฐฐ์šด ์ฒ˜์Œ์—” ์‹ ๊ธฐํ–ˆ๊ณ , ๊ทธ ๋‹ค์Œ์—” ์กฐ๊ธˆ ๊ท€์ฐฎ์•˜๋Š”๋ฐ, ์ง€๊ธˆ์€ ํ…Œ์ŠคํŠธ๊ฐ€ ์—†์œผ๋ฉด ์ค„ ์—†์ด ๋ฒˆ์ง€์ ํ”„ํ•˜๋Š” ๊ธฐ๋ถ„์ด ๋“ ๋‹ค. ํ…Œ์ŠคํŠธ๋Š” ๋‚˜์—๊ฒŒ ์ž์œ ๋กญ๊ณ  ์žฌ๋ฐŒ๊ฒŒ ์ฝ”๋“œ๋ฅผ ๊ฐ–๊ณ  ๋†€๊ฒŒ ํ•ด์ฃผ๋Š” ์•ˆ์ „์žฅ์น˜์ธ ์…ˆ์ด๋‹ค.

์ค์ค ์ „์ฒด ํ…Œ์ŠคํŠธ

ํ˜„์žฌ ๊ฐœ๋ฐœ์ค‘์ธ ์ค์ค์˜ ์ „์ฒด ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์ฝ˜์†” ํ™”๋ฉด. ํŽธ-์•ˆํ•˜๋‹ค.

์ดˆ๋‚œ๊ฐ UserDaoTest ๊ฐœ์„ ๊ธฐ

1์žฅ์—์„œ ๋งŒ๋“ค์—ˆ๋˜ UserDaoTest๋Š” ๋‚ด๊ฐ€ ์˜ˆ์ „์— ํ–ˆ๋˜ ํœด๋จผ-ํ…Œ์ŠคํŠธ๋ณด๋‹จ ๋‚ซ์ง€๋งŒ ์—ฌ์ „ํžˆ ์ดˆ๋‚œ๊ฐํ•œ ํ…Œ์ŠคํŠธ๋‹ค.

public class UserDaoTest {

    public static void main(String[] args) throws SQLException {

        UserDao userDao = new DaoFactory().userDao();

        User user = new User("hyewoncc4", "์ตœํ˜œ์›", "password");
        userDao.add(user);

        System.out.println("๋“ฑ๋ก ์„ฑ๊ณต");

        User foundUser = userDao.get(user.getId());
        System.out.println(foundUser.getName());
        System.out.println(foundUser.getPassword());
        System.out.println(foundUser.getId() + " ์กฐํšŒ ์„ฑ๊ณต");
    }
}

  1. ์ˆ˜๋™์œผ๋กœ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค

์ด ์ฝ”๋“œ๋Š” ์ž๋™ํ™”๊ฐ€ ๋œ ๋˜์—ˆ๋‹ค. ์ฝ˜์†” ์ฐฝ์— ์ฐํžŒ getName(), getPassword()๊ฐ€ ์ •๋ง ์ €์žฅ ๋œ ๊ฐ’๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€๋Š” ๊ฒฐ๊ตญ ์‚ฌ๋žŒ์ด ๋ณด๊ณ  ํŒ๋‹จํ•ด์•ผ ํ•œ๋‹ค.

  1. ์‹คํ–‰ ์ž‘์—…์ด ๋ฒˆ๊ฑฐ๋กญ๋‹ค

ํ˜„์žฌ๋Š” ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค๊ฐ€ ํ•˜๋‚˜๋ฟ์ด์ง€๋งŒ, ์ด๋Ÿฐ ์‹์œผ๋กœ ๋‚ด๋ถ€์— main()์„ ํ†ตํ•ด ํ…Œ์ŠคํŠธํ•˜๋Š” ํด๋ž˜์Šค๊ฐ€ ๋Š˜์–ด๋‚œ๋‹ค๋ฉด ์ผ์ผํžˆ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ๋„ ๋ณดํ†ต ์ผ์ด ์•„๋‹ˆ๊ฒŒ ๋  ๊ฒƒ์ด๋‹ค.

์ฒซ๋ฒˆ์งธ ๋ฌธ์ œ๋Š” ์ฝ”๋“œ๋ฅผ ๊ฐœ์„ ํ•ด ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

public static void main(String[] args) throws SQLException {
    
    UserDao userDao = new DaoFactory().userDao();

    userDao.add(user);
    User user = new User("hyewoncc4", "์ตœํ˜œ์›", "password");    
    User foundUser = userDao.get(user.getId());
    
    if (!user.getName().equals(foundUser.getName())) {
        System.out.println("ํ…Œ์ŠคํŠธ ์‹คํŒจ : name");  
    }
    else if (!user.getPassword().equals(foundUser.getPassword())) {
        System.out.println("ํ…Œ์ŠคํŠธ ์‹คํŒจ : password");  
    } else {
        System.out.println("์กฐํšŒ ํ…Œ์ŠคํŠธ ์„ฑ๊ณต");
    }
}

์ด์ œ ์ฝ˜์†”์ฐฝ์—์„œ ๊ฐ’์„ ํ™•์ธํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค. ํ•˜์ง€๋งŒ ์—ฌ์ „ํžˆ ๋ฌธ์ œ๊ฐ€ ๋‚จ์•„์žˆ๋‹ค.


Junit ์ ์šฉํ•˜๊ธฐ

์ด ํ…Œ์ŠคํŠธ๋ฅผ Junit ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ด์šฉํ•ด ๋ฐ”๊ฟ”๋ณด์ž. ์ฑ…์—์„œ๋Š” Junit4๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์ง€๋งŒ, Junit5๋กœ ์˜ˆ์ œ๋ฅผ ๋”ฐ๋ผํ–ˆ๋‹ค.

testImplementation("org.junit.jupiter:junit-jupiter-api")

1์žฅ์˜ xml๋ถ€๋ถ„์€ ์ฝ”๋“œ๋กœ ๋”ฐ๋ผํ•˜์ง€ ์•Š์•˜๊ณ , @Configuration์„ ํ†ตํ•ด ๋นˆ์„ ์ƒ์„ฑํ•ด์„œ ์ฑ…๊ณผ ๋‹ค๋ฅธ ๋ถ€๋ถ„์ด ์žˆ๋‹ค.

class UserDaoTest {

    @DisplayName("์‚ฌ์šฉ์ž ์ถ”๊ฐ€ ๋ฐ ์กฐํšŒ")
    @Test
    void addAndGet() throws SQLException {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(DaoFactory.class);
        UserDao userDao = applicationContext.getBean("userDao", UserDao.class);

        User user = new User("yellow-cat", "๋…ธ๋ž€ ๊ณ ์–‘์ด", "password");
        userDao.add(user);

        User foundUser = userDao.get(user.getId());
        assertEquals(user.getName(), foundUser.getName());
        assertEquals(user.getPassword(), foundUser.getPassword());
    }
}

ํ…Œ์ŠคํŠธ๋ฅผ ๋Œ๋ฆฌ๋ฉด ํ•œ๋ฒˆ๋งŒ ์ •์ƒ์ ์œผ๋กœ ๋Œ์•„๊ฐ„๋‹ค. ๋‹ค์‹œ ์‹คํ–‰ํ•˜๋ฉด ์œ ๋‹ˆํฌ ๊ฐ’ ์ค‘๋ณต์œผ๋กœ insert์— ์‹คํŒจํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ตœ์ดˆ ํ…Œ์ŠคํŠธ ์„ฑ๊ณต
์ค‘๋ณต ํ…Œ์ŠคํŠธ ์‹คํŒจ

ํ…Œ์ŠคํŠธ๋ฅผ ์„ฑ๊ณต์‹œํ‚ค๋ ค๋ฉด DB์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋น„์›Œ์ค˜์•ผ ํ•œ๋‹ค. ์ด๋ฅผ UserDao์— deleteAll(), getCount() ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“ฆ์œผ๋กœ์จ ๋ณด์ถฉํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ฑ… ์˜ˆ์ œ์˜ ํ…Œ์ŠคํŠธ ์Šคํƒ€์ผ์ด ๋งž์ง€ ์•Š์•„, ๊ฒฐ๊ตญ ์Šคํ”„๋ง ๋ถ€ํŠธ ์Šคํƒ€ํ„ฐ ํ…Œ์ŠคํŠธ๋กœ ์˜์กด์„ฑ์„ ๋ณ€๊ฒฝํ•˜๊ณ  ํ‰์†Œ ์Šคํƒ€์ผ๋Œ€๋กœ ์ž‘์„ฑํ–ˆ๋‹คโ€ฆ

testImplementation("org.springframework.boot:spring-boot-starter-test")
class UserDaoTest {

    @DisplayName("์‚ฌ์šฉ์ž ์ถ”๊ฐ€ ๋ฐ ์กฐํšŒ")
    @Test
    void addAndGet() throws SQLException {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(DaoFactory.class);
        UserDao userDao = applicationContext.getBean("userDao", UserDao.class);

        userDao.deleteAll();
        assertThat(userDao.getCount()).isEqualTo(0);

        User user = new User("yellow-cat", "๋…ธ๋ž€ ๊ณ ์–‘์ด", "password");
        userDao.add(user);
        assertThat(userDao.getCount()).isEqualTo(1);

        User foundUser = userDao.get(user.getId());
        assertThat(foundUser.getName()).isEqualTo(user.getName());
        assertThat(foundUser.getPassword()).isEqualTo(user.getPassword());
    }
}

์ฑ…์—๋Š” User๋ฅผ ๋‘ ๋ฒˆ ์ถ”๊ฐ€ํ•˜๊ณ  getCount()์˜ ๊ฐ’ ์ฆ๊ฐ€๋ฅผ ํ™•์ธํ•˜๊ณ , ๋‘ User์˜ ๊ฐ’์„ ๊ฐ๊ฐ ์ƒ์„ธ ๊ฒ€์ฆํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์ถ”๊ฐ€๋˜์–ด ์žˆ๋‹ค.


TDD๋กœ ๊ฐœ๋ฐœํ•˜๋Š” ์˜ˆ์™ธ์ฒ˜๋ฆฌ

์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ TDD๋กœ ์†Œ๊ฐœํ•œ ๋ถ€๋ถ„์ด ์ธ์ƒ๊นŠ์—ˆ๋‹ค. ํ˜„์žฌ ์ฝ”๋“œ์—์„œ userDao.get(์กด์žฌํ•˜์ง€ ์•Š๋Š” id)๋ฅผ ํ•˜๋ฉด EmptyResultDataAccessException์„ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์‹ถ๋‹ค๊ณ  ํ•˜์ž. ๊ทธ๋Ÿผ ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘๋˜์–ด์•ผ ํ•  ํ…Œ์ŠคํŠธ๋ฅผ ๋จผ์ € ์ž‘์„ฑํ•œ๋‹ค.

@DisplayName("์กด์žฌํ•˜์ง€ ์•Š๋Š” id ์กฐํšŒ ์‹œ ์˜ˆ์™ธ")
@Test
void get_idDoesNotExists_throwException() throws SQLException {
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(DaoFactory.class);
    UserDao userDao = applicationContext.getBean("userDao", UserDao.class);

    userDao.deleteAll();

    assertThatThrownBy(() -> userDao.get("unknown_id"))
        .isInstanceOf(EmptyResultDataAccessException.class);
}

์ด ํ…Œ์ŠคํŠธ๋Š” ๋‹ค๋ฅธ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด์„œ ์‹คํŒจํ•œ๋‹ค.

java.lang.AssertionError: 
Expecting:
  <java.sql.SQLException: Illegal operation on empty result set.>
to be an instance of:
  <org.springframework.dao.EmptyResultDataAccessException>
but was:
  <"java.sql.SQLException: Illegal operation on empty result set.

์ด์ œ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผ์‹œํ‚ฌ ์ฝ”๋“œ๋ฅผ ๊ฐœ๋ฐœํ•˜๋ฉด ๋œ๋‹ค. id๋กœ User๋ฅผ ์กฐํšŒํ–ˆ์„ ๋•Œ ๊ฒฐ๊ณผ๊ฐ€ ์—†๋‹ค๋ฉด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์ž.

public User get(final String id) throws SQLException {
    Connection connection = dataSource.getConnection();

    PreparedStatement statement = connection.prepareStatement(
        "select id, name, password from users where id = ?"
    );
    statement.setString(1, id);

    ResultSet resultSet = statement.executeQuery();
    User user = null;

    if (resultSet.next()) {
        user = new User();
        user.setId(resultSet.getString("id"));
        user.setName(resultSet.getString("name"));
        user.setPassword(resultSet.getString("password"));
    }

        resultSet.close();
        statement.close();
        connection.close();

    if (user == null) {
        throw new EmptyResultDataAccessException(1);
    }

    return user;
}

ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.


@Before๊ณผ Junit ์ƒ๋ช…์ฃผ๊ธฐ

ํ˜„์žฌ ํ…Œ์ŠคํŠธ๋Š” userDao ๋นˆ์„ ์ปจํ…Œ์ด๋„ˆ์—์„œ ๊ฐ€์ ธ์˜ค๋Š” ๊ณผ์ •์ด ์ค‘๋ณต๋˜์–ด์žˆ๋‹ค. ์ด ์ค‘๋ณต์„ Junit์˜ @Before, @BeforeEach๋ฅผ ์ด์šฉํ•ด ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ๋‹ค.

class UserDaoTest {

    private UserDao userDao;

    @BeforeEach
    void setUp() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(DaoFactory.class);
        userDao = applicationContext.getBean("userDao", UserDao.class);
    }
    
    @Test
    void addAndGet() {...}

    @Test
    void get_idDoesNotExists_throwException() {...}
}

์›๋ž˜ @Before๋„ ์‚ฌ์šฉํ•ด๋ณด๋ ค๊ณ  ํ–ˆ๋Š”๋ฐ, setUp()์ด ํ˜ธ์ถœ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค. Junit4์—์„œ๋Š” ๋ชจ๋‘ public์ด์–ด์•ผ ํ–ˆ๋˜ ๊ฒƒ ๊ฐ™์•„์„œ ํด๋ž˜์Šค์™€ ๋ฉ”์„œ๋“œ ์ ‘๊ทผ์ œ์–ด์ž๋ฅผ ๋ฐ”๊พธ๊ธฐ ๋“ฑ๋“ฑ ์‹œ๋„ํ•ด๋ณด์•˜๋Š”๋ฐ ์ž˜ ์•ˆ๋๋‹คโ€ฆ ์–ด์จŒ๋“  addAndGet(), get_idDoesNotExists_throwException()์ด ๊ฐ๊ฐ ์‹คํ–‰๋  ๋•Œ ์ƒˆ๋กœ์šด UserDaoTest ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ๊ธฐ๊ณ  setUp()์ด ํ˜ธ์ถœ๋œ๋‹ค. ์ด๋Š” ํ…Œ์ŠคํŠธ ๊ฐ„ ๊ฒฉ๋ฆฌ๋ฅผ ์œ„ํ•œ Junit ๊ธฐ๋ณธ ์ŠคํŽ™์ด๋‹ค. ๋งŒ์•ฝ ํ…Œ์ŠคํŠธ๊ฐ€ ํ•˜๋‚˜์˜ ์ธ์Šคํ„ด์Šค์—์„œ ์ด๋ค„์ ธ์•ผ ํ•œ๋‹ค๋ฉด ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.

@TestInstance(Lifecycle.PER_CLASS)
class UserDaoTest {
    ...

์ฑ…์—์„œ๋Š” ์ƒ์„ฑ๋˜๋Š” User ํ”ฝ์Šค์ณ๋„ setUp()์„ ํ†ตํ•ด ์„ค์ •ํ•˜๊ฒŒ ๋ฐ”๊ฟ”๋‘์—ˆ๋Š”๋ฐโ€ฆ ๊นƒํ—™์— ์˜ฌ๋ฆฌ์ง„ ์•Š์•˜๋‹ค. ๋งŽ์€ ๊ฒฝ์šฐ์— ํ”ฝ์Šค์ณ์™€ ์ด๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋Š” ์ฝ”๋“œ๋Š” ๊ฑฐ๋ฆฌ๊ฐ€ ๊ฐ€๊นŒ์šด ๊ฒŒ ์ข‹๋‹ค๋Š” ์ƒ๊ฐ ๋•Œ๋ฌธ์ด๋‹ค. ์•„๋งˆ @Before๋กœ ์ค‘๋ณต ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ๋ฐฉ์‹์„ ๋ณด์—ฌ์ฃผ๋ ค๊ณ  ์—ฌ๊ธฐ์—๋„ ์ ์šฉํ•˜์‹  ๊ฒƒ ๊ฐ™๋‹ค.


ํ…Œ์ŠคํŠธ ๊ฐ„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ ๊ณต์œ 

์†”์งํžˆ ์ด ๋ถ€๋ถ„์€ ๊ดœํžˆ ์ต์ˆ™ํ•œ ๋Œ€๋กœ ํ•˜๊ฒ ๋‹ค๊ณ  ํ•˜๋‹ค๊ฐ€โ€ฆ ์ฑ…์˜ ์„ค๋ช…์„ ์ž˜ ์ดํ•ดํ•˜์ง€ ๋ชปํ–ˆ๋‹ค. @RunWith(SpringJunit4ClassRunner.class)์™€ @SpringBootTest๊ฐ€ ๋น„์Šทํ•œ ์—ญํ• ์„ ํ•ด์ฃผ๋Š” ๊ฒƒ ๊ฐ™๋‹ค๋Š” ๊ฒƒ๋งŒ ์ถ”๋ก ํ–ˆ๋‹ค.

// ๊ทธ๋ฆฌ๊ณ  ์ด์ฏค์—์„œ DaoFactory ๋ผ๋Š” ์ด๋ฆ„์ด ๋ชน์‹œ ์ž˜๋ชป๋˜์—ˆ๋‹ค๋Š”๊ฒŒ ๋ณด์—ฌ์„œ AppConfig๋กœ ๋ฐ”๊ฟจ๋‹ค  
@SpringBootTest
@ContextConfiguration(classes = DaoFactory.class)
class UserDaoTest {
    
    @Autowired
    private ApplicationContext applicationContext;
    
    private UserDao userDao;
    
    @BeforeEach
    void setUp() {
        userDao = applicationContext.getBean("userDao", UserDao.class);
    }
    ...

์ฝ˜์†”๋กœ ๊ฐ ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ์—์„œ this(UserDaoTest์˜ ์ธ์Šคํ„ด์Šค)์™€ applicationContext๋ฅผ ์ฐ์–ด๋ณด๋ฉด, UserDaoTest๋Š” ๋งค๋ฒˆ ์ƒˆ ์ธ์Šคํ„ด์Šค๊ฐ€ ๋งŒ๋“ค์–ด์ง์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  applicationContext์€ ๊ฐ™์€ ์ธ์Šคํ„ด์Šค๊ฐ€ ์žฌํ™œ์šฉ๋จ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ApplicationContext๋„ ์Šคํ”„๋ง ๋นˆ์œผ๋กœ ๋“ฑ๋ก๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋งŒ์•ฝ ๋™์ผํ•˜๊ฒŒ @ContextConfiguration(classes = DaoFactory.class)๋งŒ ์‚ฌ์šฉํ•˜๋Š” (๊ทธ๋Ÿฌ๋‹ˆ๊นŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ ์„ค์ •์ด ๊ฐ™์€) ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค๊ฐ€ ์žˆ๋‹ค๋ฉด, ๊ทธ ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋“ค๊ณผ๋„ ๋™์ผํ•œ applicationContext๋ฅผ ๊ณต์œ ํ•œ๋‹ค. ์ธ์ˆ˜ํ…Œ์ŠคํŠธ๋ฅผ ๋ฐฐ์šฐ๋ฉฐ ์•Œ๊ฒŒ๋œ @DirtiesContext๋กœ ์ƒˆ๋กœ์šด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋„๋ก ๊ฐ•์ œํ•  ์ˆ˜ ์žˆ๋‹ค.

์•„์˜ˆ UserDao๋ฅผ ์ฃผ์ž…๋ฐ›๋„๋ก ๋ณ€๊ฒฝํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

@SpringBootTest
@ContextConfiguration(classes = AppConfig.class)
class UserDaoTest {

    @Autowired
    private UserDao userDao;
    ...

DataSource๋ฅผ ์ฃผ์ž…๋ฐ›์•„ ํ…Œ์ŠคํŠธ์—์„œ ๋‹ค๋ฅธ DB๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

@SpringBootTest
@ContextConfiguration(classes = TestAppConfig.class)
class UserDaoTest {

    @Autowired
    private DataSource dataSource;
    ...

@Configuration
public class TestAppConfig {

    @Bean
    public DataSource dataSource() {
        SimpleDriverDataSource dataSource = new SimpleDriverDataSource();

        dataSource.set...;

        return dataSource;
    }
}

์ฑ…๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์€ ์•„๋‹ˆ์ง€๋งŒ ์˜ˆ์ „์— ํ…Œ์ŠคํŠธ์—์„œ๋งŒ ๋นˆ ๊ฐˆ์•„ ๋ผ์šฐ๊ธฐ๋ฅผ ๊ณ ๋ฏผํ•œ ์  ์žˆ์–ด์„œ ๋ฐ˜๊ฐ€์› ๋‹ค.


์•„๋‹ˆ๋ฉด, ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์—†์• ๋ฒ„๋ฆฌ๊ธฐ

์ˆœ์ˆ˜ํ•˜๊ฒŒ UserDao๋งŒ ํ…Œ์ŠคํŠธ ํ•  ๊ฒƒ์ด๋ผ๋ฉด ์ด๋Ÿฐ ์‹์œผ๋กœ DI๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.

class UserDaoTest {

    private UserDao userDao;

    @BeforeEach
    void setUp() {
        userDao = new UserDao();
        userDao.setDataSource(dataSource());
    }
    
    private DataSource dataSource() {
        SimpleDriverDataSource dataSource = new SimpleDriverDataSource();

        dataSource.setDriverClass(com.mysql.cj.jdbc.Driver.class);
        dataSource.setUrl("jdbc:mysql://localhost:3306/springbook?serverTimezone=Asia/Seoul");
        dataSource.setUsername("spring");
        dataSource.setPassword("book");

        return dataSource;
    }
    ...

์šฐํ…Œ์ฝ” ๋ ˆ๋ฒจ1์—์„œ ์ฝ˜์†” ์ฒด์Šค ๊ฒŒ์ž„ ๋งŒ๋“ค๊ธฐ ๋ฏธ์…˜์„ ํ•  ๋•Œ, ์ฒด์ŠคํŒ์„ ๋œปํ•˜๋Š” Board ๊ฐ์ฒด๋ฅผ ํ…Œ์ŠคํŠธ์—์„œ DI๋ฅผ ์ด์šฉํ•ด ์ดˆ๊ธฐํ™”ํ•ด์คฌ๋‹ค.

public Board(final PiecesSetup piecesSetup) {
    pieces = new Pieces(piecesSetup);
}

public interface PiecesSetup {
    Map<Position, Piece> initialize();
}

@Test
@DisplayName("ํฐ์€ ์ƒ๋Œ€๊ธฐ๋ฌผ์ด ๋ชฉ์ ์ง€์— ์กด์žฌํ•˜๋ฉด ๋Œ€๊ฐ์„ ์œผ๋กœ ์›€์ง์ผ ์ˆ˜ ์žˆ๋‹ค")
void pawnCanMoveDiagonal_targetExist() {
    Piece whitePawn = new Piece(Color.WHITE, new Pawn());
    Board board = new Board(() -> {
        Map<Position, Piece> pieces = new HashMap<>();
        pieces.put(Position.of("a2"), whitePawn);
        pieces.put(Position.of("b3"), new Piece(Color.BLACK, new Pawn()));
        return pieces;
    });

    board.move("a2", "b3");
    assertThat(board.findPiece(Position.of("b3")).get()).isEqualTo(whitePawn);
}

์ด๋ ‡๊ฒŒ ํ…Œ์ŠคํŠธ ์ƒํ™ฉ์— ๋งž๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ์ž…ํ•ด ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. DI๊ฐ€ ์Šคํ”„๋ง์— ์ข…์†์ ์ธ ๊ธฐ์ˆ ์ด ์•„๋‹ˆ๋ผ๋Š” ๊ฑธ, ์ˆœ์ˆ˜(?) ์ž๋ฐ” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ตํ•ด ์ฒด๋“ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

์ „์ฒด ์‹ค์Šต ์ฝ”๋“œ๋Š” ๊นƒํ—™ topring ๋ ˆํฌ์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.


ํ•™์Šตํ…Œ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค์ž!

ํ† ํ”„๋ง ๋‚ด์šฉ ์š”์•ฝ์€ ์œ„์—์„œ ๊ฑฐ์˜ ๋๋‚ฌ๊ณ , ์—ฌ๊ธฐ๋Š” ๋ฒˆ์™ธ ๋Š๋‚Œ์ด๋‹ค. ํ•™์Šตํ…Œ์ŠคํŠธ๋กœ ํ•™์Šต์„ ํ•œ๋ฒˆ์ด๋ผ๋„ ํ•ด ๋ณธ ์‚ฌ๋žŒ๋“ค์€ ํ•™์Šตํ…Œ์ŠคํŠธ์˜ โœจ๋ฉ‹์งโœจ์„ ์•Œ ๊ฒƒ์ด๋‹ค. ๋‚˜๋Š” ํ‰์†Œ์— XXX-sandbox๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด ๊ณต๋ถ€ํ•œ ์ฝ”๋“œ๋ฅผ ์ •๋ฆฌํ•˜๊ณ , ๋ธ”๋กœ๊ทธ์— ์˜ฌ๋ฆด ์˜ˆ์ œ ์ฝ”๋“œ๋ฅผ ๊นƒํ—™์— ์˜ฌ๋ฆฐ๋‹ค. ์†”์งํžˆ ๊ณต๋ถ€๊ฐ€ ์ž˜๋˜๋Š” ๊ฑด ๋‘˜์งธ์น˜๊ณ โ€ฆ ์ •๋ง ํ›Œ๋ฅญํ•œ ์ปค๋‹ํŽ˜์ดํผ๊ฐ€ ๋˜์–ด์ค€๋‹ค. ์ฝ”ํ‹€๋ฆฐ์œผ๋กœ given/when/then ํ…Œ์ŠคํŠธ ์–ด๋–ป๊ฒŒ ์ž‘์„ฑํ–ˆ๋”๋ผ? ํ•˜๋ฉด ๊ตฌ๊ธ€์—์„œ ๊ฒ€์ƒ‰ํ•˜๊ณ  ๋ธ”๋กœ๊ทธ์˜ ์ฝ”๋“œ๋ธ”๋Ÿญ ๊ธ์–ด์˜ค๊ณ  ํ•  ํ•„์š” ์—†์ด ๊ทธ๋ƒฅ ํ”„๋กœ์ ํŠธ ์ผœ์„œ ๋ณด๋ฉด ๋œ๋‹ค.

internal class BehaviorTest: BehaviorSpec({

    given("when์œผ๋กœ ๋™๋ฌผ ์†Œ๋ฆฌ๋ฃฐ ์ฐพ์œผ๋ฉด") {
        val grammar = Grammar()

        `when`("๊ณ ์–‘์ด๋Š”") {
            val name = "cat"
            val sound = grammar.whenAnimalSound(name)
            then("์•ผ์˜น์ด ๋‚˜์˜จ๋‹ค") {
                sound shouldBe "meow"
            }
        }

        `when`("๊ฐœ๋Š”") {
            val name = "dog"
            val sound = grammar.whenAnimalSound(name)
            then("๋ฉ๋ฉ์ด ๋‚˜์˜จ๋‹ค") {
                sound shouldBe "woff"
            }
        }

        `when`("์ด๋ฆ„ ๋ชจ๋ฅผ ๋™๋ฌผ์€") {
            val name = "x"
            val sound = grammar.whenAnimalSound(name)
            then("unknown์ด ๋‚˜์˜จ๋‹ค") {
                sound shouldBe "unknown"
            }
        }
    }
})

๋ง‰์ƒ ์˜ฌ๋ฆฌ๋ ค๋‹ˆ ๋ถ€๋„๋Ÿฝ๊ธดํ•œ๋ฐ ์–ด์จŒ๋“  ์ด๋Ÿฐ์‹์œผ๋กœ ์ •๋ฆฌํ•ด๋‘๊ณ  ์œ ์šฉํ•˜๊ฒŒ ๋ฒ ๋ผ๊ณ  ์žˆ๋‹ค.


์ด๋ฒˆ ์žฅ์€ ์ง€๋‚œ๋ฒˆ ๋ณด๋‹ค ๊ฐ€๋ณ๊ฒŒ ์ฝ์—ˆ๋‹ค. ํ…Œ์ŠคํŠธ๋ฅผ ๋ชจ๋ฅด๋˜ ๋•Œ ์ฝ์—ˆ๋‹ค๋ฉด ์–ด๋• ์„๊นŒ? ๊ฐ๋™๋ฐ›์•„์„œ ํ…Œ์ŠคํŠธ ์‹ ๋ด‰์ž๊ฐ€ ๋˜์—ˆ์„๊นŒ, ์•„๋‹ˆ๋ฉด ์ด๋Ÿฐ๊ฒŒ ์žˆ๊ตฌ๋‚˜ ํ•˜๊ณ  ๋„˜์–ด๊ฐ”์„๊นŒ. ์–ด์จŒ๋“  ์ง€๊ธˆ์€ ํ…Œ์ŠคํŠธ์—†๋Š” ๊ฐœ๋ฐœ์ด ๋ถ€์‹ค๊ณต์‚ฌ๋กœ ๋ณด์ธ๋‹ค. ๊ทธ๋Ÿฐ ์ฝ”๋“œ๋Š” ๋นจ๋ฆฌ ์ง„ํ–‰๋˜๋Š” ๊ฒƒ ๊ฐ™์•„๋„ ํ•œ๋ฒˆ ๋ฌด๋„ˆ์ง€๋ฉด ๊ฒ‰์žก์„ ์ˆ˜ ์—†๊ฒŒ ๋œ๋‹ค. ํ…Œ์ŠคํŠธ์˜ ๋ฉ‹์ง์„ ์•Œ๊ฒŒ ๋˜๊ณ  ๋‚˜์„œ๋Š” ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ๋ฆฌํŒฉํ„ฐ๋ง ํ•œ ๋’ค์— ์ „์ฒด ํ…Œ์ŠคํŠธ๋ฅผ ๋Œ๋ฆฌ๊ณ  ์ดˆ๋ก๋ถˆ์ด ์ผ์‚ฌ๋ถ„๋ž€ํ•˜๊ฒŒ ์ผœ์ง€๋Š” ๊ฑธ ๋ณด๋Š” ์žฌ๋ฏธ๋กœ ์ฝ”๋“œ๋ฅผ ์ง ๋‹ค. ๋‹ค์Œ์žฅ์€ ํ›‘์–ด๋ณด๋‹ˆ ๋งŒ๋งŒ์น˜ ์•Š์€ ๊ฒƒ ๊ฐ™์€๋ฐ ๋‹ค์Œ์ฃผ๊ฐ€ ๊ธฐ๋Œ€๋œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ํฌ์ŠคํŒ… ์ œ๋ชฉ์„ ์œ„ํŠธ์žˆ๊ฒŒ ์ง“๊ณ  ์‹ถ์—ˆ๋Š”๋ฐ ์•„๋ฌด ์ƒ๊ฐ๋„ ๋‚˜์ง€ ์•Š์•„์„œ ์ด๋ชจ์ง€๋‚˜ ๋ถ™์˜€๋‹ค. ํ…Œ์ŠคํŠธ์˜ ๋ฉ‹์ง์ด ์ „ํ•ด์ง€๋ฉด ์ข‹๊ฒ ๋‹คโ€ฆ