Develope Story

2019-04-29 [java-study] 로또1단계 스터디

|

로또 구현

요구사항

  • 로또 구입 금액을 입력하면 구입 금액에 해당하는 로또를 발급해야 한다.
  • 로또 1장의 가격은 1000원이다.
구입금액을 입력해 주세요.
14000
14개를 구매했습니다.
[8, 21, 23, 41, 42, 43]
[3, 5, 11, 16, 32, 38]
[7, 11, 16, 35, 36, 44]
[1, 8, 11, 31, 41, 42]
[13, 14, 16, 38, 42, 45]
[7, 11, 30, 40, 42, 43]
[2, 13, 22, 32, 38, 45]
[23, 25, 33, 36, 39, 41]
[1, 3, 5, 14, 22, 45]
[5, 9, 38, 41, 43, 44]
[2, 8, 9, 18, 19, 21]
[13, 14, 18, 21, 23, 35]
[17, 21, 29, 37, 42, 45]
[3, 8, 27, 30, 35, 44]

지난 주 당첨 번호를 입력해 주세요.
1, 2, 3, 4, 5, 6

당첨 통계
---------
3개 일치 (5000원)- 1개
4개 일치 (50000원)- 0개
5개 일치 (1500000원)- 0개
6개 일치 (2000000000원)- 0개
총 수익률은 35.7%입니다.

프로그래밍 요구사항

  • 모든 기능을 TDD로 구현해 단위 테스트가 존재해야 한다. 단, UI(System.out, System.in) 로직은 제외

힌트

  • 모든 기능을 TDD로 구현해 단위 테스트가 존재해야 한다. 단, UI(System.out, System.in) 로직은 제외
    • 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
    • UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다.
  • 로또 자동 생성은 Collections.shuffle() 메소드 활용한다.
  • Collections.sort() 메소드를 활용해 정렬 가능하다.
  • ArrayList의 contains() 메소드를 활용하면 어떤 값이 존재하는지 유무를 판단할 수 있다.

할일

  • test+구현
  • 구현
  • Test 추가구현
  • 구현 및 리팩토링

목표

  • test코드를 작성하고 코드를 구현하자!

궁금증

  • 첫번째 (1부터 45까지 리스트를 미리 받아놓고 메소드에서 셔플 및 sublist실행)
public List<String> buyLotto(int totalLottoPrice) {
    List<String> numbersInRange = ListGenerator.getNumbersInRange(LOTTO_NUMBER_RANGE);
    for (int i = 0; i < getLottoCount(totalLottoPrice); i++) {
        lottos.add(new Lotto(getRandomNumbers(numbersInRange)));
    }
    return showLottoHistory();
}
private List<String> getRandomNumbers(List<String> numbersInRange) {
    Collections.shuffle(numbersInRange);
    return numbersInRange.subList(0, LOTTO_NUMBER_COUNT);
}

출력

[26, 43, 13, 12, 18, 42]
[26, 43, 13, 12, 18, 42]
  • 두번째 (1부터 45까지 리스트를 받아 셔플및 subList실행)
public List<String> buyLotto(int totalLottoPrice){
    for (int i=0; i<getLottoCount(totalLottoPrice); i++){
        lottos.add(new Lotto(getRandomNumbers(LOTTO_NUMBER_RANGE)));
    }
    return showLottoHistory();
}
private List<String> getRandomNumbers(int range){
    List<String> numbersInRange = ListGenerator.getNumbersInRange(range);
    Collections.shuffle(numbersInRange);
    return numbersInRange.subList(0,LOTTO_NUMBER_COUNT);
}

출력

[26, 43, 13, 15, 18, 42]
[11, 18, 19, 33, 28, 34]

=> 첫번째 두번째 왜 결과가 다른지 궁금합니다.

  • 업데이트 예정

힘든점

  • nextInt() 실행후 nextLine() 메소드 실행시 그냥 넘어가 버림.

