Java 2

Week 4

 

Software Development Life Cycle, Steps 4-5

Fraction Project

5. Testing

  • Inside the FractionCalculator class, create a method that takes in a String input and returns a String[] array output.
  • The purpose of this method is to split the user input string into three parts: first fraction, operator, and second fraction.
  • The method will throw an IllegalArgumentException if the input format or operator is invalid.
  • To distinguish a division symbol / from a fraction, we will require that mathematical operators are surrounded by spaces.
/**
 * Splits the user input string into three parts: first fraction, operator, and second fraction.
 *
 * @param input the raw input string from the user.
 * @return a String array of size 3.
 * @throws IllegalArgumentException if the input format or operator is invalid.
 */
private static String[] splitCalculation(String input) {
    return new String[]{};
}

5. Testing

  • Create a FractionCalculatorTest class.
  • This class does not need an @BeforeEach setup() method, because we will only be testing static methods.
  • Write tests for the splitCalculation method.
  • Start by testing addition.
  • I will discuss Arrange, Act, and Assert on the next slide.
@Test
@DisplayName("Test splitCalculation with addition operator")
void splitCalculationWithAddition() {
    // Arrange
    String input = "1/2 + 3/4";
    String[] expected = {"1/2", "+", "3/4"};

    // Act
    String[] actual = FractionCalculator.splitCalculation(input);

    // Assert
    assertArrayEquals(expected, actual);
}

5. Testing

  • The Arrange, Act, Assert (AAA) pattern is a widely adopted structure for writing clear and effective unit tests. It divides a unit test into three distinct phases:
  • Arrange: In this phase, the necessary preconditions and inputs for the test are set up. This includes creating and configuring objects, initializing data, and establishing the expected values to be returned.
  • Act: This phase involves executing the specific method being tested. The result is the actual value returned from the method.
  • Assert: This phase involves using assertion methods to confirm whether the code under test behaved as intended.
  • By following the AAA pattern, unit tests become more readable, maintainable, and focused.

5. Testing

  • Next, test subtraction.
  • Test a mixed number as the first fraction.
@Test
@DisplayName("Test splitCalculation with subtraction operator and mixed numbers")
void splitCalculationWithSubtractionAndMixedNumbers() {
    // Arrange
    String input = "3 1/4 - 1/2";
    String[] expected = {"3 1/4", "-", "1/2"};

    // Act
    String[] actual = FractionCalculator.splitCalculation(input);

    // Assert
    assertArrayEquals(expected, actual);
}

5. Testing

  • Next, test multiplication.
  • Test a mixed number as the second fraction.
  • Test negative fractions
@Test
@DisplayName("Test splitCalculation with multiplication and negative numbers")
void splitCalculationWithMultiplicationAndNegatives() {
    // Arrange
    String input = "-5 * -2 1/3";
    String[] expected = {"-5", "*", "-2 1/3"};

    // Act
    String[] actual = FractionCalculator.splitCalculation(input);

    // Assert
    assertArrayEquals(expected, actual);
}

5. Testing

  • Next, test division.
@Test
@DisplayName("Test splitCalculation with division operator")
void splitCalculationWithDivision() {
    // Arrange
    String input = "10/3 / 5";
    String[] expected = {"10/3", "/", "5"};

    // Act
    String[] actual = FractionCalculator.splitCalculation(input);

    // Assert
    assertArrayEquals(expected, actual);
}

5. Testing

  • Next, test for extra spaces that need to be trimmed.
@Test
@DisplayName("Test splitCalculation with extra spaces in input")
void splitCalculationWithExtraSpaces() {
    // Arrange
    String input = "  1/2   +   3/4  ";
    String[] expected = {"1/2", "+", "3/4"};

    // Act
    String[] actual = FractionCalculator.splitCalculation(input);

    // Assert
    assertArrayEquals(expected, actual);
}

5. Testing

@Test
@DisplayName("Test splitCalculation with no spaces around operator should throw exception")
void splitCalculationWithNoSpacesAroundOperator() {
    // Arrange
    String input = "1/2+3/4";

    // Act & Assert
    Exception exception = assertThrows(IllegalArgumentException.class, () -> {
        FractionCalculator.splitCalculation(input);
    });

    String expectedMessage = "Invalid format. Ensure operator (+, -, *, /) has a space on both sides.";
    String actualMessage = exception.getMessage();
    assertTrue(actualMessage.contains(expectedMessage));
}

5. Testing

  • Next, test for invalid input due to a lack of space around the operator.
  • If the method throws multiple exceptions for various reasons, you may want to use the Exception returned from the assertThrows method to verify that the correct error message was thrown.
@Test
@DisplayName("Test splitCalculation with no spaces around operator should throw exception")
void splitCalculationWithNoSpacesAroundOperator() {
    // Arrange
    String input = "1/2+3/4";

    // Act & Assert
    Exception exception = assertThrows(IllegalArgumentException.class, () -> {
        FractionCalculator.splitCalculation(input);
    });

    String expectedMessage = "Invalid format.";
    String actualMessage = exception.getMessage();
    assertTrue(actualMessage.contains(expectedMessage));
}

5. Testing

  • Next, test for an invalid operator.
@Test
@DisplayName("Test splitCalculation with unknown operator should throw exception")
void splitCalculationWithUnknownOperator() {
    // Arrange
    String input = "1/2 ^ 3/4";

    // Act & Assert
    assertThrows(IllegalArgumentException.class, () -> {
        FractionCalculator.splitCalculation(input);
    });
}

5. Testing

  • Next, test for a missing fraction.
