๐Ÿ“‹ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ VS ๋‹จ์œ„ ํ…Œ์ŠคํŠธ


ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ

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

์žฅ์ 

  • ์Šคํ”„๋ง ๋ถ€ํŠธ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋„์›Œ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์šด์˜ํ™˜๊ฒฝ๊ณผ ๊ฐ€์žฅ ์œ ์‚ฌํ•œ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ์ „์ฒด์ ์ธ Flow๋ฅผ ์‰ฝ๊ฒŒ ํ…Œ์ŠคํŠธ ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‹จ์ 

  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์„ค์ •, ๋ชจ๋“  Bean์„ ๋กœ๋“œํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‹œ๊ฐ„์ด ์˜ค๋ž˜๊ฑธ๋ฆฌ๊ณ  ๋ฌด๊ฒ๋‹ค.
  • ํ…Œ์ŠคํŠธ ๋‹จ์œ„๊ฐ€ ์ปค ๋””๋ฒ„๊น…์ด ์–ด๋ ต๋‹ค.

@SpringBootTest

  • ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋ณธ์ ์ธ ์Šคํ”„๋ง ๋ถ€ํŠธ ํ…Œ์ŠคํŠธ ์–ด๋…ธํ…Œ์ด์…˜.
@SpringBootTest
@ActiveProfiles("test")
@Transactional
class ChessServiceImplTest {
  // ...
}

@SpringBootTest ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋“ค

  • value: ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ์ ์šฉํ•  ํ”„๋กœํผํ‹ฐ ์ฃผ์ž….(๊ธฐ์กด์˜ ํ”„๋กœํผํ‹ฐ ์˜ค๋ฒ„๋ผ์ด๋“œ)
  • properties : ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— {key=value} ํ˜•์‹์œผ๋กœ ํ”„๋กœํผํ‹ฐ ์ถ”๊ฐ€.
  • classes : ApplicationContext์— ๋กœ๋“œํ•  ํด๋ž˜์Šค ์ง€์ •. (์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด @SpringBootConfiguration์„ ์ฐพ์•„์„œ ๋กœ๋“œ)
  • webEnvironment : ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹คํ–‰๋  ๋•Œ์˜ ์›น ํ™˜๊ฒฝ์„ ์„ค์ •. (๊ธฐ๋ณธ๊ฐ’์€ Mock ์„œ๋ธ”๋ฆฟ์„ ๋กœ๋“œํ•˜์—ฌ ๊ตฌ๋™)

โž• webEnvironment

  • MOCK : ServletContainer๋ฅผ ํ…Œ์ŠคํŠธ์šฉ์œผ๋กœ ๋„์šฐ์ง€์•Š๊ณ  ์„œ๋ธ”๋ฆฟ์„ mocking ํ•œ ๊ฒƒ์ด ๋™์ž‘ํ•œ๋‹ค. (๋‚ด์žฅ ํ†ฐ์บฃ์ด ๊ตฌ๋™๋˜์ง€ ์•Š๋Š”๋‹ค.)
    MockMvc๋Š” ๋ธŒ๋ผ์šฐ์ €์—์„œ ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ์˜๋ฏธํ•˜๋Š” ๊ฐ์ฒด๋กœ์„œ Controller ํ…Œ์Šคํ…Œ ์‚ฌ์šฉ์„ ์šฉ์ดํ•˜๊ฒŒ ํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • RANDOM_PORT : ์ž„์˜์˜ Port Listener. EmbeddedWebApplicationContext๋ฅผ ๋กœ๋“œํ•˜๋ฉฐ ์‹ค์ œ ์„œ๋ธ”๋ฆฟ ํ™˜๊ฒฝ์„ ๊ตฌ์„ฑ

@ActiveProfiles

์›ํ•˜๋Š” ํ”„๋กœํŒŒ์ผ ํ™˜๊ฒฝ ๊ฐ’ ์„ค์ •์ด ๊ฐ€๋Šฅํ•˜๋‹ค. (ํ”„๋กœํŒŒ์ผ ์ „๋žต)

@Transactional

ํ…Œ์ŠคํŠธ ์™„๋ฃŒ ํ›„ ์ž๋™์œผ๋กœ Rollback ์ฒ˜๋ฆฌ๊ฐ€ ๋œ๋‹ค.
ํ•˜์ง€๋งŒ WebEnvironment.RANDOM_PORT, DEFINED_PORT๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์‹ค์ œ ํ…Œ์ŠคํŠธ ์„œ๋ฒ„๋Š” ๋ณ„๋„์˜ ์Šค๋ ˆ๋“œ์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํŠธ๋žœ์žญ์…˜์ด ๋กค๋ฐฑ๋˜์ง€ ์•Š๋Š”๋‹ค. (์™œ?)

  • WebEnvironment.RANDOM_PORT, DEFINED_PORT ์‹ค์ œ ์›น ์‹คํ–‰ ํ™˜๊ฒฝ์„ ๋„์šด๋‹ค.

