Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
# java-calculator-precourse
# java-calculator-precourse

## Feature Details
- CLI I/O
- 구분자 추출
- 수식 계산

## Code Flow
- 수식 입력
- (커스텀 구분자가 있다면) 커스텀 구분자 추출
- 수식 구성
- 수식 계산

## Design Pattern
- Hexagonal Architecture 도입
6 changes: 6 additions & 0 deletions src/main/java/calculator/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package calculator;

import calculator.adapter.in.InputTerminal;
import calculator.adapter.out.OutputTerminal;
import calculator.application.service.CalculatorService;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
CalculatorService calculatorService = new CalculatorService(new OutputTerminal(), new InputTerminal());
calculatorService.run();
}
}
10 changes: 10 additions & 0 deletions src/main/java/calculator/adapter/in/InputTerminal.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package calculator.adapter.in;

import calculator.application.port.inport.GetExpressionUserCase;
import camp.nextstep.edu.missionutils.Console;

public class InputTerminal implements GetExpressionUserCase {
public String getExpression() {
return Console.readLine();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package calculator.application.port.inport;

public interface GetExpressionUserCase {
String getExpression();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package calculator.application.port.outport;

import calculator.domain.enums.MessageConstant;

public interface ResultTerminal {
void printMessage(MessageConstant message);
void printResult(int result);
void handleException(RuntimeException e);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package calculator.application.service;

import calculator.application.port.inport.GetExpressionUserCase;
import calculator.application.port.outport.ResultTerminal;
import calculator.domain.Expression;
import calculator.domain.enums.MessageConstant;

public class CalculatorService {
private final ResultTerminal resultTerminal;
private final GetExpressionUserCase getExpressionUserCase;

public CalculatorService(ResultTerminal resultTerminal, GetExpressionUserCase getExpressionUserCase) {
this.resultTerminal = resultTerminal;
this.getExpressionUserCase = getExpressionUserCase;
}

public void run() {
try {
resultTerminal.printMessage(MessageConstant.START_GUIDE_MESSAGE);
String line = getExpressionUserCase.getExpression();
Expression expression = new Expression(line);
int result = expression.calculate();
resultTerminal.printResult(result);
} catch (RuntimeException e) {
resultTerminal.handleException(e);
}
}
}
47 changes: 47 additions & 0 deletions src/main/java/calculator/domain/Expression.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package calculator.domain;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

public class Expression {
private final List<Integer> operands;
private static final String CUSTOM_SEPARATOR_REGEX = "^//(.)\\\\n(.*)";
private static final String EXPRESSION_REGEX_BASE = "([0-9]+)[[%s]+([0-9]+)]*";

public Expression(String expressionLine) {
operands = new ArrayList<>();

String separator = "[,:]";
String expressionBody = expressionLine;

Matcher matcher = Pattern.compile(CUSTOM_SEPARATOR_REGEX).matcher(expressionBody);
if (matcher.find()) {
separator = String.format("[,:%s]", Pattern.quote(matcher.group(1)));
expressionBody = matcher.group(2);
}

validateExpression(expressionBody, separator);
parseOperand(expressionBody, separator);
}

private void validateExpression(String expressionBody, String separator) {
String fullRegex = String.format(EXPRESSION_REGEX_BASE, separator);
if (!expressionBody.matches(fullRegex)) {
throw new IllegalArgumentException("잘못된 수식입니다: " + expressionBody);
}
}

private void parseOperand(String body, String separatorRegex) {
Stream.of(body.split(separatorRegex))
.map(Integer::parseInt)
.forEach(operands::add);
}


public int calculate() {
return operands.stream().reduce(0, Math::addExact);
}
}
15 changes: 15 additions & 0 deletions src/main/java/calculator/domain/enums/MessageConstant.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package calculator.domain.enums;

public enum MessageConstant {
START_GUIDE_MESSAGE("덧셈할 문자열을 입력해 주세요."),
RESULT_MESSAGE("결과 : ");

private String message;

MessageConstant(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
93 changes: 87 additions & 6 deletions src/test/java/calculator/ApplicationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,100 @@
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class ApplicationTest extends NsTest {

@Test
void exampleTest() {
assertSimpleTest(() -> {
run("1,2:3");
assertThat(output()).contains("결과 : 6");
});
}

@Test
void constantExpressionTest() {
assertSimpleTest(() -> {
run("5");
assertThat(output()).contains("결과 : 5");
});
}

@Test
void customSeparatorTest() {
assertSimpleTest(() -> {
run("//;\\n3;4:5");
assertThat(output()).contains("결과 : 12");
});
}

@Test
void intOverflowTest() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException("20000000000"))
.isInstanceOf(IllegalArgumentException.class)
);
}

@Test
void longOverflowTest() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException("50000000000000000000"))
.isInstanceOf(IllegalArgumentException.class)
);
}

@Test
void severalCustomSeparatorTest() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException("//^&\\n3^4&5"))
.isInstanceOf(IllegalArgumentException.class)
);
}