nextInt() 메소드 다음에 nextLine() 메소드를 실행하려고 할때 nextLine()메소드가 그냥 넘어가버리는 오류가 생겨난다. 이 이유는 nextInt()메소드를 실행 할 때 20을 콘솔에 입력하고 엔터를 누를때 20을 리턴시켰지만 Enter값은 그대로 남아있다. nextLine() 메소드는 Enter값을 기준으로 메소드를 종료시키기 때문에 nextLine()메소드가 실행될 때 남아있는 Enter값을 그대로 읽어 바로 종료된 것이다. 그래서 첫번째 문자열입력: 이 넘어가고 두번째 정수입력: 이 출력된 것이다.

=> 해결법 : nextInt() 사용 x, nextInt()와 nextLine()사이에 nextLine()한번더 사용

  • 저번엔 문제에서 test코드가 미리 주어져서 원할하게 코드를 작성하였는데 이번엔 test-> 구현 방식을 지키려니 어려웠음.
  • 처음 구상했던 로직으로 완성됬더라면 test를 변경할 일이없었겠지만 코드 구현하고 보니 도 좋은 아이디어가 생겨 test를 변경한 적이 많았음.
  • 로또 번호를 Integer, String 중 무엇으로 구현할지 고민하였음. String이 메소드를 1개 덜 써서 사용함.
    • 로또 번호로 연산할 일이 없기 때문에 문자열로 하기로 결정

2019-04-29 [java-study] 레이싱게임2단계 스터디

|

레이싱 게임 2단계

기능 요구사항

  • 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
  • 자동차 이름은 쉼표(,)를 기준으로 구분한다.
  • 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한명 이상일 수 있다.

프로그래밍 요구사항

  • 메소드가 너무 많은 일을 하지 않도록 분리하기 위해 노력해 본다.
  • 규칙 1: 한 메서드에 오직 한 단계의 들여쓰기(indent)만 한다.
  • 규칙 2: else 예약어를 쓰지 않는다.
  • 모든 로직에 단위 테스트를 구현한다. 단, UI(System.out, System.in) 로직은 제외
  • UI 로직과 핵심 비지니스 로직을 분리한다.

실행 결과

  • 위 요구사항에 따라 3대의 자동차가 5번 움직였을 경우 프로그램을 실행한 결과는 다음과 같다.
경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분).
pobi,crong,honux
시도할 회수는 몇회인가요?
5

실행 결과

pobi : -----
crong : ----
honux : -----

pobi, honux가 최종 우승했습니다.

후기

이번 실습에서 변수명, getter/setter, 클래스 당 50줄 넘지 않고 각각의 기능들이 객체 내에서 처리되도록 노력했다. 기능적인 요소들은 private 메소드로 만들고 기능을 활용하는 메소드는 public으로 만들었다. 구현적인 측면에서는 이전보다 더 나아졌지만 리팩토링 구현 시간( 구현2 : 리팩토링8 )이 늘었다. 최대한 만족도 높은 코드를 짜기 위해서 다양한 관점에서 코드를 봤기 때문이다. 매일 코딩을 이런식으로 할 수는 없겠다 싶어 시간을 정해두고 코딩을 하는 방향으로 추진해 나갈 계획이다. 다른 사람들의 코드를 보니 람다식과 stream을 사용하여 java8에 대해 공부를 하였다. 아직까진 람다식이나 stream보단 for문이 더 가독성이 높다고 생각하고 람다식이나 stream은 자주 써보는 식으로 공부해나갈 예정이다.

다른 사람과 차이점

  • 람다식 (Stream 활용)

람다식 정리

stream 정리

전체 소스코드 https://github.com/sproutt/java-study-racingGame/pull/35/files

2019-04-09 [java-study] 레이싱게임1단계 스터디

|

기능 요구사항

  • 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.

  • 실행 규칙은 n대의 자동차가 전진 또는 멈춤 여부를 판단하는 것을 한번의 횟수로 판단한다.

  • 사용자는 몇 대의 자동차로 몇 번 이동을 할 것인지를 입력할 수 있어야 한다.

  • 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4이상일 경우이다.

  • 자동차의 상태를 화면에 출력한다. 어느 시점에 출력할 것인지에 대한 제약은 없다.

구현중 공부 내용

StringBuilder 사용 이유

  • String : 기존의 문자열에 문자를 추가한다면 새로운 객체를 만들어 지정하고 기존의 문자열 객체는 쓰레기가 된다.
  • StringBuilder : 기존의 문자열 객체의 크기를 증가시켜 값을 더한다. // 동기화 기능 제공 X
  • StringBuffer : 기존의 문자열 객체의 크기를 증가시켜 값을 더한다. // 동기화 기능 제공 O

연산이 증가하면 성능 Builder > Buffer > String

객체지향 생활체조

리뷰 받은 내용

  • 무조건 메소드 접근 제한자 public 으로 하지마라

  • View, Control, Model 세가지로 분류하여 작업하자

  • 어디서든 사용하는 메소드는 util 클래스를 만들어 사용하자

  • 상수에도 접근제한자를 붙이자

  • 배열 대신 List 자료구조를 활용하자

  • get, set 사용을 자제하자

    • String track = car.showTrack("-"); 을 resultView에서 사용하고
      car에는
          
      public String showTrack(String trackShape) {
             StringBuffer track = new StringBuffer();
             for(int i=0; i<position; i++) {
                    track.append(trackShape);
             }
                 
             return track.toString();
      }
      
  • InputView, ResultView 메소드를 static으로 선언하여 객체를 선언하지 않도록하자
  • 객체에 담긴 내용은 객체를 이해하고 구현하도록하자

소스

model

package model;

public class Car {

    private int position;

    public Car() {
        position = 0;
    }

    public void move() {
        position++;
    }

    public String showTrack(char trackShape) {
        StringBuffer track = new StringBuffer();
        for(int i=0; i<position; i++) {
            track.append(trackShape);
        }
        return track.toString();
    }
}
package model;

import util.*;

import java.util.ArrayList;
import java.util.List;

public class RacingGame {

    private final static int CONDITION_TO_MOVE = 4;
    private final static int RANDOM_NUMBER_RANGE = 10;
    private int numberOfAttempts;
    private List<Car> cars;

    public RacingGame() {
        cars = new ArrayList<>();
    }

    public List<Car> playGame() {
        for (int i = 0; i < numberOfAttempts; i++) {
            moveCars();
        }
        return cars;
    }

    public void readyGame(int numberOfCars, int numberOfAttempts) {
        this.numberOfAttempts = numberOfAttempts;
        for (int i = 0; i < numberOfCars; i++) {
            cars.add(new Car());
        }
    }

    private void moveCars() {
        for (Car car : cars) {
            moveOrStopCar(car);
        }
    }

    private boolean canMove(int number) {
        if (number >= CONDITION_TO_MOVE) {
            return true;
        }
        return false;
    }

    private void moveOrStopCar(Car car) {
        if (canMove(RandomGenerator.getRandomNumber(RANDOM_NUMBER_RANGE))) {
            car.move();
        }
    }
}

util

package util;

import java.util.Random;

public class RandomGenerator {
    public static int getRandomNumber(int range) {
        return new Random().nextInt(range);
    }
}

view

package view;

import java.util.Scanner;

public class InputView {

    public static int InputNumberOfCars() {
        System.out.println("자동차 대수는 몇 대 인가요?");
        return new Scanner(System.in).nextInt();
    }

    public static int InputTime() {
        System.out.println("시도할 횟수는 몇 회 인가요?");
        return new Scanner(System.in).nextInt();
    }
}
package view;

import model.Car;
import java.util.List;

public class ResultView {

    private static final char HYPHEN = '-';

    public static void showRacingCarResult(List<Car> cars) {
        for (Car car : cars) {
            System.out.println(car.showTrack(HYPHEN));
        }
    }
}

TDD

import model.Car;
import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.*;

public class CarTest {

    Car car;

    @Before
    public void setUp() {
        car = new Car();
    }

    @Test
    public void testShowTrack() {
        assertEquals("", car.showTrack('-'));
    }

    @Test
    public void testShowTrackAfterMove() {
        car.move();
        assertEquals("-", car.showTrack('-'));
    }
}
import model.Car;
import model.RacingGame;
import org.junit.Before;
import org.junit.Test;

import java.util.List;

import static org.junit.Assert.*;

public class RacingGameTest {

    RacingGame racingGame;

    @Before
    public void setUp(){
        racingGame = new RacingGame();
    }

    @Test
    public void test준비하지않고PlayGame(){
        List<Car> tmpCar = racingGame.playGame();
        assertEquals(0,tmpCar.size());
    }

    @Test
    public void test준비하고PlayGame(){
        racingGame.readyGame(3,5);
        List<Car> tmpCar = racingGame.playGame();
        assertEquals(3, tmpCar.size());
    }
}

2019-04-04 [java-study] 객체지향 생활 체조 정리

|

객체지향 생활 체조 정리

규칙 1: 한 메서드에 오직 한 단계의 들여쓰기만 한다.

메서드의 크기가 크면 클수록 응집력이 떨어진다. 응집력이 강한 코드를 작성하기 위해서는 메서드가 한가지 일만하도록 즉 메서드당 하나의 제어 구조나 하나의 문장 단락으로 이루워지도록 코드 작성이 이루워져야한다.

한 메서드 안에 중첩된 제어구조가 있다면 다단계의 추상화를 코드로 짠 것이고 메서드가 한 가지 이상의 일을 하고있다는 뜻이다.

class Board {
   ...
   String board() {
      StringBuffer buf = new StringBuffer();
      for (int i = 0; i < 10; i++) { //collectRows();
         for (int j = 0; j < 10; j++) //collectRow();
            buf.append(data[i][j]);
         buf.append("\n");
      }
      return buf.toString();
   }
}
lass Board {
   ...
   String board() {
      StringBuffer buf = new StringBuffer();
      collectRows(buf);
      return buf.toString();
   }
 
   void collectRows(StringBuffer buf) {
      for (int i = 0; i < 10; i++)
         collectRow(buf, i);
   }
 
   void collectRow(StringBuffer buf, int row) {
      for (int i = 0; i < 10; i++)
         buf.append(data[row][i]);
      buf.append("\n");
   }
}

다음은 위의 코드를 한가지 일을 하는 메소드로 이루어지도록 리팩토링 한것이다.

리팩토링을 하므로써 여러가지 장점을 얻을 수 잇다.

  • 메소드를 분리시키므로 메소드를 재사용할 수 있음.
  • 각 메소드는 이름을 가지므로 코드의 가독성이 향샹됨(메소드명 잘 짜야함) => 주석을 작성하지 않아도 됨
  • 유지보수나 버그를 찾을 때 좋음.

규칙 2 : else 예약어 금지

보통 if문을 사용 하면 else-if, else문을 자연스럽게 사용하게 된다. if문은 누구나 쉽게 이해 할 수 있는 논리 연산자이다. 하지만 else, else-if문은 가독성이 떨어지고 복잡성이 증가한다. 또한 if문을 조건을 봐야 else문을 이해 할 수 있고 else문이 다 실행되야만 if문이 끝난다.

public int calculate(int firstValue, char operator, int secondValue){
  	if(operator == '+'){
    		return add(firstValue, secondValue);
  	}else if(operator == '-'){
    		return subtract(firstValue, secondValue);
  	}else if(operator == '*'){
  	 	 	return multiply(firstValue, secondValue);
 	 	}else if(operator == '/'){
  	  	return divide(firstValue, secondValue);
  	}else{
    		throw new RuntimeException();
  }
}
    public int calculate(int firstValue, char operator, int secondValue) {
        if (operator == '+')
            return add(firstValue, secondValue);
        if (operator == '-')
            return subtract(firstValue, secondValue);
        if (operator == '*')
            return multiply(firstValue, secondValue);
        if (operator == '/')
            return divide(firstValue, secondValue);
        throw new RuntimeException();
    }

위의 코드는 if-else문 아래는 else문을 사용하지 않았다.

else문을 사용하지 않고 사용했을 시 여러가지 장점을 얻을 수 있다.

  • 코드의 가독성이 좋아짐
  • return 을 활용하여 특정 조건일때 반환하면 코드를 더욱 간단하게 만들수있음.
  • 유지보수나 버그를 찾을 때 좋음.

그 외 다형성을 활용한 방법

public void barkAnimal(Animal animal) {
    if (animal instanceof Tiger) {
        System.out.println("어흥");
    } else if (animal instanceof Lion) {
        System.out.println("으르렁");
    }
}
public interface Barkable {
    public void bark();
}

public class Tiger extends Animal {
    public void bark() {
        System.out.println("어흥");
    }
}

public void barkAnimal(Barkable animal) {
    animal.bark();
}
  • 조건문을 사용하였을 때보다 유지보수가 좋아짐
  • 코드의 의도가 분명하게 보임

규칙 3 : 원시값과 문자열의 포장

원시값을 파라미터로 사용하는 메서드를 작성할 경우, 컴파일러가 의미적으로 맞는 코드작성을 안내해 줄 수 있는 방법이 없을 뿐만 아니라, 원시값의 방어코드를 작성하면서 중복이 발생할 가능성이 많다.

int값은 그냥 아무 의미 없는 스칼라 값일 뿐 (주석, 변수명 등으로 정보 전달) -> 객체

  • 유지보수에 좋음
  • 왜 쓰고 있는지에 대한 정보를 전달
  • 재사용성 증가

규칙 4 : 한 줄에 한 점만을 사용

한 줄에 여러개의 점이 있다면 어떤 객체가 동작을 맡고 있는지 찾기 힘들고 여러 개의 점이 들어 있는 코드 몇 줄을 들여다보기 시작하면 책임 소재의 오류를 많이 발견하기 시작한다. 한줄에 점들이 많다는 말은 다른 객체에 깊숙히 관여하고 있다고 말 할 수 있다. 즉 캡슐화를 어기고 있다는 증거이다. 객체가 자기 속을 들여다보려 하기보다는 뭔가 작업을 하도록 만들 필요가 있다.

buf.append(l.current.representation.substring(0, 1));

한 줄에 여러개의 점이 존재하는 코드이다

 String character() {
         return representation.substring(0, 1);
      }
 void addTo(StringBuffer buf) {
         buf.append(character());
      }
void addTo(StringBuffer buf) {
         current.addTo(buf);
      }
l.addTo(buf);

꼬리 물기식으로 기능별 메소드를 분리할 수 있다.

  • 메소드를 재사용할 수 있음.
  • 메소드 이름 짓기 중요
  • 긴 코드를 이해하기 위해서 주석을 달 필요가 없음

규칙 5 : 축약 금지

클래스, 메서드, 변수 등 다양한 이름들이 존재한다. 많은 이름을 사용하면서 간단한 변수의 이름은 줄이려는 경향이 있다. 만약 단어 사용이 반복되어 축약하고 있다면 메소드를 활용할 기회를 놓치고 있는 것이다. 단어를 축약하면서 개발자들의 소통에 문제가 생기고 단어마다 주석을 달며 시간을 허비 할 수 있다. 주석은 필요한 내용만을 적고 클래스, 메서드, 변수명에 역할을 명확하게 작성하여 가독성을 높일 필요가 있다.

+) 클래스와 클래스 변수에 표현이 중복되지 않도록 해야함.

  • class Order 이고 메소드 shipOrder() Order 중복 -> 메소드명 ship() (변수도 마찬가지)

규칙 6 : 모든 엔티티를 작게 유지

50줄 이상 되는 클래스와 파일이 10개 이상인 패키지가 없어야한다.

50줄 이상인 클래스는 한가지 이상의 일을 한다고 보고 코드의 이해나 재사용을 점점 어렵게 한다. (50줄짜리 클래스는 스크롤 하지 않아도 됨) 클래스가 점점 작아지고 하는 일이 줄어들며 패키지 크기를 제한함에 따라 패키지가 하나의 목적을 달성하기 위해 모인 연관 클래스들의 집합을 나타낸다는 사실을 알아차리게 된다. 패키지 또한 클래스처럼 응집력 있고 단일한 목표가 있어야한다.

  • 50줄 이상으로 크기가 커지면 어쩔 수 없는 거임. (그 만큼 프로젝트 크기가 큼)

규칙 7 : 2개이상의 인스턴스 변수를 가진 클래스 사용금지

대부분의 클래스들이 간단하게 하나의 상태 변수를 처리하는 일을 맡아 마땅하지만, 몇몇 경우 둘이 필요할 때가 있습니다. 하지만 새로운 인스턴스 변수를 하나 더 추가하게 되면 클래스의 응집도는 떨어집니다.

class Name {
    String first;
    String middle;
    String last;
}

새로운 인스턴스 변수를 하나 더 기존 클래스에 추가하면 클래스의 응집도는 떨어진다. 일반적으로 많은 인스턴스 변수를 지닌 클래스를 대상으로 응집력 있는 단일 작업을 설명할 수 있는 경우는 거의 없다.

class Name {
    Surname family;
    GivenNames given;
}
class Surname {
    String family;
}
class GivenNames {
    List<String> names;
}

규칙 8 : 일급 콜렉션 사용

Map<String, String> map = new HashMap<>();
map.put("1", "A");
map.put("2", "B");
map.put("3", "C");
public class GameRanking {

    private Map<String, String> ranks;

    public GameRanking(Map<String, String> ranks) {
        this.ranks = ranks;
    }
}

Collection을 Wrapping하면서, 그 외 다른 멤버 변수가 없는 상태를 말함.

규칙3과 같은 원리이다. 또한 일반적인 콜렉션은 범용적으로 사용되기 위해 다양한 메소드들을 제공 하는데 클래스로 감싸면서(Wrap) 외부에 노출할 메소드를 조절할 수 있다.

일급 컬렉션 (First Class Collection)의 소개와 써야할 이유 자세한 내용 참고

규칙 9 : 게터 /세터/ 속성 사용 금지

  • 세터를 사용하지 않으면 객체가 생성된 후 임의로 값이 변하는 것을 방지할 수 있다. 외부에서 임의로 객체 내부의 값을 변경하지 않고 그 책임을 객체가 지도록 한다.
  • 클라이언트에서 게터를 통해 값을 가져와 확인하고(if-else) 뭔가 처리를 하려고 하지 말고 그냥 그 객체에 책임을 넘기도록 한다. Don't ask! Just Tell 이라고 한다.

참고자료

https://developerfarm.wordpress.com/

https://github.com/iamkyu/TIL/blob/master/object-calisthenics/object-calisthenics.md

https://blog.one0.me/java

https://jojoldu.tistory.com/412

2019-04-03 [java-study] 문자열 계산기 구현

|

문자열 계산기 구현

기능 요구사항

  • 사용자가 입력한 문자열 값에 따라 사칙연산을 수행할 수 있는 계산기를 구현해야 한다.
  • 문자열 계산기는 사칙연산의 계산 우선순위가 아닌 입력 값에 따라 계산 순서가 결정된다. 즉, 수학에서는 곱셈, 나눗셈이 덧셈, 뺄셈 보다 먼저 계산해야 하지만 이를 무시한다.
  • 예를 들어 “2 + 3 * 4 / 2”와 같은 문자열을 입력할 경우 2 + 3 * 4 / 2 실행 결과인 10을 출력해야 한다.

프로그래밍 요구사항

  • 메소드가 너무 많은 일을 하지 않도록 분리하기 위해 노력해 본다.
  • 규칙 1: 한 메서드에 오직 한 단계의 들여쓰기(indent)만 한다.
  • 규칙 2: else 예약어를 쓰지 않는다.

문자열 계산기 구현 중 학습(?)

if - else 문을 사용 하지 않는 이유

  • else문 사용시 복잡성 증가 -> else if 문 까지 비교를 해야 if 문이 끝남.
  • 한번에 이해하기 힘듬 -> if 문이 쉽다는 느낌이 강함
  • 의사소통이 쉬움

TDD란

테스트를 먼저 만들고 테스트를 통과하기 위한 것을 짜는 것.

TDD가 필요한 상황

  • 나에 대한 불확실성이 높은 경우
  • 외부적 불확실성이 높은 경우
  • 개발하는 중에 코들 많이 바꿔야 하는 경우
  • 다른사람이 유지 보수를 할 경우

효과

  • 피드백 증가
  • 남들에게 테스트 코드를 보여주고 그 코드를 직접 실행
  • 협력
    • 남이 짠 코드 쉽고 빠르게 이해
  • 불확실성에 대비
  • 버그를 줄일 수 있음.
  • 코드 복잡도가 떨어짐 -> 클린 코드 -> 유지보수 비용 감소

단점

  • 개발 시간이 늘어남
  • 개발 방식을 바꿔야함

Unit 사용법

    @Test
    public void testSum() {
        Calculator calculator = new Calculator();
        assertEquals(30, calculator.sum(10, 20));
    }

Test 클래스에서 @Test 와 메소드를 생성 -> assertEqulas(); 메소드를 이용하여 구현할 코드를 테스트

@Before
public void setUp() {
    calculator = new StringCalculator();
}

test 코드를 사용하기 전에 미리 구현할 수 있는 메소드 @before, public void setUp() 사용

소스코드

StringCalculator.java

import java.util.Scanner;

public class StringCalculator {

    public String input() {
        Scanner scanner = new Scanner(System.in);
        return scanner.nextLine();
    }

    public boolean isBlank(String input) {
        if (input.equals(" ") || input == null)
            return true;
        return false;
    }

    public int makeResult(String input) {
        if (isBlank(input))
            throw new RuntimeException();
        return calculateSplitedString(splitBlank(input));
    }

    public String[] splitBlank(String str) {
        return str.split(" ");
    }

    public int toInt(String str) {
        return Integer.parseInt(str);
    }

    public int calculateSplitedString(String[] str) {
        int result = toInt(str[0]);
        for (int i = 0; i < str.length - 2; i += 2) {
            result = calculate(result, str[i + 1].charAt(0), toInt(str[i + 2]));
        }
        return result;
    }

    public int calculate(int firstValue, char operator, int secondValue) {
        if (operator == '+')
            return add(firstValue, secondValue);
        if (operator == '-')
            return subtract(firstValue, secondValue);
        if (operator == '*')
            return multiply(firstValue, secondValue);
        if (operator == '/')
            return divide(firstValue, secondValue);
        throw new RuntimeException();
    }

    public int add(int i, int j) {
        return i + j;
    }

    public int subtract(int i, int j) {
        return i - j;
    }

    public int multiply(int i, int j) {
        return i * j;
    }

    public int divide(int i, int j) {
        try {
            return i / j;
        } catch (ArithmeticException e) {
            System.out.println("숫자 0으로 나눌 수 없습니다.");
        }
        return i / j;
    }
}

StringCalculatorTest

import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.*;

public class StringCalculatorTest {
    StringCalculator calculator;

    @Before
    public void SetUp() {
        calculator = new StringCalculator();
    }

    @Test
    public void testAdd() {
        assertEquals(3, calculator.add(1, 2));
    }

    @Test
    public void testSubtract() {
        assertEquals(1, calculator.subtract(3, 2));
    }

    @Test
    public void testMultiply() {
        assertEquals(8, calculator.multiply(4, 2));
    }

    @Test
    public void testDivide() {
        assertEquals(2, calculator.divide(8, 4));
    }

    @Test
    public void testCalculate() {
        assertEquals(6, calculator.makeResult("1 + 2 * 4 / 2"));
    }

    @Test(expected = ArithmeticException.class)
    public void testDivideZero() {
        calculator.divide(3, 0);
    }

    @Test
    public void test계산() {
        assertEquals(5, calculator.calculate(1, '+', 4));
    }

    @Test(expected = RuntimeException.class)
    public void testIsBlankError() {
        calculator.makeResult(" ");
    }
}