๋‹จ์œ„ ํ…Œ์ŠคํŠธ

์žฅ์ 

  • WebApplication ๊ด€๋ จ๋œ Bean๋“ค๋งŒ ๋“ฑ๋กํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ณด๋‹ค ๋น ๋ฅด๋‹ค.
  • ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๊ธฐ ์–ด๋ ค์šด ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ ๊ฐ€๋Šฅํ•˜๋‹ค.

๋‹จ์ 

  • ์š”์ฒญ๋ถ€ํ„ฐ ์‘๋‹ต๊นŒ์ง€ ๋ชจ๋“  ํ…Œ์ŠคํŠธ๋ฅผ Mock ๊ธฐ๋ฐ˜์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‹ค์ œ ํ™˜๊ฒฝ์—์„œ๋Š” ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค.

@WebMvcTest

  • MVC๋ฅผ ์œ„ํ•œ ํ…Œ์ŠคํŠธ๋กœ, ์›น ์ƒ์—์„œ ์š”์ฒญ๊ณผ ์‘๋‹ต์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ.
  • MVC ๊ด€๋ จ๋œ ์„ค์ •์ธ @Controller, @ControllerAdvice, @JsonCompoent์™€ Filter, WebMvcConfiguer, HandlerMetohdAgumentResolver๋งŒ ๋นˆ์œผ๋กœ ๋“ฑ๋ก๋œ๋‹ค. (๋””์ŠคํŒจ์ณ ์„œ๋ธ”๋ฆฟ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์•„์ด๋“ค๋งŒ ์ฃผ์ž…๋ฐ›๋Š”๋‹ค.)
  • ๋•Œ๋ฌธ์— Service, Repository ์™€ ๊ฐ™์€ ์›น ๊ณ„์ธต ์•„๋ž˜ ๋นˆ๋“ค์€ ๋“ฑ๋ก๋˜์ง€ ์•Š์•„ ์˜์กด์„ฑ๋„ ๋Š๊ธด๋‹ค.
  • ํ…Œ์ŠคํŠธ์— ์‚ฌ์šฉํ•˜๋Š” ์˜์กด์„ฑ์ด ์žˆ๋‹ค๋ฉด @MockBean์œผ๋กœ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•œ๋‹ค.
@WebMvcTest(ChessController.class)
class ChessControllerTest {
    private final static long CHESS_GAME_TEST_ID = 0;

    @Autowired
    MockMvc mockMvc;

    @MockBean
    ChessService chessService;

