Java 2

Week 5

 

Software Development Life Cycle Steps 4-6

Fraction Project, Movie Project DAO

4. Coding

  • The start() method in the FractionCalculator class needs full implementation.
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();
}

Validate the input

  • Call the splitCalculation() method by passing the user's input.
  • The value returned should be an array with three parts.
  • If an error occurs, call the static displayError method from the UIUtility class and call the continue statement to prompt for another equation.
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();
}

Validate the input

  • If no error exists, continue by using the first and third String parts returned to instantiate new Fraction objects  by calling the parseFraction method.
  • If an error occurs, call the static displayError method from the UIUtility class and call the continue statement to prompt for another equation.
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();
}

Perform mathematical operations

  • If no error exists, call the correct mathematical method based on the operator.
  • If a divide by zero error occurs, call the static displayError method from the UIUtility class and call the continue statement to prompt for another equation.
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();
}

Perform mathematical operations

  • Finally, display the result in a user-friendly format.
  • This code shows the user's input along with the output written as a mathematical equation.
  • Run the program!
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();
}

5. Testing

  • Read "Best Practices for Unit Testing in Java."

  • 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());
}

Mocking

  • 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

  • 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.

FractionCalculatorTest

  • 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());
        }
    }
}

Explanation of the Code

  • @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.

Explanation of the Code

  • 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.

FractionCalculatorTest

  • 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));
        }
    }
}

6. Deployment

  • 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>

6. Deployment

  • 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.

6. Deployment

  • 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.

6. Deployment

  • 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"

Java 2 - Week 5

By Marc Hauschildt

Java 2 - Week 5

  • 135