1
2
3
4
5
6
7
8
9 package com.ucoruh.calculator;
10
11 import static org.junit.jupiter.api.Assertions.assertEquals;
12 import static org.junit.jupiter.api.Assertions.assertTrue;
13
14 import java.io.ByteArrayOutputStream;
15 import java.io.PrintStream;
16 import java.nio.charset.StandardCharsets;
17
18 import org.junit.jupiter.api.AfterEach;
19 import org.junit.jupiter.api.BeforeEach;
20 import org.junit.jupiter.api.DisplayName;
21 import org.junit.jupiter.api.Nested;
22 import org.junit.jupiter.api.Test;
23
24
25
26
27
28
29
30
31
32
33 class CalculatorAppTest {
34
35 @Nested
36 @DisplayName("run: normal input")
37 class NormalInput {
38
39 @Test
40 @DisplayName("adds two numbers")
41 void add() {
42 assertEquals("5", CalculatorApp.run(new String[] {"2", "+", "3"}));
43 }
44
45 @Test
46 @DisplayName("subtracts two numbers")
47 void subtract() {
48 assertEquals("-1", CalculatorApp.run(new String[] {"2", "-", "3"}));
49 }
50
51 @Test
52 @DisplayName("multiplies two numbers")
53 void multiply() {
54 assertEquals("12", CalculatorApp.run(new String[] {"3", "*", "4"}));
55 }
56
57 @Test
58 @DisplayName("divides two numbers")
59 void divide() {
60 assertEquals("2.0", CalculatorApp.run(new String[] {"6", "/", "3"}));
61 }
62 }
63
64 @Nested
65 @DisplayName("run: boundary / edge input")
66 class BoundaryInput {
67
68 @Test
69 @DisplayName("no arguments prints usage instead of blocking")
70 void noArguments() {
71 assertEquals(CalculatorApp.USAGE, CalculatorApp.run(new String[] {}));
72 }
73
74 @Test
75 @DisplayName("null argument array prints usage instead of throwing")
76 void nullArguments() {
77 assertEquals(CalculatorApp.USAGE, CalculatorApp.run(null));
78 }
79
80 @Test
81 @DisplayName("too few arguments prints usage")
82 void tooFewArguments() {
83 assertEquals(CalculatorApp.USAGE, CalculatorApp.run(new String[] {"1", "+"}));
84 }
85
86 @Test
87 @DisplayName("too many arguments prints usage")
88 void tooManyArguments() {
89 assertEquals(CalculatorApp.USAGE, CalculatorApp.run(new String[] {"1", "+", "2", "3"}));
90 }
91
92 @Test
93 @DisplayName("Integer.MAX_VALUE operand is accepted")
94 void maxIntOperand() {
95 assertEquals("2147483647", CalculatorApp.run(new String[] {"2147483647", "+", "0"}));
96 }
97 }
98
99 @Nested
100 @DisplayName("run: invalid input")
101 class InvalidInput {
102
103 @Test
104 @DisplayName("non-numeric first operand is reported, not thrown")
105 void nonNumericFirstOperand() {
106 String result = CalculatorApp.run(new String[] {"abc", "+", "3"});
107 assertTrue(result.startsWith("Error: operands must be integers"));
108 }
109
110 @Test
111 @DisplayName("non-numeric second operand is reported, not thrown")
112 void nonNumericSecondOperand() {
113 String result = CalculatorApp.run(new String[] {"3", "+", "xyz"});
114 assertTrue(result.startsWith("Error: operands must be integers"));
115 }
116
117 @Test
118 @DisplayName("unknown operator is reported, not thrown")
119 void unknownOperator() {
120 String result = CalculatorApp.run(new String[] {"1", "%", "2"});
121 assertTrue(result.startsWith("Error: unknown operator"));
122 }
123
124 @Test
125 @DisplayName("division by zero is reported, not thrown")
126 void divisionByZero() {
127 assertEquals("Error: division by zero", CalculatorApp.run(new String[] {"1", "/", "0"}));
128 }
129 }
130
131 @Nested
132 @DisplayName("main: prints run()'s result and returns without reading stdin")
133 class MainMethod {
134
135 private final PrintStream originalOut = System.out;
136 private ByteArrayOutputStream capturedOut;
137
138 @BeforeEach
139 void redirectOut() {
140 capturedOut = new ByteArrayOutputStream();
141 System.setOut(new PrintStream(capturedOut, true, StandardCharsets.UTF_8));
142 }
143
144 @AfterEach
145 void restoreOut() {
146 System.setOut(originalOut);
147 }
148
149 @Test
150 @DisplayName("prints the computed result and exits without waiting for input")
151 void printsResult() {
152
153
154 CalculatorApp.main(new String[] {"4", "*", "5"});
155 assertEquals("20", capturedOut.toString(StandardCharsets.UTF_8).trim());
156 }
157
158 @Test
159 @DisplayName("prints the usage message for invalid arguments and exits")
160 void printsUsageForInvalidArguments() {
161 CalculatorApp.main(new String[] {"not-enough-args"});
162 assertEquals(CalculatorApp.USAGE, capturedOut.toString(StandardCharsets.UTF_8).trim());
163 }
164 }
165 }