본문 바로가기
공부/TDD

볼링게임 TDD [2]

by Moonsc 2020. 10. 2.
728x90
[v] 실패하는 테스트를 만들고 스텁으로 우선 빠르게 테스트를 작성할 것 이다.
[v] 볼링 객체를 만든다.
[v] 볼링 객체는 10개의 핀을 가지고 있고 21번의 투구 기회가 있다.
[v] 볼링의 프레임은 총 10 프레임이고 한 프레임이 끝나면 핀의 갯수는 10개로 초기화된다.
[v] 투구 2번 끝나면 한 프레임도 끝낸다. 단 마지막 프레임은 3번의 투구가 필요하다.
[v] 볼링의 프레임과 핀은 점수 계산기와 연관이 있는데 이것은 나중에 생각해본다.

[] 볼링을 상속받는 객체로는 볼링 플레이어와 볼링 점수 계산기가 존재한다. (플레이어와 점수 계산기는 1 : 1 관계를 갖는다.)
[] 점수 계산기를 통해 계산된 점수는 점수 표출기를 통해 점수를 볼 수 있어야 한다.
[] 투구(pitch)는 Bowling Player 객체가 가지고 있어야 한다.
[] 투구(pitch) 후 쓰러진 pin은 BowlingScore에 전달되어야 한다.
[] 한 플레이어가 투구를 21회 이상 시도하면 예외를 뱉어준다.

 

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

public class Bowling {

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

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

    public void setPin(int pin) {
        this.pin = pin;
    }

    public void setPitchingCount(int pitchingCount) {
        this.pitchingCount = pitchingCount;
    }

    public void setFrame(int frame) {
        this.frame = frame;
    }

    public int getPin(){
        return pin;
    }

    public int getPitchingCount(){
        return pitchingCount;
    }

    public int getFrame(){
        return frame;
    }

    public int downPin(){
        return (int)((Math.random()*10000)%10);
    }

}

 

Bowling에서 pitch()를 제거 해주었고 몇 개의 핀을 쓰러트렸는지 반환시켜줄 downPin() 메소드를 추가하였다.

 

 

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

public class BowlingPlayer extends Bowling{

    private String name;

    public String getName() {
        return name;
    }

    public BowlingPlayer(String name){
        this.name = name;
    }

    public void pitch(){
        int nowFrame = getFrame();
        setPin(getPin() - downPin());
        setPitchingCount(getPitchingCount() - 1);
        setFrame(getPitchingCount() / 2);
        if(nowFrame != getFrame()){
            setPin(10);
        }
    }

}

 

 

private으로 멤버 변수를 사용하다보니 setter, getter를 통해 Value를 수정하도록 하였고,

BowlingPlayer 객체는 생성 될 때 선수의 이름을 갖는다라는 규칙을 추가였다.

 

 

지금까지 상황을 정리해보자.

볼링은 핀, 굴릴 횟수, 프레임이란 값을 가지고 있고, 볼링선수는 이름과, 굴리는 행동을 가지고 있다.

다만 여기서 아쉬운게 있다면 pitch메소드를 옮기면서 Bowling 객체의 멤버변수들의 계산을 BowlingPlayer에게 맡긴다는 것이다.

이전 글에서 pitch()를 BowlingPlayer에 옮기기로 한 이유는 단일 책임 원칙을 지키기 위해서였다.

하지만 지금은 그것조차 되고 있지 않으니 수정을 통해 볼링선수는 공을 굴리기만 하고, 나머지 계산은 Bowling에서 하도록 수정해보겠다.

또한 투구 횟수가 21번이 넘으면 Exception을 뱉도록 할 것이다.

 

 

[] 한 플레이어가 투구를 21회 이상 시도하면 예외를 뱉어준다.

    @Test
    void 투구는_총_21(){
        BowlingPlayer bp = new BowlingPlayer("Test");
        BowlingPlayer bp2 = new BowlingPlayer("Test2");
        RuntimeException e
                = assertThrows(RuntimeException.class, () ->{
            for(int i=1; i<=22; i++){
                bp.pitch();
            }
        });
        assertThat(e.getMessage()).isEqualTo("투구 횟수가 초과되었습니다.");
        bp2.pitch();
    }

 

    public void setPitchingCount(int pitchingCount) {
        if(this.pitchingCount == 0){
            throw new RuntimeException("투구 횟수가 초과되었습니다.");
        }
        this.pitchingCount = pitchingCount;
    }

 

내가 만든 코드에서는 공을 굴리는 pitch 메소드를 호출할 때 setsetPitchingCount 메소드를 호출해서 남은 횟수를 -1 을 시켜주는데 

덕분에 간단하게 현재 투구 횟수가 0이면 Exception을 발생시킬수 있었다.

 

[] pitch() 수정하여 SRP 지키기

    //BowlingPlayer
    public void pitch(){
        setPitchingCount(getPitchingCount() - 1);
    }

 

public class BowlingTest {

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

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

