Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

58 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

blackjack-tdd

구현 기능 목록

1. [입력] 게임에 참여할 사람의 이름을 입력 받는다.(쉼표 기준으로 분리)

[예외처리] 쉼표 외의 다른 문자를 구분자로 한 경우 IllegalArgumentException 예외를 발생시킨다.

[예외처리] 입력값이 공백일 경우 IllegalArgumentException 예외를 발생시킨다.

2. [입력] 게임에 참여한 사람의 배팅 금액을 입력 받는다.

[예외처리] 입력값이 공백일 경우 IllegalArgumentException 예외를 발생시킨다.

[예외처리] 입력값이 숫자가 아닐 경우 IllegalArgumentException 예외를 발생시킨다.

[예외처리] 입력값이 0 이하일 경우 IllegalArgumentException 예외를 발생시킨다.

3. [중간 과정] 딜러와 사용자에게 카드를 2장씩 나누어준다.

[카드 조건] 덱은 1개만 사용한다.(A ~ K : 13 * 4 = 52장)

4. [중간 과정] 딜러와 사용자의 최초 카드 합계를 구하며, 블랙잭 여부를 판단한다.

[ACE 조건] ACE가 11이어도 21이 넘지 않는 경우에는 11, 넘는 경우에는 1로 계산한다.

5. [출력] 카드 현황을 출력한다.

[출력 조건] 딜러는 첫 번째 카드만 출력한다.

6. [입력] 사용자별로 카드 추가 지급 여부를 입력 받는다.

[예외처리] y, n 외의 다른 문자일 경우 IllegalArgumentException 예외를 발생시킨다.

[입력 조건] 모든 사용자가 추가 지급을 완료할 때까지 반복한다.

[지급 조건] 사용자가 y를 입력하고, 사용자의 현재 카드 합계가 21미만일 경우에 카드를 지급한다.

7. [출력] 사용자별로 지급 현황을 출력한다.

8. [출력] 딜러의 카드 합계가 17 이상이 일 될 때까지 추가 지급 현황을 출력한다.

9. [출력] 사용자별 카드 현황 및 결과를 출력한다.

10. [중간 과정] 최종 승패 및 수익을 계산한다.

11. [출력] 참여자별 최종 수익을 출력한다.

tdd 사고 과정

기능 목록 1

  • 사용자의 이름을 검증하는 거니까 User 클래스의 기능으로 분류하자.
    • UserTest를 우선 만들고 입력값이 공백일 경우 예외를 발생시키는 테스트를 작성하자. image
    • User가 존재하지 않아 컴파일 에러 발생 image
    • User를 생성하자.
      image
    • 테스트 재실행 image
    • 예외를 예상했으나 예외가 발생하지 않아 테스트 실패
    • User 클래스에서 이름을 검증하여 공백일 경우 예외를 발생하는 코드 추가하자. image
    • 테스트 재실행 -> 성공!
      image
  • 쉼표 외의 다른 문자를 구분자로 한 경우를 검증하기 위해 검증 클래스를 만들어야 해.
    • 쉼표로 문자열을 구분하는 클래스니까 CommaSplitter를 클래스명으로 하고, CommaSpliiterTest를 만들자. image
    • 테스트 케이스를 생각하자.
      1. 공백이 입력되는 경우 예외 발생
      2. 구분자가 쉼표가 아닌 경우 예외 발생 -> 이건 User 클래스에서 이름 검증할 때 하자. ex) 이름에 특수문자가 포함될 수 없습니다.
      3. 문자열을 쉼표로 올바르게 구분하여 리스트로 반환 테스트
    • 입력값이 공백일 경우 예외를 발생시키는 테스트를 작성하자. image
      • CommaSplitter 클래스가 존재하지 않아 컴파일 에러 발생 image
      • CommaSpliiter를 생성하자.
        image
      • 테스트 재실행 -> split() 메서드가 존재하지 않아 컴파일 에러 발생 image
      • CommaSplitter에 split() 메서드 생성
        image
      • 테스트 재실행 -> 예외가 발생하는 것을 예상했으나 예외가 발생하지 않아 테스트 실패 image
      • split() 메서드에 입력값이 공백일 경우 예외를 발생시키는 코드를 추가하자. image
      • 테스트 재실행 -> 성공!
        image
    • 문자열을 쉼표로 구분하여 리스트로 올바르게 반환하는지 테스트하자.
      • 실패하는 테스트 작성
        image
      • split() 메서드가 List를 반환할 것을 기대했으나 현재 반환 타입이 void라서 테스트 실패 image
      • CommaSplitter.split()의 반환 타입을 List로 변경 image
      • 테스트 재실행 -> 반환하는 리스트의 길이가 0으로 고정되어 있어 테스트 실패 image
      • 테스트가 통과하도록 CommaSplitter.split() 메서드 로직 수정하자. image
      • 테스트 재시도 -> 입력값이 공백인 경우에는 검증 로직에서 걸렬 테스트 실패 image
      • 테스트 케이스 수정
        image
      • 테스트 재시도 -> 성공!
        image
    • 구현하다 보니 CommaSplitter에서 pobi , jason을 입력받는 경우 User에 공백이 포함된 이름이 들어간다. User 생성자로 들어오는 input에 대해 문자열 양 쪽 공백을 제거하는 코드를 추가하자.

