Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[라빈] 온보딩 - 학습 테스트, 단위 테스트 #3

Merged
merged 18 commits into from
Feb 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ed7ba12
add : 요구사항 1 - split 했을 때 contains()를 활용해 변환 값이 맞는지 검증하는 테스트 코드 작성
giantim Feb 5, 2020
b7f56bf
add : 요구사항 2 - substring()을 활용해 올바른 문자열이 반환되는지 테스트
giantim Feb 5, 2020
4646843
add : 요구사항 3 - charAt() 메소드를 활용해 범위를 넘어가는 예외 테스트
giantim Feb 5, 2020
3994dee
add : 요구사항 1 - size() 메소드를 활용해 Set의 크기를 확인하는 테스트
giantim Feb 5, 2020
db637c0
add : 요구사항 2 - JUnit의 ParameterizedTest를 이용해 중복 코드를 제거
giantim Feb 5, 2020
1da46a8
add : 요구사항 3 - @CsvSource를 활용해 입력 값에 따라 결과 값이 다른 경우의 테스트
giantim Feb 5, 2020
43d67f6
add : 문자열 계산기 기능 구현 및 테스트 코드 작성
giantim Feb 7, 2020
75191ca
add : README 수정
giantim Feb 7, 2020
f5b3b85
Update README.md
giantim Feb 7, 2020
19caade
refact : operator를 enum 클래스로 만들어서 관리 / Validator -> ValidityInspector…
giantim Feb 8, 2020
006eded
refact : if 문의 나열 -> if ~ else if 문으로 변경 / 조건문의 단계별로 return
giantim Feb 8, 2020
470960a
refact : 입력 문자열의 공백 또는 null 검사 메소드를 하나로 통일 / 매직넘버 제거 / checkIsDoubleN…
giantim Feb 8, 2020
b3fbab4
modify : gitignore 수정
giantim Feb 8, 2020
1985f2b
refact : call stack 이 쌓이는 것을 방지하기 위해 잘못된 문자열이 입력될 시 계산기의 기능이 종료되도록 변경…
giantim Feb 8, 2020
061557d
refact : 여러 클래스에서 공통적으로 사용하는 0, 공백, 빈 문자열을 상수 클래스로 분리
giantim Feb 8, 2020
9d2cac8
refact : 변경한 메소드에 맞게 테스트 코드 수정 / MethodSource -> ValueSource 로 변경
giantim Feb 8, 2020
d5d53d5
Merge branch 'onboarding' of https://github.com/giantim/java-calculat…
giantim Feb 8, 2020
4f46d30
refact : Operator 를 컨벤션에 맞게 이름 수정 / Operator.find() 와 Operator.calcul…
giantim Feb 9, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
!gradle/wrapper/gradle-wrapper.jar
/out/
/target/
/src/main/java/nickname.txt
/src/main/java/study
/src/test/java/study/CabenetTest.java


### STS ###
.apt_generated
Expand Down
35 changes: 33 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,36 @@
# java-calculator
문자열 계산기 미션 저장소

## 우아한테크코스 코드리뷰
* [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md)
## 기능적 요구사항
- 문자열을 입력 받아 공백 단위로 split 하는 함수를 만든다
- 입력이 정상적인지 확인
- 입력이 없는지
- 숫자와 연산자 외의 다른 문자가 있는지
- split 된 결과가 정상적인지 확인
- 숫자와 연산자의 순서가 맞는지

- 사칙연산을 하는 함수를 만든다
- 나눗셈의 경우 0으로 나누는 경우에 대한 예외 처리
- 입력 값이 자료형의 범위를 넘어 가는 경우에 대한 예외 처리

- split 된 결과 값이 순서대로 연산이 되는 함수를 만든다

- 연산 결과를 출력하는 함수를 만든다

- 단위별로 테스트 하는 함수를 만든다
- split 테스트를 진행한다
- 입력으로 null, 공백, 유효하지 않은 double 범위의 수, 숫자와 연산자 외의 문자열
- split 할 수 없는 문자열, 숫자와 연산자 순서가 바뀐 문자열, 정상 계산식
- 사칙연산 테스트
- 사칙연산의 결과 값이 실제 값과 맞는지 테스트
- 0으로 나누었을 때, double 범위를 넘어가는 결과 값


## 비기능적 요구사항
- indent 2까지 허용한다.
- 메소드는 15줄 까지 허용한다.
- switch문 사용하지 않는다.
- 하드코딩하지 않는다.
- 변수와 메소드 명명은 컨벤션에 따른다.
- 자바 코드 컨벤션을 지키면서 프로그래밍 한다.
- 함수의 인자 수를 3개까지만 허용한다.
13 changes: 13 additions & 0 deletions src/main/java/calculator/Calculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package calculator;