    @Test
    void 투구와_프레임_검증(){
        BowlingPlayer bp = new BowlingPlayer("Test");
        assertTrue(bp.getPin() == 10); // 첫 핀의 갯수는 10
        assertTrue(bp.getFrame() == 1); // 첫 프레임은 1
        assertTrue(bp.getPitchingCount() == 21); 굴리는 횟수는 21
        bp.pitch(); // 첫 번째 투구
        assertTrue(bp.getPitchingCount() == 20);
        assertTrue(bp.getFrame() == 1);
        bp.pitch(); // 두 번째 투구
        assertTrue(bp.getPitchingCount() == 19); 
        assertTrue(bp.getPin() == 10); //두 번째 투구가 끝나면 핀은 10개로 리셋된다
        assertTrue(bp.getFrame() == 2); //두 번째 투구가 끝나면 프레임은 2가 된다
        bp.pitch(); // 세 번째 투구
        assertTrue(bp.getFrame() == 2);
    }

    @Test
    void 투구는_총_21(){
        BowlingPlayer bp = new BowlingPlayer("Test");
        BowlingPlayer bp2 = new BowlingPlayer("Test2");
        RuntimeException e
                = assertThrows(RuntimeException.class, () ->{
            for(int i=1; i<=22; i++){
                bp.pitch();
            }
        });
        assertThat(e.getMessage()).isEqualTo("투구 횟수가 초과되었습니다.");
        bp2.pitch();
    }
}

 

이제 공을 굴리고 나면 남은 굴리는 횟수가 감소하고, 프레임이 끝나면 핀의 갯수는 10개로 초기화되고,

핀은 공이 굴릴때 넘어지도록 하였으니 다음 할 일 목록을 추가해보겠다.

 

이전 글 볼링게임 TDD[1] 과 다르게 프레임이 10으로 시작하면 안될것 같아서 프레임에 대한 코드를 수정하였다.
투구와_프레임_검증 테스트 코드를 수정하였다. 수정된 Bowling은 아래 코드를 참고!

 

public class Bowling {

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

    public Bowling(){
        this.pin = 10;
        this.pitchingCount = 21;
        this.frame = 1;
        this.scores = new ArrayList<>();
    }

    public void setPitchingCount(int pitchingCount) {
        if(this.pitchingCount == 0){
            throw new RuntimeException("투구 횟수가 초과되었습니다.");
        }

        int nowFrame = getFrame();
        this.pin -= downPin();
        this.pitchingCount = pitchingCount;

        if (pitchingCount % 2 != 0
                && pitchingCount != 21 && pitchingCount != 1){
            this.frame ++;
        }

        if(nowFrame != getFrame()){
            this.pin =10;
        }
    }

    public void setFrame(int frame) {
        this.frame = frame;
    }

    public int getPin(){
        return pin;
    }

    public int getPitchingCount(){
        return pitchingCount;
    }

    public int getFrame(){
        return frame;
    }

    public int downPin(){
        int downPin = (int)((Math.random()*10000) % getPin()+1);
        return downPin;
    }

}

 

[] 볼링 점수 계산기를 만든다. (스트라이크, 스페어를 고려한다.)

    @Test
    void 점수쌓기_테스트(){
        BowlingPlayer player1 = new BowlingPlayer("player1");
        for(int i=1; i<=21; i++){
            player1.pitch(); //공을 굴리면
            System.out.println(player1.getDownPin(i-1)); //쓰러진 핀을 저장.
            if (i == 21){
                System.out.println(player1.getScores());
            }
        }
    }

 

    //Bowling Class

    public int downPin(){
        int downPin = (int)((Math.random()*10000) % getPin()+1);
        downPins.add(downPin);
        return downPin;
    }

    public List<Integer> getDownPins(){
        return downPins;
    }

    public int getDownPin(int index){
        return downPins.get(index);
    }

 

BowlingPlayer 객체가 공을 굴리면 pitch()가 실행되고 pitch()는 Bowling 객체의 setPitchingCount를 실행하는데 

setPitchingCount에서는 downPin()을 이용하여 현재 핀의 개 수를 계산한다.

이때 간단하게 쓰러진 pin의 개 수를 구할 때 그걸 downPins란 변수에 add해주기로 하였다. (여기서 downPins란 쓰러진 점수와 같다.)

 

콘솔 출력

8
1
5
4
8
1
1
4
3
4
10
0
10
0
5
2
3
6
3
4
1
[8, 1, 5, 4, 8, 1, 1, 4, 3, 4, 10, 0, 10, 0, 5, 2, 3, 6, 3, 4, 1]

 

눈으로 확인해봤으니 테스트 코드를 리팩토링해서 값으로 검증하자.

 

    @Test
    void 점수쌓기_테스트(){
        int sum = 0;
        int count = 0;
        BowlingPlayer player1 = new BowlingPlayer("player1");
        for(int i=1; i<=21; i++){
            count ++;
            player1.pitch(); //공을 굴리면
            sum += player1.getDownPin(i-1);
            assertThat(sum < 10);
            if(count == 2 && i != 20)
                sum = 0;
        }
    }

 

첫 번째와 두 번째, 세 번째와 네 번째 점수가 ..... 쓰러진 핀이 10이 넘지 않는 것을 확인하였다.

이제 스트라이크, 스페어를 고려한다 를 처리할 생각인데 이때 점수를 처리하면서 pitch 내부의 동작과정에도 영향을 줄 생각이다.

스트라이크를 치게되면 해당 프레임에 공을 굴릴 수 있는 횟수는 0 이다. 즉 프레임당 공 굴리는 횟 수가 2번이 아닌 1번이 되는 것이다.

 

댓글