기능 목록 2

  • 배팅 금액의 클래스명은 BettingAmount로 하자.
  • 우선 BettingAmountTest 클래스를 생성하자.
    image
  • 배팅 금액이 공백인 경우를 테스트하자.
    • 실패 테스트 작성
      image
    • BettingAmount가 존재하지 않아 테스트 실패 image
    • BettingAmount를 생성하고 문자열을 파라미터로 받는 생성자를 생성하자.
      image
    • 테스트 재시도 -> 예외 발생을 예상했으나 예외가 발생하지 않아 테스트 실패 image
    • 입력값이 공백일 경우 예외를 발생하는 코드를 작성하자. image
    • 테스트 재시도 -> 성공!
      image
  • 배팅 금액이 숫자 타입이 아닐 경우를 테스트하자.
    • 실패 테스트 작성 image
    • 예외가 발생하는 것을 예상했으나 예외가 발생하지 않아 테스트 실패 image
    • 배팅 금액이 숫자 타입이 아닐 경우 예외를 발생시키는 로직을 추가하자. image
    • 테스트 재시도 -> 성공!
      image
  • 배팅 금액이 0 이하인 경우를 테스트하자.
    • 실패 테스트 작성 image
    • 예외가 발생하는 것을 예상했으나 예외가 발생하지 않아 테스트 실패 image
    • 배팅 금액이 0 이하일 경우 예외를 발생시키는 로직을 추가하자. image