@Test
@DisplayName("Test splitCalculation with missing first fraction should throw exception")
void splitCalculationWithMissingFirstFraction() {
    // Arrange
    String input = " + 3/4";

    // Act & Assert
    Exception exception = assertThrows(IllegalArgumentException.class, () -> {
        FractionCalculator.splitCalculation(input);
    });

    String expectedMessage = "Missing the first fraction";
    String actualMessage = exception.getMessage();
    assertTrue(actualMessage.contains(expectedMessage));
}

@Test
@DisplayName("Test splitCalculation with missing second fraction should throw exception")
void splitCalculationWithMissingSecondFraction() {
    // Arrange
    String input = "1/2 + ";

    // Act & Assert
    Exception exception = assertThrows(IllegalArgumentException.class, () -> {
        FractionCalculator.splitCalculation(input);
    });

    String expectedMessage = "Missing the second fraction";
    String actualMessage = exception.getMessage();
    assertTrue(actualMessage.contains(expectedMessage));
}

Final Code Mon-Wed

@Test
void splitCalculationWithAddition() {
    // Arrange - define input and expected outputs
    String input = "1/2 + 3/4";
    String[] expected = {"1/2", "+", "3/4"};
    // Act - call the method to be tested
    String[] actual = FractionCalculator.splitCalculation(input);
    // Assert - call one of the assert methods
    assertArrayEquals(expected, actual);
}

@Test
void splitCalculationWithSubtraction() {
    // Arrange - define input and expected outputs
    String input = "3 1/4 - 1/2";
    String[] expected = {"3 1/4", "-", "1/2"};
    // Act - call the method to be tested
    String[] actual = FractionCalculator.splitCalculation(input);
    // Assert - call one of the assert methods
    assertArrayEquals(expected, actual);
}

@Test
void splitCalculationWithMultiplication() {
    // Arrange - define input and expected outputs
    String input = "-5 * -2 1/3";
    String[] expected = {"-5", "*", "-2 1/3"};
    // Act - call the method to be tested
    String[] actual = FractionCalculator.splitCalculation(input);
    // Assert - call one of the assert methods
    assertArrayEquals(expected, actual);
}

@Test
void splitCalculationWithDivision() {
    // Arrange - define input and expected outputs
    String input = "10/3 / 5";
    String[] expected = {"10/3", "/", "5"};
    // Act - call the method to be tested
    String[] actual = FractionCalculator.splitCalculation(input);
    // Assert - call one of the assert methods
    assertArrayEquals(expected, actual);
}


@Test
void splitCalculationTooManySpaces() {
    // Arrange - define input and expected outputs
    String input = "     10/3     /    5     ";
    String[] expected = {"10/3", "/", "5"};
    // Act - call the method to be tested
    String[] actual = FractionCalculator.splitCalculation(input);
    // Assert - call one of the assert methods
    assertArrayEquals(expected, actual);
}

@Test
void splitCalculationNoSpacesAroundOperator() {
    // Arrange - define input and expected outputs
    String input = "10/3/5";
    // Act and Assert
    Exception e = assertThrows(IllegalArgumentException.class, () -> FractionCalculator.splitCalculation(input));

    // Arrange
    String expectedError = "Invalid format";
    // Act
    String actualError = e.getMessage();
    // Assert
    assertTrue(actualError.contains(expectedError));
}

@Test
void splitCalculationInvalidOperator1() {
    // Arrange - define input and expected outputs
    String input = "10/3 ! 5";
    // Act and Assert
    Exception e = assertThrows(IllegalArgumentException.class, () -> FractionCalculator.splitCalculation(input));

    // Arrange
    String expectedError = "Invalid format";
    // Act
    String actualError = e.getMessage();
    // Assert
    assertTrue(actualError.contains(expectedError));
}

@Test
void splitCalculationInvalidOperator2() {
    // Arrange - define input and expected outputs
    String input = "10/3 x 5";
    // Act and Assert
    Exception e = assertThrows(IllegalArgumentException.class, () -> FractionCalculator.splitCalculation(input));

    // Arrange
    String expectedError = "Invalid format";
    // Act
    String actualError = e.getMessage();
    // Assert
    assertTrue(actualError.contains(expectedError));
}

@Test
void splitCalculationFirstFractionMissing() {
    // Arrange - define input and expected outputs
    String input = " / 5";
    // Act and Assert
    Exception e = assertThrows(IllegalArgumentException.class, () -> FractionCalculator.splitCalculation(input));

    // Arrange
    String expectedError = "Missing fraction";
    // Act
    String actualError = e.getMessage();
    // Assert
    assertTrue(actualError.contains(expectedError));
}

@Test
void splitCalculationSecondFractionMissing() {
    // Arrange - define input and expected outputs
    String input = "5 / ";
    // Act and Assert
    Exception e = assertThrows(IllegalArgumentException.class, () -> FractionCalculator.splitCalculation(input));

    // Arrange
    String expectedError = "Missing fraction";
    // Act
    String actualError = e.getMessage();
    // Assert
    assertTrue(actualError.contains(expectedError));
}

Final Code Tue-Thr

@Test
void splitInputWithAddition() {
    // Arrange
    String input = "1/2 + 3/4";
    String[] expected = {"1/2", "+", "3/4"};
    // Act
    String[] actual = MarcsFractionCalculator.splitInput(input);
    // Assert
    assertArrayEquals(expected, actual);
}

@Test
@DisplayName("Test splitCalculation with subtraction operator and mixed numbers")
void splitCalculationWithSubtractionAndMixedNumbers() {
    // Arrange
    String input = "3 1/4 - 1/2";
    String[] expected = {"3 1/4", "-", "1/2"};
    // Act
    String[] actual = MarcsFractionCalculator.splitInput(input);
    // Assert
    assertArrayEquals(expected, actual);
}

