2018-4-4 TIL TDD기반으로 Lotto 2단계 구현하기와 this, 생성자에 관한 생각들..

TDD기반으로 로또 2단계 구현하기

LottoGameTest


public class LottoGameTest {
    @Test
    public void SECOND등수매기기() {
        List<Lotto> lottos = Arrays.asList(new Lotto("1,2,3,4,5,7"), new Lotto("1,2,3,4,5"), new Lotto("1,2,3,4,5,6"));
        Lotto winningLotto = new Lotto("1,2,3,4,5,6");
        int bonusNumber = 7;
        LottoGame game = new LottoGame(lottos);
        WinningLotto winLotto = new WinningLotto(winningLotto, bonusNumber);
        assertEquals(Rank.SECOND ,game.decision(lottos.get(0), winLotto));
    }

    @Test
    public void FIRST등수매기기() {
        List<Lotto> lottos = Arrays.asList(new Lotto("1,2,3,4,5,7"), new Lotto("1,2,3,4,5"), new Lotto("1,2,3,4,5,6"));
        Lotto winningLotto = new Lotto("1,2,3,4,5,6");
        int bonusNumber = 7;
        WinningLotto winLotto = new WinningLotto(winningLotto, bonusNumber);
        LottoGame game = new LottoGame(lottos);
        assertEquals(Rank.FIRST ,game.decision(lottos.get(2), winLotto));
    }

    @Test
    public void 랭크리스트() {
        List<Lotto> lottos = Arrays.asList(new Lotto("11,22,33,44,55,66"), new Lotto("1,2,3,4,5,7"), new Lotto("1,2,3,4,5,6"));
        Lotto winningLotto = new Lotto("1,2,3,4,5,6");
        int bonusNumber = 7;
        WinningLotto winLotto = new WinningLotto(winningLotto, bonusNumber);
        LottoGame game = new LottoGame(lottos);
        List<Rank> ranks = Arrays.asList(Rank.NONE, Rank.SECOND, Rank.FIRST);
        assertEquals(ranks.get(0), game.match(winLotto).getRanks().get(0));
        assertEquals(ranks.get(1), game.match(winLotto).getRanks().get(1));
        assertEquals(ranks.get(2), game.match(winLotto).getRanks().get(2));
    }

    @Test
    public void 매치테스트() {
        List<Lotto> lottos = Arrays.asList(new Lotto("1,2,3,4,5,6"), new Lotto("1,2,3,7,8,9"));
        Lotto winningLotto = new Lotto("1,2,3,4,5,6");
        int bonusNumber = 7;
        LottoGame game = new LottoGame(lottos);
        WinningLotto lotto = new WinningLotto(winningLotto, bonusNumber);
        game.match(lotto);
    }
}
  • 1단계에서는 무작정 맞춘갯수의 리스트인 counts와 거기서 한단계 더 나아가서 count가 3이상인 리스트 allCounts를 이용을 해서 맞춘갯수를 출력을 했다.
  • 2단계로 넘어오면서 Enum을 이용을 해서 Rank를 사용을 하게 되었다.
  • 우선 1단계와 기본 골격은 같지만 2단계로 넘어오면서 달라진점은 WinningLotto클래스를 사용했다는 점이다. WinningLotto에는 기본생성자에 (Lotto winningLotto, int bonusNumber)가 들어가서 이것을 통해서 당첨번호와 보너스번호를 한곳에서 관리를 할 수 있게 되었다.
  • 이전에는 game.match(Lotto lottos, Lotto winningLotto, int bonusNumber)f를 통해서 counts를 리턴을 해주는 방식으로 진행을 했다.
  • LottoGame클래스를 재설계를 해서 생성자에서 아예 List<Lotto> lottos를 받게했고, 거기다가 WinningLotto를 만듬으로서 game.match(WinningLotto winLotto)만을 넣게해서 숫자가 아닌 Rank가 리턴되는 형식으로 재설계를 했다.
  • 처음에는 Rank가 리턴이 되도록 설계를 했는데, 포비의 피드백을 받고 생성자와 WinningLotto를 만들고나서 훨씬 더 간결해졌다.

RankTest