기능 목록 3

  • 어, 기능 목록 3에서 나오는 도메인은 카드라고 생각하면 될 거 같은데, 카드 도메인에는 딱히 로직이 없는데 어떻게 TDD로 구현하지? 얘는 그냥 바로 만들어도 되나?

  • 별다른 도메인 로직이 존재하지 않는 값객체는 도메인 규칙을 정의하는 용도로 TDD를 진행하자.

  • Card 객체는 필드로 Rank와 Suit을 가지고 있다. 이 두 클래스 먼저 TDD를 진행하자.(사전에 한 설계를 가지고 TDD를 하는 것이기 때문에 이렇게 바로 필드 구조가 나온 것. 만약 처음 설계를 진행했다면 Card의 필드로 Rank와 Suit을 두는 것을 바로 생각하지는 못 했을 것.)

  • Rank를 먼저 테스트하자. Rank는 Card의 숫자를 나타내는 클래스로 enum 클래스로 만들 것이다.

    • enum 클래스도 값객체다. 특별한 도메인 로직은 존재하지 않지만, 도메인 규칙은 존재하기 때문에 이를 정의하는 용도로 테스트를 작성해보자.

    • 도메인 규칙을 정의해보자.

      1. 2 ~ 9 Rank는 점수로 2 ~ 9를 각각 반환한다.
      2. Ace Rank는 점수로 1을 반환한다.
      3. J, Q, K는 점수로 10을 반환한다.
    • RankTest라는 이름으로 테스트 클래스를 생성하자.
      image

    • 1번 규칙 먼저 테스트를 작성하자. image

    • 이젠 돌려보지 않아도 알겠지만 그래도 테스트를 돌려보자. image

      • Rank 클래스가 존재하지 않아서 테스트 실패
    • Rank 클래스를 생성하자.
      image

    • 테스트 재시도 -> getScore()라는 메서드가 존재하지 않아서 테스트 실패 image

    • getScore()메서드를 생성하자.
      image

    • 테스트 재시도 -> score 필드가 존재하지 않아서 컴파일 에러 발생 image

    • 테스트 코드에서 정의한 score 필드를 추가하자.
      image

      • score를 final로 설정하여 생성자도 같이 추가했음
    • 테스트 재시도 -> Rank enum 객체가 존재하지 않아서 컴파일 에러 발생 image

    • 테스트에 정의한 Rank enum 객체들을 추가하자.
      image

    • 테스트 재시도 -> 성공!
      image

    • 2번 규칙(ACE Rank)의 테스트 코드를 작성하자.
      image

    • 테스트 실행 -> ACE 객체가 없어서 테스트 실패 image

    • ACE 객체를 추가하자.
      image

    • 테스트 재시도 -> 성공!
      image

    • 3번 규칙(J, Q, K)의 테스트 코드를 작성하자.
      image

    • 테스트 실행 -> J, Q, K 객체가 존재하지 않아 테스트 실패 image

    • J, Q, K 객체를 추가하자.
      image

    • 테스트 재시도 -> 성공!

  • 이렇게 Card의 숫자를 나타내는 Rank 클래스를 도메인 규칙에 맞게 생성했다. 누군가가 J가 11을 반환하도록 코드를 수정해도 테스트에서 잡아낼 수 있게 되었다.

  • +) view에서 각 Rank를 숫자로 표현해야 하는 요구사항을 발견했다. 각 Rank마다 어울리는 format을 반환하도록 필드를 추가하자. (이것은 view를 위한 필드이므로 테스트 코드를 작성하지 않아도 된다고 판단했다. TDD는 로직을 테스트하는 것이지, 값 자체를 테스트하는 것이 아니기 때문) image

  • 다음으로 Suit을 생성하자.

    • Suit은 Card의 모양을 나타내는 객체이다. 특별한 도메인 규칙이 존재하지 않고, 단순히 view를 위한 format 필드가 필요하기 때문에 TDD를 진행하지 않고 바로 생성하자.
      image
  • 다음으로 Rank와 Suit을 필드로 가지는 Card를 생성하자.

    • 테스트를 짜기에 앞서, Card의 도메인 로직이나 규칙이 존재하는지 고민하자.
    • 고민한 결과, 아직 Card의 도메인 로직이 떠오르지 않는다.
    • 도메인 규칙은 Rank와 Suit에서 적용해서 Card에서 따로 적용할 건 없어보이지만, 한 가지 테스트를 해야 한다.
    • 동등성 테스트이다. 기본적으로 Card는 식별자가 존재하지 않고, Rank와 Suit을 기준으로 식별하기 때문에 Rank와 Suit이 같다면 같은 Card로 봐야 한다.
    • 또한 Card는 Rank와 Suit을 파라미터로 받아 생성하도록 할 예정이다.
    • 따라서 두 가지의 테스트 케이스를 짤 수 있다.
      1. Card는 Rank와 Suit으로 생성할 수 있다.
      2. Card는 Rank와 Suit이 같다면 같은 객체이다.
    • 이제 첫 번째 테스트 케이스부터 짜자.
    • CardTest라는 이름의 테스트 클래스를 생성하고 Card 정상 생성 테스트 코드를 짰다. image
    • Card 클래스가 존재하지 않아 컴파일 에러가 발생하므로 Card 클래스를 생성하자.(필드인 Rank와 Suit도 한 번에 만들어줬다.)
      image
    • 테스트 실행 -> 성공!
      image
    • 이제 두 번째 테스트 케이스를 짜자.
      image
    • 테스트 실행 -> 두 객체가 달라 테스트 실패 image
      • Card 클래스에서 equals&hashcode를 오버라이딩하지 않아 필드가 같은 두 객체를 같다고 보지 않는다.
    • Card 클래스 내에 equals&hashcode를 오버라이딩하자. image
    • 테스트 재시도 -> 성공!
      image
  • 이제 Card도 만들었으니, 모든 카드들을 가지고 있는 객체가 필요하다. 이 객체의 이름은 Deck으로 하자.(블랙잭에서 덱은 카드 한 더미를 의미하며, 조커를 제외한 52장의 카드로 구성되어 있음)

  • 이 Deck의 역할을 생각해보자.

    1. Deck 초기화(52장의 Card 객체들을 생성하여 가지고 있어야 한다.)
    2. Card 셔플(52장의 Card 객체들을 순서대로 가지고 있는 것이 아니라 섞어야 한다.)
    3. Card 드로우(플레이어와 딜러에게 카드를 한 장씩 나눠줘야 한다.)
    4. Deck에 Card가 없는데 드로우할 경우 예외 처리
  • 첫 번째 역할부터 테스트 코드를 짜자.

    • Deck은 처음 생성될 때 52장의 카들르 생성하여 가지고 있어야 한다.

About

TDD로 구현하는 블랙잭

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages