[워밍업 클럽 4기 - 백엔드] 4주차 발자국

image

개인일정이 있어 늦었지만 작성해봅니다!

 

 

 

과제 피드백 반영

일반과제

https://inf.run/yndvb

Day 18 미션 중 DRY가 무조건 안 좋은 것은 아니며 댓글에 대한 테스트이기 때문에 맥락이해를 떨어뜨릴 수 있는

게시글, 사용자 관련 로직은 BeforeEach로 분리해도 이해에 지장을 주지않는다는 것을 이해하고 적용.

 

 

지뢰찾기

https://github.com/jsween5723/readable-code/pull/1

  • 일반 생성자를 꼭 private으로 반영 해야하는 것은 아니다.

    Board(int rowCount, int columnCount, int mineCount) {
        this.mineCount = mineCount;
        this.cells = new Cell[rowCount][columnCount];
        this.rowCount = rowCount;
        this.columnCount = columnCount;
        initNormalCell();
        initMineCell();
    }
  • 테스트 코드 작성시 맥락을 이해하기 어렵다면 단락을 나눈다

        @Test
        @DisplayName("깃발이 달렸거나 이미 열린 셀은 열리지 않는다.")
        void openCellWithCoordinatesn() {
            //given
            BoardConfig config = MineSweeperGameLevel.BEGINNER.boardConfig;
            Board board = spy(Board.withConfig(config));

            Coordinate flaggedCoordinate = new Coordinate(1, 1);
            Cell flagCell = spy(Cell.normalCell());
            flagCell.toggleFlag();
            when(board.get(flaggedCoordinate)).thenReturn(flagCell);

            Coordinate openedCoordinate = new Coordinate(1, 2);
            Cell openedCell = spy(Cell.normalCell());
            openedCell.open();
            when(board.get(openedCoordinate)).thenReturn(openedCell);
            
            //when
            board.open(flaggedCoordinate);
            board.open(openedCoordinate);
            //then
            verify(flagCell, never()).open();
            verify(openedCell, times(1)).open();
        }
  • 너무 많지 않다면 Nested는 오히려 테스트 코드의 가독성을 떨어뜨린다.

    • if 블록과 비슷하다.

 

추가적인 리팩토링

  • public 메소드를 상위로 올리고 주요도 순으로 재배치함.

    public void open(Coordinate coordinate) {}

    public void toggleFlag(Coordinate coordinate) {}


    public boolean isCleared() {}

    public boolean isMineOpened() {}
  • Config을 필드로 들고있지 않고 값만 필드에 대입하도록 변경

public class Board {

    public static final int[][] DELTAS = new int[][]{{-1, -1}, {-1, 0}, {-1, +1}, {0, -1}, {0, +1},
        {1, -1}, {1, 0}, {1, +1}};
    private final Cell[][] cells;
    private final int rowCount;
    private final int columnCount;
    private final int mineCount;
    private final BoardStringGenerator stringGenerator = new BoardStringGenerator();
}
  • String 생성관련 로직 BoardStringGenerater에 역할 분리 및 추상화 레벨 균일화

    class BoardStringGenerator {

        public String generate() {
            StringBuilder sb = new StringBuilder();
            appendColumnDefinition(sb);
            for (int row = 0; row < rowCount; row++) {
                appendRowNumber(sb, row);
                for (int column = 0; column < columnCount; column++) {
                    Coordinate coordinate = new Coordinate(row, column);
                    appendCell(sb, coordinate);
                }
                sb.append("\n");
            }
            return sb.toString();
        }
// private 생략
    }
  • BoardStringGenerator 테스트 작성

    @Test
    @DisplayName("toString 테스트")
    void generateTest() {
        //given
        int rowCount = 4;
        int columnCount = 4;
        int mineCount = 3;
        Board board = spy(new Board(rowCount, columnCount, mineCount));
        for (int row = 0; row < rowCount; row++) {
            for (int col = 0; col < columnCount; col++) {
                Coordinate coordinate = new Coordinate(row, col);
                when(board.get(coordinate)).thenReturn(switch (col % 4) {
                    case 0 -> generateFlagCell();
                    case 1 -> generateOpenedCell(row);
                    case 2 -> Cell.normalCell();
                    case 3 -> generateOpenedMineCell();
                    default -> throw new IllegalStateException("Unexpected value: " + col);
                });
            }
        }
        //when
        String boardString = board.new BoardStringGenerator().generate();
        //then     CLOSED("□"), FLAGGED("⚑"), NORMAL_OPENED("■"), MINE_OPENED("☼");
        assertThatCharSequence(boardString).isEqualToIgnoringWhitespace("""
              A B C D
              1 ⚑ ☼ □ ☼
              2 ⚑ 1 □ ☼
              3 ⚑ ■ □ ☼
              4 ⚑ ■ □ ☼
            """);

    }
//private 생략
  • 복잡한 초기화 로직 추상화레벨 균일화

class A {

    @Test
    @DisplayName("지뢰가 아니고 주변에 지뢰가 없다면, 깃발을 제외하고 함께 연다.")
    void openNormalCell() {
        //given
        Coordinate targetCoordinate = new Coordinate(1, 1);
        Cell[] cells = createCellsForAround();
        Coordinate[] coordinates = createCoordinatesForAround(targetCoordinate);
        for (int i = 0; i < coordinates.length; i++) {
            when(board.get(coordinates[i])).thenReturn(cells[i]);
        }
        //when
        board.open(targetCoordinate);
        //then
        for (Cell cell : cells) {
            verify(cell, atLeastOnce()).open();
        }
    }

    private Coordinate[] createCoordinatesForAround(Coordinate targetCoordinate) {
        int[][] deltas = {{0, -1}, {0, +1}, {1, -1}, {1, 0},
            {1, +1}, {-1, -1}, {-1, 0}, {-1, 1}};
        return Arrays.stream(deltas)
            .map(delta -> new Coordinate(targetCoordinate.row() + delta[0],
                targetCoordinate.column() + delta[1]))
            .toArray(Coordinate[]::new);
    }

    private Cell[] createCellsForAround() {
        return IntStream.range(0, 8).mapToObj((i) -> spy(Cell.normalCell()))
            .toArray(Cell[]::new);
    }
}
  • 강의에서 언급된 ParameterizedTest 적용

    static Stream<Arguments> serveIncorrectCoordinates() {
        int rowCount = 4;
        int columnCount = 4;
        return Stream.of(
            Arguments.of(rowCount + 1,
                columnCount),
            Arguments.of(rowCount,
                columnCount + 1),
            Arguments.of(-1, columnCount),
            Arguments.of(rowCount, -1)
        );
    }

    @DisplayName("좌표가 범위밖이면 예외를 던진다.")
    @ParameterizedTest()
    @MethodSource("serveIncorrectCoordinates")
    void configCoordinateBalidate2(int row, int column) {
        //given
        Coordinate targetCoordinate = new Coordinate(row, column);
        //when
        //then
        assertThatThrownBy(() -> board.validateCoordinates(targetCoordinate))
            .isInstanceOf(IllegalArgumentException.class);
    }

가독성을 해치는 경우 ParameterizedTest를 활용해 해결했다. 

 

  • 이외 일반과제 피드백도 지뢰찾기에 반영

    private Board board;
    private int rowCount;
    private int columnCount;
    private int mineCount;

    @BeforeEach
    void setUp() {
        rowCount = 4;
        columnCount = 4;
        mineCount = 5;
        board = new Board(4, 4, 5);
    }

댓글을 작성해보세요.

채널톡 아이콘