@Test
void numberSeparatorTest() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException("//3\\n53:43"))
.isInstanceOf(IllegalArgumentException.class)
);
}

@Test
void emptyExpressionTest() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException(""))
.isInstanceOf(IllegalArgumentException.class)
);
}

@Test
void intAddingOverflowTest() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException("2000000000:2000000000"))
.isInstanceOf(IllegalArgumentException.class)
);
}

@Test
void longAddingOverflowTest() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException("5000000000000000000:5000000000000000000"))
.isInstanceOf(IllegalArgumentException.class)
);
}

@Test
void 커스텀_구분자_사용() {
void escapeCharTest() {
assertSimpleTest(() -> {
run("//;\\n1");
assertThat(output()).contains("결과 : 1");
run("//\\\\n3\\4\\5");
assertThat(output()).contains("결과 : 12");
});
}

@Test
void 예외_테스트() {
void emptyValueTest() {
assertSimpleTest(() ->
assertThatThrownBy(() -> runException("-1,2,3"))
.isInstanceOf(IllegalArgumentException.class)
assertThatThrownBy(() -> runException("//*\\n3**:,,4"))
.isInstanceOf(IllegalArgumentException.class)
);
}

Expand Down
60 changes: 60 additions & 0 deletions src/test/java/calculator/ExpressionTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package calculator;

import calculator.domain.Expression;
import org.junit.jupiter.api.Test;

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

class ExpressionTest {

@Test
void 기본_구분자_쉼표_콜론으로_계산() {
Expression expr = new Expression("1,2:3");
assertEquals(6, expr.calculate());
}

@Test
void 커스텀_구분자_세미콜론으로_계산() {
Expression expr = new Expression("//;\\n1;2;3");
assertEquals(6, expr.calculate());
}

@Test
void 커스텀_구분자_점으로_계산() {
Expression expr = new Expression("//.\\n4.5:6");
assertEquals(15, expr.calculate());
}

@Test
void 커스텀_구분자와_기본_구분자_혼합() {
Expression expr = new Expression("//|\\n7|8,9:10");
assertEquals(34, expr.calculate());
}

@Test
void 유효하지_않은_표현식_예외() {
assertThrows(IllegalArgumentException.class, () -> {
new Expression("1,,2:3");
});
}

@Test
void 커스텀_정의된_구분자가_아닌_문자_포함시_예외() {
assertThrows(IllegalArgumentException.class, () -> {
new Expression("//;\\n1;2-3");
});
}

@Test
void 숫자가_아닌_문자가_포함되면_예외() {
assertThrows(IllegalArgumentException.class, () -> {
new Expression("1,a:3");
});
}

@Test
void 한자리_숫자가_아닌_수() {
Expression expr = new Expression("1,10,100,3000");
assertEquals(3111, expr.calculate());
}
}