public class Calculator {
public double calculate(String[] splitedInput) {
double left = Double.valueOf(splitedInput[0]);
for (int i = 1; i < splitedInput.length; i = i + 2) {
String operator = splitedInput[i];
double right = Double.valueOf(splitedInput[i + 1]);
left = Operator.find(operator).calculate(left, right);
}
return left;
}
}
8 changes: 8 additions & 0 deletions src/main/java/calculator/Constant.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package calculator;

public class Constant {
public final static int ZERO = 0;

public final static String BLANK = " ";
public final static String EMPTY_STRING = "";
}
17 changes: 17 additions & 0 deletions src/main/java/calculator/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package calculator;

public class Main {
public static void main(String[] args) {
UserInputScanner userInputScanner = new UserInputScanner();
Calculator calculator = new Calculator();
Output output = new Output();

try {
String[] splitString = userInputScanner.splitUserInputString();
double result = calculator.calculate(splitString);
output.printResult(result);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
47 changes: 47 additions & 0 deletions src/main/java/calculator/Operator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package calculator;

import java.util.Arrays;
import java.util.function.BiFunction;

public enum Operator {
PLUS("+",
(left, right) -> (left + right)),
MINUS("-",
(left, right) -> (left - right)),
MULTIPLICATION("*",
(left, right) -> (left * right)),
DIVISION("/",
(left, right) -> (left / right));

private String symbol;
private BiFunction<Double, Double, Double> expression;

Operator(String symbol, BiFunction<Double, Double, Double> expression) {
this.symbol = symbol;
this.expression = expression;
}

private String getSymbol() {
return this.symbol;
}

public static Operator find(String operator) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

잘못된 입력에 대한 예외는 처리해줬으면 더 좋았겠네요 :)

return Arrays.stream(Operator.values())
.filter(op -> op.getSymbol().equals(operator))
.findFirst()
.get();
}

public double calculate(double left, double right) {
return expression.apply(left, right);
}

public static boolean isRightOperator(String operator) {
return Arrays.stream(Operator.values())
.anyMatch(op -> op.getSymbol().equals(operator));
}

public static boolean isDivisionOperator(String operator) {
return "/".equals(operator);
}
}
7 changes: 7 additions & 0 deletions src/main/java/calculator/Output.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package calculator;

public class Output {
public void printResult(double result) {
System.out.println("결과 값은 : " + result);
}
}
22 changes: 22 additions & 0 deletions src/main/java/calculator/UserInputScanner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package calculator;

import java.util.Scanner;

public class UserInputScanner {
private ValidityInspector validityInspector = new ValidityInspector();
private Scanner scanner = new Scanner(System.in);

private String inputStringToUser() {
System.out.print("계산식을 입력하시오: ");
String inputLine = scanner.nextLine();
validityInspector.checkUserInputIsBlankOrEmpty(inputLine);
return inputLine;
}

public String[] splitUserInputString() {
String input = inputStringToUser();
String[] splitInput = input.split(Constant.BLANK);
validityInspector.checkCanConvertUserInputToNumberAndOperator(splitInput);
return splitInput;
}
}
60 changes: 60 additions & 0 deletions src/main/java/calculator/ValidityInspector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package calculator;

public class ValidityInspector {
public void checkUserInputIsBlankOrEmpty(String input) {
if (isBlank(input) || isEmpty(input)) {
throw new IllegalArgumentException("공백 또는 빈 문자열을 입력하셨습니다.");
}
}

private boolean isEmpty(String input) {
return input.equals(Constant.EMPTY_STRING);
}

private boolean isBlank(String input) {
return input.equals(Constant.BLANK);
}

public void checkCanConvertUserInputToNumberAndOperator(String[] splitedInput) {
checkSplitedInputEmpty(splitedInput);
String firstClause = splitedInput[0];
checkCorrectDoubleNumber(firstClause);
try {
for (int i = 1; i < splitedInput.length; i = i + 2) {
String operator = splitedInput[i];
String secondClause = splitedInput[i + 1];
checkDivideByZero(operator, secondClause);
checkCorrectOperator(operator);
checkCorrectDoubleNumber(secondClause);
}
} catch (ArrayIndexOutOfBoundsException e) {
throw new ArrayIndexOutOfBoundsException("잘못된 계산식을 입력하였습니다.");
}
}

private void checkCorrectDoubleNumber(String s) {
try {
Double.valueOf(s);
} catch (Exception e) {
throw new IllegalArgumentException("잘못된 수를 입력하였습니다.");
}
}

private void checkCorrectOperator(String s) {
if (!Operator.isRightOperator(s)) {
throw new IllegalArgumentException("잘못된 연산자를 입력하였습니다.");
}
}

private void checkSplitedInputEmpty(String[] splitedInput) {
if (splitedInput.length == 0) {
throw new IllegalArgumentException("공백을 입력하였습니다.");
}
}

private void checkDivideByZero(String operator, String secondClause) {
if (Operator.isDivisionOperator(operator) && Integer.toString(Constant.ZERO).equals(secondClause)) {
throw new IllegalArgumentException("0으로 나누는 식을 입력하셨습니다.");
}
}
}
64 changes: 64 additions & 0 deletions src/test/java/calculator/CalculatorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package calculator;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

public class CalculatorTest {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MethodSource 이 외에도 여러가지 ParameterizedTest 를 위한 Source 들이 제공됩니다 :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MethodSource 로 테스트 코드를 작성했었던 이유는 이전에 테스트를 할 때 Double.toString() 을 이용해서 테스트를 해보려고 했었기 때문이었습니다. 목적에 맞는 Source 를 사용하도록 코드를 변경했습니다. 잘 몰랐던 부분인데 감사합니다 :)

private Calculator calculator;
private ValidityInspector validityInspector;

@BeforeEach
public void setUp() {
calculator = new Calculator();
validityInspector = new ValidityInspector();
}

@ParameterizedTest
@ValueSource(strings = {" ", ""})
public void checkUserInputIsBlankOrEmptyTest(String input) {
Assertions.assertThatThrownBy(() -> {
validityInspector.checkUserInputIsBlankOrEmpty(input);
}).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("공백 또는 빈 문자열을 입력하셨습니다.");
}

@ParameterizedTest
@ValueSource(strings = {" ", "q + w + e", "1+2+3", "+ + 2", "1 abcd 2", "2 * 65 / 0", "2 + 3/", "/1 + 55", "1 + a3"})
public void checkCanConvertUserInputToNumberAndOperatorTest(String input) {
String[] splitData = input.split(Constant.BLANK);
Assertions.assertThatThrownBy(() -> {
validityInspector.checkCanConvertUserInputToNumberAndOperator(splitData);
}).isInstanceOf(IllegalArgumentException.class);
}

@Test
public void addTest() {
String[] numericalExpression = {"1", "+", "2"};
Double result = calculator.calculate(numericalExpression);
Assertions.assertThat(result).isEqualTo(3);
}

@Test
public void subtractTest() {
String[] numericalExpression = {"1", "-", "2"};
Double result = calculator.calculate(numericalExpression);
Assertions.assertThat(result).isEqualTo(-1);
}

@Test
public void multipleTest() {
String[] numericalExpression = {"1", "*", "2"};
Double result = calculator.calculate(numericalExpression);
Assertions.assertThat(result).isEqualTo(2);
}

@Test
public void divideTest() {
String[] numericalExpression = {"1", "/", "2"};
Double result = calculator.calculate(numericalExpression);
Assertions.assertThat(result).isEqualTo(0.5d);
}
}
Empty file removed src/test/java/empty.txt
Empty file.
44 changes: 44 additions & 0 deletions src/test/java/study/SetTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package study;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.HashSet;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.assertTrue;