public class RankTest {
    @Test
    public void SECOND랭크테스트() {
        assertEquals(Rank.SECOND, Rank.valueOf(5, true));
    }

    @Test
    public void FIRST랭크테스트() {
        assertEquals(Rank.FIRST, Rank.valueOf(6, false));
    }

    @Test
    public void 랭크에서맞춘갯수출력() {
        List<Rank> ranks = Arrays.asList(Rank.FOURTH, Rank.FOURTH, Rank.FIFTH, Rank.SECOND);
        Result result = new Result(ranks);
        result.getRanks();
        int num1 = Result.finalResult.get(Rank.FOURTH);
        int num2 = Result.finalResult.get(Rank.FIFTH);
        int num3 = Result.finalResult.get(Rank.SECOND);
        int num4 = Result.finalResult.get(Rank.THIRD);
        int num5 = Result.finalResult.get(Rank.NONE);
        assertEquals(2, num1);
        assertEquals(1, num2);
        assertEquals(1, num3);
        assertEquals(0, num4);
        assertEquals(0, num5);
    }
}
  • Ranks리스트에 Rank들을 넣고나서 Rank의 갯수가 정확하게 들어갔는지 확인하는 테스트를 구현을 했다.

ProfitTest

public class ProfitTest {
    @Test
    public void 벌어들인돈() {
        List<Rank> ranks = Arrays.asList(Rank.FIFTH, Rank.FIFTH, Rank.FOURTH, Rank.NONE, Rank.NONE);
        Profit profit = new Profit();
        assertEquals(60000, profit.earn(ranks));
    }

    @Test
    public void 수익률() {
        List<Rank> ranks = Arrays.asList(Rank.FIFTH, Rank.FIFTH, Rank.FOURTH, Rank.NONE, Rank.NONE);
        Profit profit = new Profit();
        assertEquals(1200, profit.percent(ranks));
    }
}
  • 이전에 Profit을 구했을때는, totalMoney를 통해서 총 얼마를 벌어들였고 거기다가 inputMoney까지 인자로 받아서 복잡하게 계산을 하는 과정을 겪었었다. 하지만 생각을 하다보니 내가 Rank에 NONE이라는 것을 만들었기에 Ranks에 있는 길이에다가 1000을 곱하면 그것이 inputMoney가 되고, 벌어들인돈은 ranks에 있는 값들에 해당하는 당첨금액을 더하면 되서 결론은 ranks 하나로 벌어들인돈과 수익률을 개산을 할 수 있게되었다.

LottoGame

public class LottoGame {
    private List<Lotto> lottos;

    public LottoGame(List<Lotto> lottos) {
        this.lottos = lottos;
    }

    public Result match(WinningLotto winLotto) {
        List<Rank> ranks = new ArrayList<>();
        for (Lotto lotto: lottos) {
            ranks.add(decision(lotto, winLotto));
        }
        Result result = new Result(ranks);
        return result;
    }