@Test
@DisplayName("Test splitCalculation with multiplication and negative numbers")
void splitCalculationWithMultiplicationAndNegatives() {
    // Arrange
    String input = "-5 * -2 1/3";
    String[] expected = {"-5", "*", "-2 1/3"};
    // Act
    String[] actual = MarcsFractionCalculator.splitInput(input);
    // Assert
    assertArrayEquals(expected, actual);
}

@Test
@DisplayName("Test splitCalculation with division operator")
void splitCalculationWithDivision() {
    // Arrange
    String input = "10/3 / 5";
    String[] expected = {"10/3", "/", "5"};
    // Act
    String[] actual = MarcsFractionCalculator.splitInput(input);
    // Assert
    assertArrayEquals(expected, actual);
}

@Test
@DisplayName("Test splitCalculation with extra spaces in input")
void splitCalculationWithExtraSpaces() {
    // Arrange
    String input = "  1/2   +   3/4  ";
    String[] expected = {"1/2", "+", "3/4"};

    // Act
    String[] actual = MarcsFractionCalculator.splitInput(input);

    // Assert
    assertArrayEquals(expected, actual);
}

@Test
void splitInputWithNoSpacesAroundOperator() {
    //Arrange
    String input = "1/2+3/4";
    // Act and Assert
    Executable actual = () -> MarcsFractionCalculator.splitInput(input);
    // Assert
    Exception e = assertThrows(IllegalArgumentException.class, actual);

    // Arrange
    String expectedErrorMsg = "Invalid format";
    //Act
    String actualErrorMsg = e.getMessage();
    assertTrue(actualErrorMsg.contains(expectedErrorMsg));
}

@Test
void splitInputWithInvalidOperator() {
    //Arrange
    String input = "1/2 ! 3/4";
    // Act and Assert
    assertThrows(IllegalArgumentException.class, () -> MarcsFractionCalculator.splitInput(input));
}

@Test
void splitInputWithInvalidFraction1() {
    //Arrange
    String input = " + 3/4";
    // Act and Assert
    Exception e = assertThrows(IllegalArgumentException.class, () -> MarcsFractionCalculator.splitInput(input));

    //Arrage
    String expectedErrorMsg = "First fraction is required";
    //Act
    String actualErrorMsg = e.getMessage();
    //Assert
    assertEquals(expectedErrorMsg, actualErrorMsg);
}

@Test
void splitInputWithInvalidFraction2() {
    //Arrange
    String input = "1/2 + ";
    // Act and Assert
    Exception e = assertThrows(IllegalArgumentException.class, () -> MarcsFractionCalculator.splitInput(input));

    //Arrage
    String expectedErrorMsg = "Second fraction is required";
    //Act
    String actualErrorMsg = e.getMessage();
    //Assert
    assertEquals(expectedErrorMsg, actualErrorMsg);
}

4. Coding

  • Implement code to pass the tests. 
  • This code checks if the input contains a +, -, *, or / surrounded by spaces. 
  • If not, it throws an IllegalArgumentException.
  • If yes, it sets the operator string and determines the string index of the operator.
/**
 * Splits the user input string into three parts: first fraction, operator, and second fraction.
 *
 * @param input the raw input string from the user.
 * @return a String array of size 3.
 * @throws IllegalArgumentException if the input format or operator is invalid.
 */
private static String[] splitCalculation(String input) {
	String operator = null;
    int operatorIndex = -1;
    
    // Find the operator with spaces around it to correctly handle negative numbers
    if (input.contains(" + ")) {
        operator = "+";
        operatorIndex = input.indexOf(" + ");
    } else if (input.contains(" - ")) {
        operator = "-";
        operatorIndex = input.indexOf(" - ");
    } else if (input.contains(" * ")) {
        operator = "*";
        operatorIndex = input.indexOf(" * ");
    } else if (input.contains(" / ")) {
        operator = "/";
        operatorIndex = input.indexOf(" / ");
    }
    
    if (operator == null) {
        throw new IllegalArgumentException("Invalid format. Ensure operator (+, -, *, /) has a space on both sides.");
    }
    
    return new String[]{};
}

4. Coding

  • Use the String class substring method to obtain the two fraction strings using the index of the operator.
  • Call the String class trim method to remove any extra white space.
  • If either String is an empty string, throw an exception.
  • All nine unit tests should pass.
/**
 * Splits the user input string into three parts: first fraction, operator, and second fraction.
 *
 * @param input the raw input string from the user.
 * @return a String array of size 3.
 * @throws IllegalArgumentException if the input format or operator is invalid.
 */
private static String[] splitCalculation(String input) {
	String operator = null;
    int operatorIndex = -1;
    
    // Find the operator with spaces around it to correctly handle negative numbers
    if (input.contains(" + ")) {
        operator = "+";
        operatorIndex = input.indexOf(" + ");
    } else if (input.contains(" - ")) {
        operator = "-";
        operatorIndex = input.indexOf(" - ");
    } else if (input.contains(" * ")) {
        operator = "*";
        operatorIndex = input.indexOf(" * ");
    } else if (input.contains(" / ")) {
        operator = "/";
        operatorIndex = input.indexOf(" / ");
    }
    
    if (operator == null) {
        throw new IllegalArgumentException("Invalid format. Ensure operator (+, -, *, /) has a space on both sides.");
    }
    
    String fractionStr1 = input.substring(0, operatorIndex).trim();
    String fractionStr2 = input.substring(operatorIndex + 3).trim(); // +3 to skip operator and spaces

    if (fractionStr1.isEmpty()) {
        throw new IllegalArgumentException("Missing the first fraction in the calculation.");
    }

    if (fractionStr2.isEmpty()) {
        throw new IllegalArgumentException("Missing the second fraction in the calculation.");
    }

    return new String[]{fractionStr1, operator, fractionStr2};
}