    @Test
    @DisplayName("๊ฒŒ์ž„ ๋ฆฌ์ŠคํŠธ ์กฐํšŒ ํ…Œ์ŠคํŠธ")
    void getGames() throws Exception {
        List<ChessGameManager> chessGameManagers = new ArrayList<>();
        ChessGameManager chessGameManager = ChessGameManagerFactory.createRunningGame(CHESS_GAME_TEST_ID);
        chessGameManagers.add(chessGameManager);

        given(chessService.findRunningGames()).willReturn(new ChessGameManagerBundle(chessGameManagers));

        mockMvc.perform(get("/games"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.runningGames." + CHESS_GAME_TEST_ID)
                        .value("WHITE"));
    }

    @Test
    @DisplayName("์ƒˆ๋กœ์šด ๊ฒŒ์ž„ ์‹œ์ž‘ ํ…Œ์ŠคํŠธ")
    void gameStart() throws Exception {
        ChessGameManager chessGameManager = ChessGameManagerFactory.createRunningGame(CHESS_GAME_TEST_ID);

        given(chessService.start()).willReturn(chessGameManager);

        mockMvc.perform(get("/game/start"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(CHESS_GAME_TEST_ID))
                .andExpect(jsonPath("$.color").value("WHITE"))
                .andExpect(jsonPath("$.piecesAndPositions.size()").value(32));
    }

    @Test
    @DisplayName("๊ฒŒ์ž„ ์ ์ˆ˜ ์กฐํšŒ ํ…Œ์ŠคํŠธ")
    void getScore() throws Exception {
        ChessGameStatistics chessGameStatistics = ChessGameStatistics.createNotStartGameResult();

        given(chessService.getStatistics(CHESS_GAME_TEST_ID)).willReturn(chessGameStatistics);

        mockMvc.perform(get("/game/" + CHESS_GAME_TEST_ID + "/score"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.matchResult").value("๋ฌด์Šน๋ถ€"))
                .andExpect(jsonPath("$.colorsScore.size()").value(2));
    }

    @Test
    @DisplayName("ํ•ด๋‹น ๊ฒŒ์ž„ ๋กœ๋”ฉ ํ…Œ์ŠคํŠธ")
    void loadGame() throws Exception {
        ChessGameManager chessGameManager = ChessGameManagerFactory.createRunningGame(CHESS_GAME_TEST_ID);

        given(chessService.findById(CHESS_GAME_TEST_ID)).willReturn(chessGameManager);

        mockMvc.perform(get("/game/" + CHESS_GAME_TEST_ID + "/load"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(CHESS_GAME_TEST_ID))
                .andExpect(jsonPath("$.color").value("WHITE"))
                .andExpect(jsonPath("$.piecesAndPositions.size()").value(32));
    }

    @Test
    void movePiece() throws Exception {
        Gson gson = new Gson();
        String content = gson.toJson(new MoveRequestDto("a2", "a3"));

        given(chessService.isEnd(CHESS_GAME_TEST_ID)).willReturn(false);
        given(chessService.nextColor(CHESS_GAME_TEST_ID)).willReturn(Color.BLACK);

        mockMvc.perform(MockMvcRequestBuilders
                .post("/game/" + CHESS_GAME_TEST_ID + "/move")
                .content(content).header("Content-Type", "application/json"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.end").value(false))
                .andExpect(jsonPath("$.nextColor").value("BLACK"));
    }
}
  • given - willReturn : ํŠน์ •ํ–‰์œ„์— ๋Œ€ํ•œ ๋ฐ˜ํ™˜ ๊ฐ’์„ ์ง€์ •ํ•˜์—ฌ ์‹ค์ œ ๊ฐ์ฒด์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜๊ฒŒ ํ•œ๋‹ค.

@JdbcTest

  • JDBC ๊ธฐ๋ฐ˜ ๊ตฌ์„ฑ ์š”์†Œ์—๋งŒ ์ดˆ์ ์„ ๋งž์ถ˜ JDBC ํ…Œ์ŠคํŠธ ์–ด๋…ธํ…Œ์ด์…˜
  • ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ JdbcTemplate์ด ์ƒ์„ฑ๋œ๋‹ค.
  • ๊ธฐ๋ณธ์ ์œผ๋กœ ํŠธ๋žœ์žญ์…˜์ด ์ด๋ฃจ์–ด์ง„๋‹ค.
  • in-memory database๊ฐ€ ์„ค์ •๋œ๋‹ค.
@JdbcTest
class JdbcTemplateChessDaoTest {
    private static final long DEFAULT_CHESS_GAME_ID = 1;
    ChessGame chessGame;
    ChessGameManager sampleGame;

    private final JdbcTemplate jdbcTemplate;
    private final JdbcTemplateChessDao jdbcTemplateChessDao;

    @Autowired
    public JdbcTemplateChessDaoTest(JdbcTemplate jdbcTemplate, @Qualifier("dataSource") DataSource dataSource) {
        this.jdbcTemplate = jdbcTemplate;
        this.jdbcTemplateChessDao = new JdbcTemplateChessDao(jdbcTemplate, dataSource);
    }

    @BeforeEach
    void beforeEach() {
        String sample = "RKBQKBKRPPPPPPPP................................pppppppprkbqkbkr"; // move a2 a3 ํ•œ ๋ฒˆ ์ง„ํ–‰
        chessGame = new ChessGame(DEFAULT_CHESS_GAME_ID, WHITE, true, sample);
        sampleGame = ChessGameManagerFactory.loadingGame(chessGame);
    }

    @Test
    @DisplayName("์ฒด์Šค ๊ฒŒ์ž„์„ ์ €์žฅํ•œ๋‹ค.")
    void save() {
        long newId = jdbcTemplateChessDao.save(chessGame);
        assertThat(newId).isEqualTo(2);
    }

    @Test
    @DisplayName("id๋กœ ์ฒด์Šค ๊ฒŒ์ž„์„ ์ฐพ๋Š”๋‹ค.")
    void findById() {
        assertThat(jdbcTemplateChessDao.findById(DEFAULT_CHESS_GAME_ID).isPresent()).isTrue();
    }

    @Test
    @DisplayName("์ฒด์Šค ๊ฒŒ์ž„ ์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธํ•œ๋‹ค.")
    void update() {
        sampleGame.move(Position.of("a2"), Position.of("a4"));

        jdbcTemplateChessDao.update(new ChessGame(sampleGame));

        ChessGame expectedChessGame = jdbcTemplateChessDao.findById(DEFAULT_CHESS_GAME_ID).get();
        ChessGameManager expectedChessGameManager = ChessGameManagerFactory.loadingGame(expectedChessGame);
        Square a4 = expectedChessGameManager.getBoard().findByPosition(Position.of("a4"));
        assertThat(a4.getPiece().getClass()).isEqualTo(Pawn.class);
        assertThat(a4.getPiece().getColor()).isEqualTo(WHITE);
    }

    @Test
    void findAllOnRunning() {
        List<ChessGame> allOnRunning = jdbcTemplateChessDao.findAllOnRunning();

        assertThat(allOnRunning.size()).isEqualTo(1);
    }

    @Test
    void delete() {
        //when
        jdbcTemplateChessDao.delete(DEFAULT_CHESS_GAME_ID);

        //then
        try {
            jdbcTemplateChessDao.findById(DEFAULT_CHESS_GAME_ID);
        } catch (Exception e) {
        }
    }
}

์ฐธ๊ณ