Marc Hauschildt
Web Technologies and Computer Software Development Instructor at Kirkwood Community College in Cedar Rapids, IA.
Week 5
Software Development Life Cycle Steps 4-6
Fraction Project, Movie Project DAO
public static void start() {
fractionGreet();
while(true) {
String input = getString("Enter your equation (or 'q' to quit)");
if(input.equalsIgnoreCase("q") || input.equalsIgnoreCase("quit")) {
break;
}
// Todo: Validate the input
// Todo: Perform mathematical operations
// Todo: Display output
}
fractionGoodbye();
pressEnterToContinue();
}public static void start() {
fractionGreet();
while(true) {
String input = getString("Enter your equation (or 'q' to quit)");
if(input.equalsIgnoreCase("q") || input.equalsIgnoreCase("quit")) {
break;
}
String[] parts = null;
try {
// Find the operator and split the input string into three parts
parts = splitCalculation(input);
} catch (IllegalArgumentException e) {
// Catches errors from parsing two fractions with an operator
displayError(e.getMessage());
continue;
}
}
fractionGoodbye();
pressEnterToContinue();
}public static void start() {
fractionGreet();
while(true) {
String input = getString("Enter your equation (or 'q' to quit)");
if(input.equalsIgnoreCase("q") || input.equalsIgnoreCase("quit")) {
break;
}
String[] parts = null;
try {
// Find the operator and split the input string into three parts
parts = splitCalculation(input);
} catch (IllegalArgumentException e) {
// Catches errors from parsing two fractions with an operator
displayError(e.getMessage());
continue;
}
String fractionStr1 = parts[0];
String operator = parts[1];
String fractionStr2 = parts[2];
Fraction f1 = null;
Fraction f2 = null;
try {
// Parse the string representations into Fraction objects
f1 = parseFraction(fractionStr1);
f2 = parseFraction(fractionStr2);
} catch (Exception e) {
// Catches errors from parsing a fraction or zero denominator
displayError(e.getMessage());
continue;
}
}
fractionGoodbye();
pressEnterToContinue();
}public static void start() {
fractionGreet();
while(true) {
String input = getString("Enter your equation (or 'q' to quit)");
if(input.equalsIgnoreCase("q") || input.equalsIgnoreCase("quit")) {
break;
}
String[] parts = null;
try {
// Find the operator and split the input string into three parts
parts = splitCalculation(input);
} catch (IllegalArgumentException e) {
// Catches errors from parsing two fractions with an operator
displayError(e.getMessage());
continue;
}
String fractionStr1 = parts[0];
String operator = parts[1];
String fractionStr2 = parts[2];
Fraction f1 = null;
Fraction f2 = null;
try {
// Parse the string representations into Fraction objects
f1 = parseFraction(fractionStr1);
f2 = parseFraction(fractionStr2);
} catch (Exception e) {
// Catches errors from parsing a fraction or zero denominator
displayError(e.getMessage());
continue;
}
Fraction result;
switch (operator) {
case "+":
result = f1.add(f2);
break;
case "-":
result = f1.subtract(f2);
break;
case "*":
result = f1.multiply(f2);
break;
case "/":
try {
result = f1.divide(f2);
} catch (ArithmeticException e) {
// Catches errors when f2 is zero
displayError(e.getMessage());
continue;
}
break;
default:
// This case is technically handled by splitCalculation, but serves as a safeguard.
displayError("Invalid operator '" + operator + "'. Please use +, -, *, /.");
continue;
}
}
fractionGoodbye();
pressEnterToContinue();
}public static void start() {
fractionGreet();
while(true) {
String input = getString("Enter your equation (or 'q' to quit)");
if(input.equalsIgnoreCase("q") || input.equalsIgnoreCase("quit")) {
break;
}
String[] parts = null;
try {
// Find the operator and split the input string into three parts
parts = splitCalculation(input);
} catch (IllegalArgumentException e) {
// Catches errors from parsing two fractions with an operator
displayError(e.getMessage());
continue;
}
String fractionStr1 = parts[0];
String operator = parts[1];
String fractionStr2 = parts[2];
Fraction f1 = null;
Fraction f2 = null;
try {
// Parse the string representations into Fraction objects
f1 = parseFraction(fractionStr1);
f2 = parseFraction(fractionStr2);
} catch (Exception e) {
// Catches errors from parsing a fraction or zero denominator
displayError(e.getMessage());
continue;
}
Fraction result;
switch (operator) {
case "+":
result = f1.add(f2);
break;
case "-":
result = f1.subtract(f2);
break;
case "*":
result = f1.multiply(f2);
break;
case "/":
try {
result = f1.divide(f2);
} catch (ArithmeticException e) {
// Catches errors when f2 is zero
displayError(e.getMessage());
continue;
}
break;
default:
// This case is technically handled by splitCalculation, but serves as a safeguard.
displayError("Invalid operator '" + operator + "'. Please use +, -, *, /.");
continue;
}
System.out.printf("Result: %s %s %s = %s%n%n",
f1.toMixedNumber(), operator, f2.toMixedNumber(), result.toMixedNumber()
);
}
fractionGoodbye();
pressEnterToContinue();
}Step 3.9 suggests using a framework like Mockito to mock objects.
A mock object is a dummy object that looks and acts like a real object but is completely under your control during a test.
The FractionCalculator.start() method is a good example of something that could be mocked.
// Inside FractionCalculator.start()
while (true) {
// Dependency 1: Waits for a real person to type on the console
String input = getString("Enter calculation or type 'quit'", false);
// ... lots of logic ...
// Dependency 2: Prints an error message to the console
displayError(e.getMessage());
}If you wanted to write a unit test for the logic inside this while loop, you'd have a problem:
The test would depend on the UserInput and UIUtility classes working correctly. You'd be testing three things at once, not just the FractionCalculator.
The test would halt and wait for someone to physically type "1/2 + 1/2" into the console. This completely breaks automated testing.
It's Difficult to Test Edge Cases. How would you test an invalid operator? You'd have to run the test manually over and over.
Mocks solve this. We can replace the real UserInput and UIUtility with mock versions that we control.
Mockito is the most popular Java library for creating and managing these mock objects. It gives you a simple API to:
Create Mocks: Instantiate a "dummy object" of a class.
Stub Methods: Tell a mock method what to return when it's called. (e.g., "When getString is called, pretend the user typed '1/2 + 3/4'").
Verify Interactions: Check if a method on your mock was actually called. (e.g., "After running the code, verify that displayError was called exactly one time.").
Add mockito-core to the pom.xml file.
Also add Mockito JUnit support to the pom.xml file.
Write tests for your FractionCalculator.start() method.
We want to simulate a user typing a valid calculation and then typing "quit".
import edu.kirkwood.controller.FractionCalculator;
import edu.kirkwood.view.UIUtility;
import edu.kirkwood.view.UserInput;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class FractionCalculatorTest {
@Test
@DisplayName("Test start() loop with a valid calculation and quit command")
void start_withValidInput_thenQuits() {
// We are mocking static methods, so we need to use mockStatic in a try-with-resources block.
try (MockedStatic<UserInput> staticUserInput = mockStatic(UserInput.class);
MockedStatic<UIUtility> staticUIUtility = mockStatic(UIUtility.class)) {
// 1. ARRANGE (Stubbing the methods)
// Tell our mock UserInput what to do when getString is called.
// It will return "1/2 + 1/2" the first time, and "quit" the second time.
staticUserInput.when(() -> UserInput.getString(anyString(), anyBoolean()))
.thenReturn("1/2 + 1/2", "quit");
// 2. ACT
// Run the method we are testing.
FractionCalculator.start();
// 3. ASSERT (Verifying interactions)
// We can verify that our mock methods were called as expected.
// Let's verify that displayError was NEVER called, because the input was valid.
staticUIUtility.verify(() -> UIUtility.displayError(anyString()), never());
}
}
}@ExtendWith(MockitoExtension.class): This tells JUnit 5 to activate the Mockito framework for this test class.mockStatic(...): This is the special command from mockito-inline to create a mock for a class with static methods. The try-with-resources block is the safe way to ensure the mock is automatically closed after the test.when(...).thenReturn(...): This is the core of "stubbing".
when(() -> UserInput.getString(anyString(), anyBoolean())): We are telling Mockito, "When you see a call to UserInput.getString with any string and any boolean...".thenReturn("1/2 + 1/2", "quit"): "...the first time, return "1/2 + 1/2". The second time, return "quit"." This allows us to control the while loop and test its logic without any real user input.verify(...): This is how we check if our code behaved as expected.
staticUIUtility.verify(...): We are checking interactions with our mocked UIUtility class.() -> UIUtility.displayError(...): We specify the exact method call we are looking for.times(1) or never(): We specify how many times we expected it to be called. This is incredibly powerful for confirming that error-handling logic was (or was not) triggered.We want to simulate a user typing an invalid calculation.
import edu.kirkwood.controller.FractionCalculator;
import edu.kirkwood.view.UIUtility;
import edu.kirkwood.view.UserInput;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class FractionCalculatorTest {
@Test
@DisplayName("Test start() loop with an invalid operator")
void start_withInvalidOperator_displaysErrorAndContinues() {
try (MockedStatic<UserInput> staticUserInput = mockStatic(UserInput.class);
MockedStatic<UIUtility> staticUIUtility = mockStatic(UIUtility.class)) {
// ARRANGE: Simulate the user typing bad input, then quitting.
staticUserInput.when(() -> UserInput.getString(anyString(), anyBoolean()))
.thenReturn("1/2 ^ 1/2", "quit");
// ACT
FractionCalculator.start();
// ASSERT: Verify that displayError was called exactly ONCE with the expected message.
staticUIUtility.verify(() -> UIUtility.displayError(
"Invalid format. Ensure operator (+, -, *, /) has a space on both sides."
), times(1));
}
}
}It is common to package the project in a single executable JAR for distribution.
You must configure the maven-jar-plugin within the pom.xml to define the main class entry point in a Maven Java project.
The mainClass element specifies the fully qualified name of the class containing the main() method.
Change com.yourpackage.YourMainClass to edu.kirkwood.CalculatorApp
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version> <!-- Use a recent version -->
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.yourpackage.YourMainClass</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>Note that the red /target file does not contain a JAR file.
Run this command to execute the Maven package
mvn clean package
All tests must pass for the JAR to be built.
If a BUILD SUCCESS message displays, the JAR file will now exist in the /target folder.
The name is based on the artifactId and version specified in the pom.xml file.
Open the "External Libraries" folder to see the JUnit JAR files that our project is using.
In Maven's versioning scheme, "RC" stands for "Release Candidate." This indicates a version of a software project that is considered stable and feature-complete, and is being prepared for a final release.
Do not push the contents of the /target folder to GitHub.
Your project should have a .gitignore file that tells Git to ignore that directory.
To run a Java JAR file from the command line, all you need is a Java Runtime Environment (JRE) or Java Development Kit (JDK) installed on your system.
Open your command-line interface.
Navigate to the directory where your JAR file is located.
Execute the JAR file using the java -jar command, followed by the name of your JAR file
java -jar YourApplication.jar
If your JAR file is in a different location, you can provide the full path. If the path contains spaces, enclose it in double quotes:
java -jar "C:\path with spaces\to\YourApplication.jar"
On my computer the full command is:
java -jar "C:\Users\k0519415\OneDrive\Kirkwood\Java II\2025-26\Calculators\target\Calculators-1.0-SNAPSHOT.jar"
By Marc Hauschildt
Web Technologies and Computer Software Development Instructor at Kirkwood Community College in Cedar Rapids, IA.