    public Rank decision(Lotto lotto, WinningLotto winLotto) {
        if(lotto.getLotto().contains(winLotto.getBonusNumber()))
            return Rank.valueOf(lotto.countNumber(winLotto.getWinningNumber()), true);
        return Rank.valueOf(lotto.countNumber(winLotto.getWinningNumber()), false);
    }
}
  • LottoGame을 만들때 생성자를 통해서 lottos를 전달을 받았다. 이전에 클래스를 설계를 했을때는 온갖 메소드와 인자를 전달을 해서 lottos를 전달을 했을텐ㄷ, 생성자와 this를 이용을 해서 정말 간편하게 인자를 전달을 하고 불러들일 수 있었다.

    WinningLotto

    ``` public class WinningLotto { private Lotto winningNumber; private int bonusNumber;

    public WinningLotto(Lotto winningNumber, int bonusNumber) { this.bonusNumber = bonusNumber; this.winningNumber = winningNumber; }

    public Lotto getWinningNumber() { return winningNumber; }

    public int getBonusNumber() { return bonusNumber; } }

- 위의 LottoGame에서 lottos를 전달받을때와, WinningLotto에 Lotto winningNumber, int bonusNumber를 전달을 하면서 위에다가 private Lotto winningNumber, private int bonusNumber을 선언하는것과 그리고 this를 통해서 인자를 받는 과정에 대해서 인지를 하게 되었다. this를 언제쓰고 또 언제 선언을 해야되는지를 조금이나마?알거같다.


#### Result

public class Result { public static HashMap<Rank, Integer> finalResult; private List ranks;

static {
    finalResult = new HashMap<>();
    finalResult.put(Rank.FIFTH, 0);
    finalResult.put(Rank.FOURTH, 0);
    finalResult.put(Rank.THIRD, 0);
    finalResult.put(Rank.SECOND, 0);
    finalResult.put(Rank.FIRST, 0);
    finalResult.put(Rank.NONE, 0);
}

public Result(List<Rank> ranks) {
    this.ranks = ranks;
    for (Rank rank: ranks) {
        int num = finalResult.get(rank);
        finalResult.put(rank , ++num);
    }
}

public List<Rank> getRanks() {
    return ranks;
}

public static HashMap<Rank, Integer> getFinalResult() {
    return finalResult;
} } ``` #### Main
Result result = game.match(lotto);
View.printResult(result);
  • 위의 2문장이 처음에 나를 고민을 하게 되었다. game.match는 원래는 ranks를 반환을 하는 형태였다. 하지만 Result타입으로 리턴을 하게되었을때, result에는 무엇이 들어가야되나 어떤 설계를 해야되나 고민을 했기때문이다. 우선은 Result를 차근차근 설계를 해보기로 했다. 결과가 저장이 되는 것이기때문에 결국에는 최종 Rank들을 담고있는 HashMap<Rank, Integer>형태의 finalResult와 수익률을 개산을 하기위해서 Ranks가 필요하므로 생성자를 통해 ranks를 받았다.

오늘의 느낀점

  • 어제 많은 생각과 좌절이 들었던 하루였다. 그리고 나서 나에게는 많이 힘든 재설계와 TDD에 대해서 본격적으로 Lotto프로젝트를 진행을 하게되었다. 1단계는 기본적으로 쉽기때문에 TDD를 통해서 구현이 됬다는것은 우연이라고 이야기를 할 수 있었다.
  • 오늘 2단계 역시 TDD를 통해서 진행을 하기로 마음을 먹었을때 두려움이 강했었다. 그러다가 어제했던것처럼 하나하나 Test코드를 설계를 하고 그것을 바탕으로 내가 테스트코드에서 작성했던 대로 클래스설계와 메소드들을 만들기 시작을 했다.
  • 이전에 로또에서는 Rank와 Ranks를 구하기 위해서 굉장히 많은 for문과 if문들이 사용이 됬었다. 이번에는 메소드들이 우선 많이 줄었다. 특히 profit을 구하는것만 보더라도 많이 줄었고 인자를 많이 전달을 안해서 기뻤다.
  • 메소드 하나하나 테스트코드를 짜고 그것에 예상되는 값들을 생각을 하고 그것을 바탕으로 메소드를 설계하고 테스트코드들이 동작을 했을때 정말 기분이 좋았다. 오늘 더 느낀점이 있다면 바로 this의 사용과 생성자를 통해서 인자들을 굉장히 효율적으로 전달을 할 수 있다는 점. 그리고 클래스의 재설계였다.
  • 메소드들을 구현을 하다보면 자연스래 인자들이 중복적으로 전달이 되고 3-4개씩 전달을 하는 경우들이 있다. 그러다보니 많이 복잡하고 인스턴스변수를 최대한 사용을 하지말라고해서 3-4개 되는 인자에 메소드에 메소드가 꼬리에 꼬리를 물어서 인자로 전달이 되다보니 나 역시도 보면서 많은 어려움과 불편함이 있었는데, 이것을 클래스 재설계를 통해서 한방에 해결을 할 수 있다니 짜릿한 경험이 였다.
  • 어제는 많이 좌절을 했던 하루지만, 오늘은 평소보다 더 많은 성취감을 느꼈던 하루였다.
  • 내일도 오늘처럼 화이팅하자 :)
Written on April 4, 2018