public class SetTest {
private Set numbers;

@BeforeEach
void setUp() {
numbers = new HashSet<>();
numbers.add(1);
numbers.add(1);
numbers.add(2);
numbers.add(3);
}

@Test
void sizeTest() {
int size = numbers.size();
Assertions.assertThat(size).isEqualTo(4);
}

@ParameterizedTest
@ValueSource(ints = {1, 1, 2, 3})
void containTest(int number) {
assertTrue(numbers.contains(number));
}

@ParameterizedTest
@CsvSource(value = {"ravie-RAVIE", "Lavine-LAVINE", "orange-ORANKE", "dog-DoG"}, delimiter = '-')
void equalUpperCase(String input, String upperCase){
Assertions.assertThat(upperCase).isEqualTo(input.toUpperCase());
}
}
33 changes: 33 additions & 0 deletions src/test/java/study/StringTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package study;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.assertj.core.api.Assertions;

public class StringTest {
@Test
void split() {
String value = "1,2";
String[] result = value.split(",");
Assertions.assertThat(result).contains("1");
Assertions.assertThat(result).contains("2");
String[] temp = {"1", "2"};
Assertions.assertThat(result).containsExactly(temp);
}

@Test
void substring() {
String value = "(1,2)";
String result = value.substring(1, value.length() - 1);
Assertions.assertThat(result).contains("1,2");
}

@Test
@DisplayName("charAt method out of bounds test")
void charAtTest() {
String value = "abc";
Assertions.assertThatThrownBy(() -> {
value.charAt(10);
}).isInstanceOf(StringIndexOutOfBoundsException.class).hasMessageContaining("String index out of range");
}
}