본문 바로가기
공부/TDD

볼링게임 TDD [1]

by Moonsc 2020. 9. 30.
728x90

TDD를 공부하면서 TDD를 활용한 볼링게임을 구현해보기로 하였다.

아래의 할 일 목록 은 구현에 앞서 생각해본 볼링게임을 객체지향적으로 나태 내기 위한 방법이다.

이 방법이 정답은 아니지만 할 일 목록을 생각하면서 내가 어떤 구현을 해나갈지 명확해졌다.

다만 아직은 할 일을 작은 단위로 나누지 못한 것 같아서 우선 할 수 있는 것부터 하기로 했다.

할 일 목록

[ ] 볼링 객체를 만든다.
[ ] 볼링의 하위 객체로는 볼링 플레이어와 볼링 점수 계산기가 존재한다. (플레이어와 점수 계산기는 1 : 1 관계를 갖는다.)
[ ] 점수 계산기를 통해 계산된 점수는 점수 표출기를 통해 점수를 볼 수 있어야 한다.
[ ] 볼링의 프레임과 핀은 점수 계산기와 연관이 있는데 이것은 나중에 생각해본다.
[ ] 실패하는 테스트를 만들고 스텁으로 우선 빠르게 테스트를 작성할 것이다.

 

[v] 실패하는 테스트를 만들고 스텁으로 우선 빠르게 테스트를 작성할 것이다.

[v] 볼링 객체를 만든다.

우선 가장 쉽지만, TDD의 시작점인 할 일을 처리하기로 했다. 한 번에 하나씩 할 일을 처리하는 것도 중요하지만 

이 두 개의 할 일은 한 번에 처리할 수 있다고 생각이 들어 볼링 객체를 스텁으로 만들 것이다.

 

public class BowlingTest {

    @Test
    void 볼링객체가_존재하는지_확인한다(){
        Bowling bowling = new Bowling();
    }

}

 

당연하지만 볼링 객체가 현재는 존재하지 않기 때문에 실패하였고 빠르게 초록 막대를 보기 위하여 스텁 구현을 하였다.

//자바는 내부적으로 생성자가 존재하지 않는다면 디폴트 생성자를 제공한다. 
public class Bowling {
}

 

테스트가 성공한다. 그럼 스텁구현을 수습할 차례이다. 처리에 앞서 볼링을 구현하기 위해서는 

볼링의 하위 객체로는 볼링 플레이어와 볼링 점수 계산기가 존재한다. (플레이어와 점수 계산기는 1 : 1 관계를 갖는다.) 을 

해결해야 하는데 당장은 어떻게 구현할지 모르겠다. 그래서 과감하게 플레이어와 점수 계산기도 스텁으로 우선 처리를 하기로 했다.

    @Test
    void 볼링게이머와_볼링점수가_존재하는지_확인한다(){
        BowlingGamer gamer = new BowlingGamer();
        BowlingScore bowlingScore = new BowlingScore();
    }

 

TDD를 하다 보니 할 일 목록의 단위가 너무 커서 조금 더 작은 단위로 나눠야겠다는 생각이 들었다.

생각해보니 볼링은 프레임, 투구, 핀 이라는 개념이 있다. 이 개념들을 어떻게 처리할지 생각해서 할 일 목록에 추가하였다.

할 일을 추가하기 전에 리팩토링부터!

[ ] 볼링 객체를 만든다.

[] 볼링 객체는 10개의 핀을 가지고 있고 21번의 투구 기회가 있다.

할 일을 추가하였고, 이것을 처리하기 위해 볼링 객체에 핀과 투구 갯수를 넣었다. (단 테스트부터)

    // 객체를 확인하는 테스트가 여러개일 필요가 없다고 생각하여 합쳤다.
    @Test
    void 객체가_존재하는지_확인한다(){
        Bowling bowling = new Bowling();
        BowlingPlayer player = new BowlingPlayer();
        BowlingScore score = new BowlingScore();
    }

    @Test
    void 볼링객체의_값을_검증(){
        Bowling bowling = new Bowling();
        assertEquals(bowling.getPin(), 10);
        assertEquals(bowling.getPitchingCount(), 21);
    }

 

public class Bowling {

    private int pin;
    private int pitchingCount;