Final Code Mon-Wed

/**
 * Splits the user input string into three parts: fraction 1, operator, fraction 2
 * @param input the raw input string from the user
 * @return a String[] array of size 3
 * @throws IllegalArgumentException if the input format or operator is invalid
 */
public static String[] splitCalculation(String input) {
    String operator = "";
    int operatorIndex = -1;

    if(input.contains(" + ")) {
        operator = "+";
        operatorIndex = input.indexOf(" + ");
    } else if(input.contains(" - ")) {
        operator = "-";
        operatorIndex = input.indexOf(" - ");
    } else if(input.contains(" * ")) {
        operator = "*";
        operatorIndex = input.indexOf(" * ");
    } else if(input.contains(" / ")) {
        operator = "/";
        operatorIndex = input.indexOf(" / ");
    }

    if(operatorIndex == -1) {
        throw new IllegalArgumentException("Invalid format. Ensure operator (+, -, *, /) has a space on both sides.");
    }

    String fraction1 = input.substring(0, operatorIndex).trim();
    String fraction2 = input.substring(operatorIndex + 3).trim();
    
    if(fraction1.isEmpty()) {
        throw new IllegalArgumentException("Missing fraction 1.");
    }

    if(fraction2.isEmpty()) {
        throw new IllegalArgumentException("Missing fraction 2.");
    }
    
    return new String[]{fraction1, operator, fraction2};
}

Final Code Tue-Thr

/**
 * Split the user input into three parts: fraction 1, operator, fraction 2
 *
 * @param input the raw input string from the user
 * @return String[] array with three Strings
 * @throws IllegalArgumentException if input or operator are invalid
 */
public static String[] splitInput(String input) {
    String operator = "";
    int operatorIndex = -1;
    if(input.contains(" + ")) {
        operator = "+";
        operatorIndex = input.indexOf(" + ");
    } else if(input.contains(" - ")) {
        operator = "-";
        operatorIndex = input.indexOf(" - ");
    } else if(input.contains(" * ")) {
        operator = "*";
        operatorIndex = input.indexOf(" * ");
    } else if(input.contains(" / ")) {
        operator = "/";
        operatorIndex = input.indexOf(" / ");
    }

    if(operatorIndex == -1) {
        throw new IllegalArgumentException("Invalid format. Ensure operator (+, -, *, /) has space on both sides.");
    }

    String fraction1 = input.substring(0, operatorIndex).trim();
    String fraction2 = input.substring(operatorIndex + 3).trim();

    if(fraction1.isEmpty()) {
        throw new IllegalArgumentException("First fraction is required");
    }
    if(fraction2.isEmpty()) {
        throw new IllegalArgumentException("Second fraction is required");
    }

    return new String[]{fraction1, operator, fraction2};
}

5. Testing

  • Inside the FractionCalculator class, create a method that takes in a String input and returns a Fraction object.
  • The purpose of this method is to convert (parse) a String into a Fraction.
  • The method must handle whole numbers, fractions, and mixed numbers.
  • The method must throw exceptions for invalid input.
    • Non-numbers will throw a NumberFormatException
    • Invalid fraction format will throw an IllegalArgumentException
/**
 * Parses a string into a Fraction object.
 * Handles whole numbers, fractions, and mixed numbers.
 *
 * @param str the string to parse.
 * @return a Fraction object representing the parsed string.
 * @throws NumberFormatException if the number parts are not valid integers.
 * @throws IllegalArgumentException if the fraction format is invalid.
 */
public static Fraction parseFraction(String str) {
    return null;
}

5. Testing

  • Write tests for the parseFraction method.
  • Start by testing whole numbers, both positive and negative.
  • These examples are simple enough that I feel the Arrange, Act, and Assert structure is not necessary.
@Test
@DisplayName("Test parseFraction with a positive whole number")
void parseFractionWithPositiveWholeNumber() {
    Fraction f = FractionCalculator.parseFraction("5");
    assertEquals(5, f.getNumerator());
    assertEquals(1, f.getDenominator());
}

@Test
@DisplayName("Test parseFraction with a negative whole number")
void parseFractionWithNegativeWholeNumber() {
    Fraction f = FractionCalculator.parseFraction("-10");
    assertEquals(-10, f.getNumerator());
    assertEquals(1, f.getDenominator());
}

5. Testing

  • Next, test simple fractions, both positive and negative.
@Test
@DisplayName("Test parseFraction with a simple fraction")
void parseFractionWithSimpleFraction() {
    Fraction f = FractionCalculator.parseFraction("3/4");
    assertEquals(3, f.getNumerator());
    assertEquals(4, f.getDenominator());
}

@Test
@DisplayName("Test parseFraction with a negative improper fraction")
void parseFractionWithNegativeImproperFraction() {
    Fraction f = FractionCalculator.parseFraction("-7/2");
    assertEquals(-7, f.getNumerator());
    assertEquals(2, f.getDenominator());
}

5. Testing

  • Next, test mixed number fractions, both positive and negative.
@Test
@DisplayName("Test parseFraction with a positive mixed number")
void parseFractionWithPositiveMixedNumber() {
    Fraction f = FractionCalculator.parseFraction("2 1/3"); // 2*3+1 = 7
    assertEquals(7, f.getNumerator());
    assertEquals(3, f.getDenominator());
}

