볼링게임
시작하기에 앞서서...
테스트 코드는 개인 프로젝트나 또는 과거 전 회사에서 보여주기용이 필요할 때나 만들었던 부분인데..
실제로 업무에서 `테스트 작성 -> 실패 -> 수정 -> 성공`이라는 다이내믹함을 느껴보고 싶고
리팩터링 할 때마다 사이드 이팩트를 걱정하는 모습에 TDD를 연습해보기로 했다.
생각나는 건 일종의 게임들 (숫자야구나 볼링 또는 알고리즘들? )을 통해 테스트 코드를 어떤 식으로 만들어 나가는지 연습하고 가볍게 웹 프로젝트를 하나 만들어 볼 생각이다.
또한 요즘 Mock이라는 것을 배웠는데 그 과정까지 가고 그 뒤에는 javascript ( 정확히는 업무에 쓸려고 준비 중인 React를 바탕으로) 하는 TDD까지 연습해보려고 한다.
TDD 생 초보의 TDD 연습기 이제 Start !~
Chapter 볼링게임
볼링 규칙
10 프레임으로 이루어져 있으며 한 프레임에 두 번 던질 수 있다.
마지막 프레임에서 스트라이크 (두 번 더 던짐) / 스페어 시에 보너스 가 있다. ( 한번 더 던짐)
스페어인 경우 다음 라운드의 첫 번째 점수를 합산한다.
스트라이크 인 경우 다음 두 번의 투구의 점수를 합산한다.
우선 볼링 게임 클래스와 테스트 클래스를 만들었다. ( cmd + shift + t)
가장 먼저 쉽게 테스트해볼 수 있는 상황은 아예 0점일 것이다.
BowlingGame에서는 스코어를 얻어올 수 있는 메소드를 추가하고 해당 메소드에 대한 테스트 코드를 추가했다.
우선 가장 빠르게 성공하는 케이스에 대해서 작성했다.
이렇게 해서 실행하면..?!
성공한다.
그다음은 단순히 0점을 반환하는 게 아니라 20번 던지는 상황과 해당 결과 0점인 상황에 대해서 고민해봤다.
조금 호흡이 길어질 거 같은 느낌이지만 이 정도는 쏘쏘 하다고 생각했다.
우선 좀 쉽게 생각해서 20번 던지니깐 사이즈 20의 배열을 만들고 각 던지는 상황을 기록하게끔 했다.
일단 테스트는 잘 돌아간다.
이제 모두 0점인 상황을 만들었으니 모두 스트라이크 즉 300점인 상황에 대해서 고민해보기로 했다.
퍼펙트게임 (올스트 라이크)는 보너스 게임 두 판이 있으니 총 12번을 던진다.
120점이라니. 당연하게도 스트라이크 시에 대한 점수 계산이 안되어있어 당연한 상황이다.
이 부분에 대해서 고민해야겠다.
가볍게 생각해본 건 해당 던진 볼이 10점이면 그다음 두개의 볼의 합을 구하는 방법이다.
뭐가 문제일까. 이번에도 실패했다. (330점)
조금 차근히 고민해보니 12번을 던지는 상황인데.. 애는 20번을 돌면서 계산을 하고 있다.
프레임 단위로 계산해보는 것에 해결책이 있을듯하니 그쪽으로 넘어가 봤다.
프레임으로 바꾸자마자 바로 성공했다.
다른 테스트도 잘 동작한다.
이번엔 1 stirke + all gutter를 구현해봐야겠다.
잘 동작한다. 여기서 잠시 테스트 코드를 조금 리팩터링 해야겠다.
저 반복문이 눈에 거슬리다 못해 불편해지기 시작했다.
우선 인텔리 J의 막강한 기능을 활용하기 위해 for 문의 변수들을 정리했다. ( cmd + shift + v)
그다음 for문을 블록 지정하고 cmd +alt + m 하면 메소드로 추출할 수 있다.
볼을 반복해서 던지는 거니 bowlRepeat이란 이름으로 만들고 중간에 껴있는 게 거슬려서 맨 아래로 보내버렸다 (cmd +shift + 아래 방향키). 이렇게 하면 인텔리 J는 알아서 중복된 코드를 잘 추출해준다.
그 결과 현재 테스트 클래스는 다음과 같은 모습니다.
자 이제 계속해서 기능에 집중하자. 원스트라이크 + 다음 프레임의 첫 라운드는 7점 그다음 올 거터에 빠진 상황이다.
예상하기로는 10점이고 그다음이 7점 0 점이니깐 10+7+7점 즉 14+10 = 24 점이다.
앞에 테스트_볼링_게임_시추에이션_바이라는 건 좀 너무 긴 거 같다. 애도 좀 정리했다.
테스트는 잘 동작한다.
이번에는 첫 번째 프레임은 9점 + 원스트라이크+다음 프레임 3점 나머진 올 거터다.
이 케이스도 잘 동작한다.
이제 스페어 상황을 한번 고민해봐야겠다.
스페어는 기본적으로 다음 한구를 더 더하는 거니까.. 헛 근데 소스코드가 기본적인 부분이 오류가 있음을 알게 되었다. ( ㅠㅠ 테스트는 잘 통과했다고 생각했건만)
그래서 실패할 상황을 추측하고 테스트 케이스를 하나 더 추가했다.
`다 거터에 빠지다가 갑자기 삘 받아서 마지막 10 프레임은 스트라이크 (스트라이크니깐 2번 기회) - 스트라이크 - 9점을 낸 상황이다`
역시 우선 배열 사이즈부터가 잘못되었다. 최종 던지는 최대 횟수는 21회니 선언한 배열 사이즈를 조정했다.
테스트는 역시나 fail 아예 0점이다. 왜 그럴까 고민해보니 frame에 대해서 반복하지만 frame안에서 두번던질 수 있는 상황이 전혀 고려가 안되었던 것이다. (즉 10번 던지는 상황처럼 되어버린 것)
버그를 찾아내서 다행이다. 이제 수정해봐야겠다.
뭐 다행히 어려운 상황은 아니다. 두번던질 수 있는 것만 고려가 되면 바로 해결될듯하다.
코드가 늘어나서 그런지 조건이 많아서 그런지 눈에 잘 안 들어온다.
우선 테스트를 돌려 보고 리팩터링을 좀 해야겠다.
역시 예상한 대로 테스트는 잘 동작했다.
그럼 다시 잠시 머리도 식힐 겸 리팩터링 타임이다.
우선 가장 헷갈리는 건 매직넘버 10인 거 같다. 프레임도 10 이하고 10이랑 같고 10을 더하니
이런 기분이다. 조금 정리해봐야겠다
최대 넘어가는 핀이라는 이름으로 분리하니깐 그나마 덜 헷갈리는 듯하다.
이번엔 조건들과 옆에 계산 로직들을 분리해야겠다. (딱 봐서는 이해가 잘 안 갈 수도..)
이제 조금 읽기가 편해진 거 같다. 테스트 코드도 이상 없이 동작한다.
버그는 해결되었고 이번엔 스페어 상황에 대한 구현이다.
첫 프레임은 스페어고 다음 프레임의 첫 번째는 6점 그다음은 3점이다.
그 뒤로는 계속 거터에 빠졌다.
고려가 안된 이 상황은 역시나 테스트가 실패한다.
스페어 상황을 구현하자.
이번 프레임 점수가 10점이면 다음 프레임의 첫 번째 구 점수를 구해서 더한다
테스트는 잘 성공한다. 조건은 다시 리팩터링 고고
테스트도 잘 동작한다.
역시 테스트 코드가 있으니 리팩터링은 안심하고 진행할 수 있는 듯하다.
이제 또 여러 상황을 테스트해봐야겠다.
스페어 -> 스트라이크 -> 스트라이크 -> 스페어 -> 5/3 -> 올 거터
테스트는 정상적으로 동작한다. 이제 마지막으로 실제 경기를 했다고 가정하고 해봐야겠다.
여기서 팁은 조금씩 해보는 거다.
완료
테스트는 이상 없이 완료되었다.
Part1 후기
이번엔 가볍게 볼링게임을 통해서 테스트 코드를 작성하는 것을 연습해보았다.
실제로 두 가지 상황을 전부 해봤는데 ( 테스트 코드를 만들면서 하기 / 그냥 손으로 써가면서 하기) 확실히 테스트 코드를 만드는 게 개발이 좀 더 다이내믹하여짐을 느낄 수 있었다.
또한 예상치 못한 버그에도 쉽게 대응할 수 있었으며 리팩터링을 함에 있어 주저 없이 할 수 있도록 도움을 줬다.
이게 테스트 코드를 작성하는 매력일까?