    public Bowling(){
        this.pin = 10;
        this.pitchingCount = 21;
    }

    public int getPin(){
        return pin;
    }

    public int getPitchingCount(){
        return pitchingCount;
    }

}

 

작성하고 나니 문제가 있다. 볼링게임은 프레임이 끝나는 시점에 핀의 개수가 초기화되는데

프레임을 어떻게 구현해야 할지 명확히 생각이 안 난다. 그렇다고 클래스로 따로 빼주기에는 부담이 간다.

그래서 메소드를 추가하여 핀의 개수를 10개로 초기화할 생각인데 이때 나중에 생길 문제점이 있을까? 를 고민하였지만

현시점에서는 문제 될 게 없다고 판단된다. 그럼 할 일 목록을 추가 하자.

 

[] 볼링의 프레임은 총 10 프레임이고 한 프레임이 끝나면 핀의 개수는 10개로 초기화된다.

[] 투구 2번 끝나면 한 프레임도 끝낸다. 단 마지막 프레임은 3번의 투구가 필요하다.

[] 투구를 통해 몇 개의 핀을 쓰러트렸는지 알아야 한다.

 

public class BowlingTest {

    @Test
    void 객체가_존재하는지_확인한다(){
        Bowling bowling = new Bowling();
        BowlingPlayer player = new BowlingPlayer();
        BowlingScore score = new BowlingScore();
    }

    @Test
    void 볼링객체의_값을_검증(){
        Bowling bowling = new Bowling();
        assertEquals(bowling.getPin(), 10);
        assertEquals(bowling.getPitchingCount(), 21);
        assertTrue(bowling.getFrame() == 10);
    }

    @Test
    void 투구와_프레임_검증(){
        Bowling bowling = new Bowling();
        assertTrue(bowling.getPin() == 10); // 투구전 핀은 10개
        assertTrue(bowling.getFrame() == 10); // 프레임은 10으로 시작
        bowling.pitch(); // 첫 번째 투구
        System.out.println(bowling.getPin()); // 투구후 핀은 모른다.

        assertTrue(bowling.getFrame() == 10); // 첫 투구가 끝나도 프레임은 10
        bowling.pitch(); // 두 번째 투구
        assertTrue(bowling.getFrame() == 9); // 두 번째 투구가 끝나면 프레임은 9
        assertTrue(bowling.getPin() == 10); // 새로운 프레임이 시작되면 핀은 초기화
    }
}

 

public class Bowling {

    private int pin;
    private int pitchingCount;
    private int frame;

    public Bowling(){
        this.pin = 10;
        this.pitchingCount = 21;
        this.frame = frameCalc();
    }

    public int getPin(){
        return pin;
    }

    public int getPitchingCount(){
        return pitchingCount;
    }

    public int getFrame(){
        return frame;
    }

    public void pitch(){
        int nowFrame = frameCalc();
        this.pin -= (int)((Math.random()*10000)%10);
        this.pitchingCount -= 1;
        this.frame = pitchingCount / 2;
        if(nowFrame != frameCalc()){
            this.pin = 10;
        }
    }

    private int frameCalc(){
        return this.pitchingCount / 2;
    }

}

 

여기까지 TDD를 진행하면서 느낀 문제가 있는데

  • 1. 자꾸 테스트보다 기능 추가를 먼저 생각한다. 
  • 2. 공을 굴리는 pitch 메소드가 볼링 객체에 있는 게 맞지 않는 것 같다.

1번의 경우.  다행히도 기능 추가를 하기 전에 다시 테스트로 돌아가고는 있지만 역시 습관은 무섭다.

2번의 경우. 아마 내 생각엔 이 메소드는 Bowling Player 객체로 이동해야 할 것 같다.

(중간에 BowlingGamer에서 Bowling Player로 수정하였다.)

그러면 단일 책임 원칙(SRP)을 지킬 수 있을 것 같다.

 

[] 투구(pitch)는 Bowling Player 객체가 가지고 있어야 한다.

[] 투구(pitch) 후 쓰러진 pin은 BowlingScore에 전달되어야 한다.

다음장은 지금까지 느낀 문제점을 해결하고 조금 더 완성도 높은 볼링게임을 위해 리팩토링 할 예정이며,

점수 관련 내용도 추가해 볼 것이다.

댓글