@Test
@DisplayName("Test parseFraction with a negative mixed number")
void parseFractionWithNegativeMixedNumber() {
    Fraction f = FractionCalculator.parseFraction("-3 1/4"); // -3*4-1 = -13
    assertEquals(-13, f.getNumerator());
    assertEquals(4, f.getDenominator());
}

5. Testing

  • Next, test for invalid input.
@Test
@DisplayName("Test parseFraction with text should throw exception")
void parseFractionWithText_ThrowsException() {
    assertThrows(NumberFormatException.class, () -> FractionCalculator.parseFraction("hello"));
}

@Test
@DisplayName("Test parseFraction with invalid mixed number format should throw exception")
void parseFractionWithInvalidMixedNumber_ThrowsException() {
    Exception e = assertThrows(IllegalArgumentException.class, () -> FractionCalculator.parseFraction("1 2 3"));
    assertTrue(e.getMessage().contains("Invalid mixed number format"));
}

@Test
@DisplayName("Test parseFraction with zero denominator should throw exception")
void parseFractionWithZeroDenominator_ThrowsException() {
    Exception e = assertThrows(ArithmeticException.class, () -> FractionCalculator.parseFraction("5/0"));
    assertTrue(e.getMessage().contains("Denominator cannot be zero"));
}

Final Code Mon-Wed

@Test
void parseFractionPositiveWholeNumber() {
    // Arrange
    String input = "5";
    Fraction expected = new Fraction(5, 1);
    // Act
    Fraction actual = FractionCalculator.parseFraction(input);
    // Assert
    assertEquals(expected, actual);
}

@Test
void parseFractionNegativeWholeNumber() {
    Fraction actual =  FractionCalculator.parseFraction("-5");
    assertEquals(-5, actual.getNumerator());
    assertEquals(1, actual.getDenominator());
}

@Test
void parseFractionSimpleFraction() {
    // Arrange
    String input = "5/7";
    Fraction expected = new Fraction(5, 7);
    // Act
    Fraction actual = FractionCalculator.parseFraction(input);
    // Assert
    assertEquals(expected, actual);
}

@Test
void parseFractionNegativeImproperFraction() {
    // Arrange
    String input = "-5/3";
    Fraction expected = new Fraction(-5, 3);
    // Act
    Fraction actual = FractionCalculator.parseFraction(input);
    // Assert
    assertEquals(expected, actual);
}

@Test
void parseFractionMixedFraction() {
    // Arrange
    String input = "2 1/3";
    Fraction expected = new Fraction(7, 3);
    // Act
    Fraction actual = FractionCalculator.parseFraction(input);
    // Assert
    assertEquals(expected, actual);
}

@Test
void parseFractionNegativeMixedFraction() {
    // Arrange
    String input = "-3 1/4";
    Fraction expected = new Fraction(-13, 4);
    // Act
    Fraction actual = FractionCalculator.parseFraction(input);
    // Assert
    assertEquals(expected, actual);
}

@Test
void parseFractionWithText() {
    // One-liner
    assertThrows(IllegalArgumentException.class, () -> FractionCalculator.parseFraction("X"));

    // Arrange
    String input = "X";
    Class expected = IllegalArgumentException.class;
    // Act
    Executable actual = () -> FractionCalculator.parseFraction(input);
    // Assert
    assertThrows(expected, actual);
}

@Test
void parseFractionInvalidSlash() {
    // one-liner
    assertThrows(IllegalArgumentException.class, () -> FractionCalculator.parseFraction("1 2"));

    // Arrange
    String input = "1 2";
    Class expected = IllegalArgumentException.class;
    // Act
    Executable actual = () -> FractionCalculator.parseFraction(input);
    // Assert
    assertThrows(expected, actual);
}

@Test
void parseFractionDivideByZero() {
    // one-liner
    assertThrows(IllegalArgumentException.class, () -> FractionCalculator.parseFraction("1/0"));

    // Arrange
    String input = "1/0";
    Class expected = IllegalArgumentException.class;
    // Act
    Executable actual = () -> FractionCalculator.parseFraction(input);
    // Assert
    assertThrows(expected, actual);
}

@Test
void parseFractionInvalidNumerator() {
    // one-liner
    assertThrows(IllegalArgumentException.class, () -> FractionCalculator.parseFraction("N/1"));

    // Arrange
    String input = "N/1";
    Class expected = IllegalArgumentException.class;
    // Act
    Executable actual = () -> FractionCalculator.parseFraction(input);
    // Assert
    assertThrows(expected, actual);
}


@Test
void parseFractionInvalidDenominator() {
    // one-liner
    assertThrows(IllegalArgumentException.class, () -> FractionCalculator.parseFraction("1/N"));

    // Arrange
    String input = "1/N";
    Class expected = IllegalArgumentException.class;
    // Act
    Executable actual = () -> FractionCalculator.parseFraction(input);
    // Assert
    assertThrows(expected, actual);
}

@Test
void parseFractionNegativeDenominator() {
    // Arrange
    String input = "1/-3";
    Fraction expected = new Fraction(-1, 3);
    // Act
    Fraction actual = FractionCalculator.parseFraction(input);
    // Assert
    assertEquals(expected, actual);
}


@Test
void parseFractionNegativeNumeratorMixedNumber() {
    // Arrange
    String input = "1 -1/3";
    Fraction expected = new Fraction(-4, 3);
    // Act
    Fraction actual =  FractionCalculator.parseFraction(input);
    // Assert
    assertEquals(expected, actual);
}

Final Code Tue-Thr

@Test
void parseFractionWithPositiveWholeNumber() {
    // Arrange
    String input = "5";
    Fraction expected = new Fraction(5,1);
    // Act
    Fraction actual = MarcsFractionCalculator.parseFraction(input);
    // Assert
    assertEquals(expected, actual);
}

@Test
void parseFractionWithNegativeWholeNumber() {
    Fraction f = MarcsFractionCalculator.parseFraction("-1");
    assertEquals(-1, f.getNumerator());
    assertEquals(1, f.getDenominator());
}

@Test
void parseFractionWithProperPositive() {
    // Arrange
    String input = "3/4";
    Fraction expected = new Fraction(3,4);
    // Act
    Fraction actual = MarcsFractionCalculator.parseFraction(input);
    // Assert
    assertEquals(expected, actual);
}

@Test
void parseFractionWithProperNegative() {
    // Arrange
    String input = "-3/4";
    Fraction expected = new Fraction(-3,4);
    // Act
    Fraction actual = MarcsFractionCalculator.parseFraction(input);
    // Assert
    assertEquals(expected, actual);

    // Arrange
    input = "3/-4";
    expected = new Fraction(-3,4);
    // Act
    actual = MarcsFractionCalculator.parseFraction(input);
    // Assert
    assertEquals(expected, actual);
}

@Test
void parseFractionWithPositiveMixedNumber() {
    Fraction f = MarcsFractionCalculator.parseFraction("2 1/3");
    assertEquals(7, f.getNumerator());
    assertEquals(3, f.getDenominator());
}

@Test
void parseFractionWithNegativeMixedNumber() {
    Fraction f = MarcsFractionCalculator.parseFraction("-2 1/3");
    assertEquals(-7, f.getNumerator());
    assertEquals(3, f.getDenominator());
}

@Test
void parseFractionWithNegativeMixedNumber2() {
    assertThrows(IllegalArgumentException.class, () -> MarcsFractionCalculator.parseFraction("2 -1/3"));
}

@Test
void parseFractionWithNegativeMixedNumber3() {
    assertThrows(IllegalArgumentException.class, () -> MarcsFractionCalculator.parseFraction("2 1/-3"));
}

@Test
void parseFractionInvalidNumber() {
    assertThrows(IllegalArgumentException.class, () -> MarcsFractionCalculator.parseFraction("X"), "X is not a valid fraction");
}

@Test
void parseFractionInvalidNumerator() {
    assertThrows(IllegalArgumentException.class, () -> MarcsFractionCalculator.parseFraction("X/1"), "X is not a valid fraction");
}

@Test
void parseFractionInvalidDenominator() {
    assertThrows(IllegalArgumentException.class, () -> MarcsFractionCalculator.parseFraction("1/X"), "X is not a valid fraction");
}

@Test
void parseFractionInvalidSlash() {
    // Arrange
    String input = "1 3!4";
    Class expected = IllegalArgumentException.class;
    // Act
    Executable actual = () -> MarcsFractionCalculator.parseFraction(input);
    // Assert
    assertThrows(expected, actual);
}

@Test
void parseFractionZeroDenominator() {
    assertThrows(IllegalArgumentException.class, () -> MarcsFractionCalculator.parseFraction("1/0"));
}

@Test
void parseFractionNonFractionCharacters() {
    assertThrows(IllegalArgumentException.class, () -> MarcsFractionCalculator.parseFraction("=1/2"));
    assertThrows(IllegalArgumentException.class, () -> MarcsFractionCalculator.parseFraction("1/=2"));
}

4. Coding

  • Implement code to pass the tests. 
  • If the input string contains a space, that indicates the input String is most likely a mixed number fraction.
  • If the input string doesn't contain a space, but contains a forward slash, that indicates the string is most likely a proper or improper fraction.
  • If neither of the first options are true, that indicates the input String is most likely a whole number.
public static Fraction parseFraction(String str) {
    if (str.contains(" ")) { // --- Mixed Number (e.g., "1 2/3" or "-2 1/4") ---
		Fraction result = null;
        return result;
    } else if (str.contains("/")) { // --- Proper/Improper Fraction (e.g., "3/4" or "-5/2") ---
        Fraction result = null;
        return result;
    } else { // --- Whole Number (e.g., "5" or "-10") ---
        return new Fraction();
    }
}

4. Coding

  • The code for the whole number is the easiest. 
  • Use the Integer class static parseInt method to convert the string into a number.
  • If an error occurs, that means the String input is not a valid number.
  • If no error occurs, instantiate a fraction with the whole number as the numerator and 1 as the denominator.
public static Fraction parseFraction(String str) {
    if (str.contains(" ")) { // --- Mixed Number (e.g., "1 2/3" or "-2 1/4") ---
		Fraction result = null;
        return result;
    } else if (str.contains("/")) { // --- Proper/Improper Fraction (e.g., "3/4" or "-5/2") ---
		Fraction result = null;
        return result;
    } else { // --- Whole Number (e.g., "5" or "-10") ---
        int whole = 0;
        try {
            whole = Integer.parseInt(str);
        } catch (NumberFormatException e) {
            // This will be thrown if the whole number is not a number
            throw new NumberFormatException("Invalid whole number: '" + str + "'");
        }
        return new Fraction(whole, 1);
    }
}

4. Coding

  • The code for the proper/improper fraction is the next easiest. 
  • Call the String class split() method passing a forward slash as the input. This will return a String[] array with the numerator at index 0 and denominator at index 1.
  • If the first value in the array is not a valid integer, throw an exception for an invalid numerator.
  • If the second value in the array is not a valid integer, throw an exception for an invalid denominator.
public static Fraction parseFraction(String str) {
    if (str.contains(" ")) { // --- Mixed Number (e.g., "1 2/3" or "-2 1/4") ---
		Fraction result = null;
        return result;
    } else if (str.contains("/")) { // --- Proper/Improper Fraction (e.g., "3/4" or "-5/2") ---
        String[] parts = str.split("/");
        int num = 0;
        int den = 0;
        try {
            num = Integer.parseInt(parts[0]);
        } catch (NumberFormatException e) {
            // This will be thrown if the numerator is not a number
            throw new ArithmeticException("Invalid numerator: '" + parts[0] + "'");
        }

        try {
            den = Integer.parseInt(parts[1]);
        } catch (NumberFormatException e) {
            // This will be thrown if the denominator is not a number
            throw new ArithmeticException("Invalid denominator: '" + parts[1] + "'");
        }

        Fraction result = null;
        try {
            result = new Fraction(num, den);
        } catch (ArithmeticException e) {
            // This will be thrown if the denominator is zero
            throw new ArithmeticException(e.getMessage() + ": '" + str + "'");
        }
        return result;
    } else { // --- Whole Number (e.g., "5" or "-10") ---
        int whole = 0;
        try {
            whole = Integer.parseInt(str);
        } catch (NumberFormatException e) {
            // This will be thrown if the whole number is not a number
            throw new NumberFormatException("Invalid whole number: '" + str + "'");
        }
        return new Fraction(whole, 1);
    }
}

4. Coding

  • Be careful when instantiating the Fraction object, because a denominator of 0 will throw an ArithmeticException. 
  • The new Exception thrown will produce an error message like "Error: Denominator cannot be zero: '1/0'"
public static Fraction parseFraction(String str) {
    if (str.contains(" ")) { // --- Mixed Number (e.g., "1 2/3" or "-2 1/4") ---
		Fraction result = null;
        return result;
    } else if (str.contains("/")) { // --- Proper/Improper Fraction (e.g., "3/4" or "-5/2") ---
        String[] parts = str.split("/");
        int num = 0;
        int den = 0;
        try {
            num = Integer.parseInt(parts[0]);
        } catch (NumberFormatException e) {
            // This will be thrown if the numerator is not a number
            throw new ArithmeticException("Invalid numerator: '" + str + "'");
        }

        try {
            den = Integer.parseInt(parts[1]);
        } catch (NumberFormatException e) {
            // This will be thrown if the denominator is not a number
            throw new ArithmeticException("Invalid denominator: '" + str + "'");
        }

        Fraction result = null;
        try {
            result = new Fraction(num, den);
        } catch (ArithmeticException e) {
            // This will be thrown if the denominator is zero
            throw new ArithmeticException(e.getMessage() + ": '" + str + "'");
        }
        return result;
    } else { // --- Whole Number (e.g., "5" or "-10") ---
        int whole = 0;
        try {
            whole = Integer.parseInt(str);
        } catch (NumberFormatException e) {
            // This will be thrown if the whole number is not a number
            throw new NumberFormatException("Invalid whole number: '" + str + "'");
        }
        return new Fraction(whole, 1);
    }
}

4. Coding

  • Lastly, we will write code to handle mixed numbers.
  • When an optional second argument is passed to the String class split() method, it defines a limit that controls the number of times the pattern is applied and therefore affects the length of the resulting array.
    • If the limit is a positive value, then the pattern will be applied at most limit - 1 times. 2 means apply it once.

public static Fraction parseFraction(String str) {
    if (str.contains(" ")) { // --- Mixed Number (e.g., "1 2/3" or "-2 1/4") ---
        String[] parts = str.split(" ", 2);
        int whole = 0;
        try {
            whole = Integer.parseInt(parts[0]);
        } catch (NumberFormatException e) {
            // This will be thrown if the first part of the parts array is not a number
            // e.g. When entering "- 2 1/4" with a space after the -, the array will be ["-", "2 1/4"]
            throw new NumberFormatException("Invalid mixed number format: '" + str + "' (the correct format is '1 2/3' or '-2 1/4')");
        }

        Fraction result = null;
        return result;
    } else if (str.contains("/")) { // --- Proper/Improper Fraction (e.g., "3/4" or "-5/2") ---
        String[] parts = str.split("/");
        int num = Integer.parseInt(parts[0]);
        int den = Integer.parseInt(parts[1]);
        Fraction result = null;
        try {
            result = new Fraction(num, den);
        } catch (ArithmeticException e) {
            // This will be thrown if the denominator is zero
            throw new ArithmeticException(e.getMessage() + ": '" + str + "'");
        }
        return result;
    } else { // --- Whole Number (e.g., "5" or "-10") ---
        int whole = 0;
        try {
            whole = Integer.parseInt(str);
        } catch (NumberFormatException e) {
            // This will be thrown if the whole number is not a number
            throw new NumberFormatException("Invalid whole number: '" + str + "'");
        }
        return new Fraction(whole, 1);
    }
}

Bug

  • When a user enters something like "1 1/ + 1" or "1/ + 1" when prompted for an equation, an ArrayIndexOutOfBoundsException occurs. 
  • Write code to ensure the length of the array is 2 before calling the parseInt functions.
String[] fractionParts = parts[1].split("/", -1);
if (fractionParts.length != 2 || fractionParts[0].isEmpty() || fractionParts[1].isEmpty()) {
    throw new IllegalArgumentException("Invalid mixed number format: '" + str + "' (the correct format is '1 2/3' or '-1/3'");
}


String[] parts = str.split("/", -1);
if(parts.length != 2 || parts[0].isEmpty() || parts[1].isEmpty()) {
    throw new IllegalArgumentException("Invalid fraction format: '" + str + "'. Fraction format is a/b or whole number");
}

Final Code Mon-Wed

/**
 * Convert a String into a Fraction object. Handles whole numbers, proper and improper fractions, and mixed number fractions
 * @param str is the String to parse
 * @return a Fraction object representing the parsed String
 * @throws IllegalArgumentException if the String is not a valid fraction
 */
public static Fraction parseFraction(String str) {
    Fraction result = null;
    if(str.contains(" ")) { // Mixed number fraction like "1 1/2"
        String[] parts = str.split(" ");
        int whole = 0;
        try {
            whole = Integer.parseInt(parts[0]);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid whole number. '" + parts[0] + "' (the correct format is '1 2/3')");
        }

        String[] fractionParts = parts[1].split("/");

        int num = 0;
        try {
            num = Integer.parseInt(fractionParts[0]);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid numerator. '" + fractionParts[0] + "' (the correct format is '1 2/3')");
        }

        int den = 1;
        if(fractionParts.length > 1) {
            try {
                den = Integer.parseInt(fractionParts[1]);
            } catch (NumberFormatException e) {
                throw new IllegalArgumentException("Invalid denominator. '" + fractionParts[1] + "' (the correct format is '1 2/3')");
            }
        } else {
            throw new IllegalArgumentException("Invalid denominator (the correct format is '1 2/3')");
        }

        if(den == 0) {
            throw new IllegalArgumentException("Invalid denominator. '" + fractionParts[1] + "' (the correct format is '1 2/3')");
        }

        // If the fraction part is negative, flip negativity as needed
        if(num < 0 && den < 0) {
            num *= -1;
            den *= -1;
        } else if (num < 0 && den > 0) {
            num *= -1;
            whole *= -1;
        } else if (num > 0 && den < 0) {
            den *= -1;
            whole *= -1;
        }

        int newNumerator = 0;
        if(whole > 0) {
            newNumerator = whole * den + num;
        } else {
            newNumerator = whole * den - num;
        }
        result = new Fraction(newNumerator, den);

    } else if(str.contains("/")) { // Proper or improper fraction like "1/2" or "3/2"
        String[] parts = str.split("/");

        int num = 0;
        try {
            num = Integer.parseInt(parts[0]);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid numerator. '" + parts[0] + "'");
        }

        int den = 0;
        try {
            den = Integer.parseInt(parts[1]);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid denominator. '" + parts[1] + "'");
        }

        if(den == 0) {
            throw new IllegalArgumentException("Invalid denominator. '" + parts[1] + "'");
        }

        result = new Fraction(num, den);

    } else { // Whole numbers
        int whole = 0;
        try {
            whole = Integer.parseInt(str);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid whole number. '" + str + "'");
        }
        result = new Fraction(whole, 1);
    }
    return result;
}

Final Code Tue-Thr

/**
 * Converts a String into a Fraction object. Handles whole numbers, improper and proper fractions, and mixed numbers
 * @param str The String representing a fraction to be parsed
 * @return a Fraction object representing the parsed String
 * @throws IllegalArgumentException if the String's fraction format is not valid.
 */
public static Fraction parseFraction(String str) {
    Fraction result = null;
    if(str.contains(" ")) { // Handle Mixed numbers (1 2/3 or -2 1/4)
        String[] parts = str.split(" ", 2);
        int whole = 0;
        // Validate the whole number part
        try {
            whole = Integer.parseInt(parts[0]);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid whole number: '" + str + "' (the correct format is '1 2/3' or '-1 2/3'");
        }
        // Validate if the fraction part is missing the forward slash
        if(!parts[1].contains("/")) {
            throw new IllegalArgumentException("Invalid mixed number format: '" + str + "' (the correct format is '1 2/3' or '-1 2/3'");
        }

        String[] fractionParts = parts[1].split("/");
        int num = 0;
        int den = 0;
        // Validate if the numerator or denominator are not numbers
        try {
            num = Integer.parseInt(fractionParts[0]);
        } catch(NumberFormatException e) {
            throw new IllegalArgumentException("Invalid numerator: '" + str + "'");
        }
        try {
            den = Integer.parseInt(fractionParts[1]);
        } catch(NumberFormatException e) {
            throw new IllegalArgumentException("Invalid denominator: '" + str + "'");
        }

        // Convert the results to an improper fraction
        if(whole >= 0) {
            // Invalid example "2 -1/3 or 2 1/-3)
            if(num < 0 || den < 0) {
                throw new IllegalArgumentException("Invalid mixed number format: '" + str + "' (the correct format is '1 2/3' or '-1 2/3'");
            }
            num = whole * den + num;
        } else if(whole < 0) {
            num = whole * den - num;
        }

        try {
            result = new Fraction(num, den);
        } catch(ArithmeticException e) {
            // Will have an error if denominator is 0
            throw new IllegalArgumentException(e.getMessage() + ": '" + str + "'");
        }


    } else if(str.contains("/")) { // Handle proper and improper fraction (3/4 or -5/2)
        String[] parts = str.split("/");
        int num = 0;
        int den = 0;
        try {
            num = Integer.parseInt(parts[0]);
        } catch(NumberFormatException e) {
            throw new IllegalArgumentException("Invalid numerator: '" + str + "'");
        }
        try {
            den = Integer.parseInt(parts[1]);
        } catch(NumberFormatException e) {
            throw new IllegalArgumentException("Invalid denominator: '" + str + "'");
        }

        try {
            result = new Fraction(num, den);
        } catch(ArithmeticException e) {
            // Will have an error if denominator is 0
            throw new IllegalArgumentException(e.getMessage() + ": '" + str + "'");
        }

    } else { // Handle whole numbers (5 or -1)
        int whole = 0;
        try {
            whole = Integer.parseInt(str);
        } catch(NumberFormatException e) {
            throw new IllegalArgumentException("Invalid whole number: '" + str + "'");
        }
        result = new Fraction(whole, 1);
    }
    return result;
}