HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Calculate loan rate based on payment and duration in months

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
paymentratecalculatebaseddurationandloanmonths

Problem

So I have the following code which works well-ish. Looking for ideas on how to improve it.

```
public void testItAll() {
System.err.println(determineRate(226.34, 25.00, 12));
System.err.println(determineRate(5800.00, 29.00, 31));
System.err.println(determineRate(4000.00, 25.00, 460));
System.err.println(determineRate(4000.00, 111.00, 173));
System.err.println(determineRate(15000.00, 270.00, 60));
System.err.println(determineRate(7000.00, 154.00, 60));
System.err.println(determineRate(27002.51, 315.00, 85));
System.err.println(determineRate(17118.33, 270.14, 73));
System.err.println(determineRate(5170.71, 143.00, 48));
System.err.println(determineRate(45297.34, 400.00, 135));
System.err.println(determineRate(6000.00, 113.00, 60));
System.err.println(determineRate(23058.98, 219.59, 145));
System.err.println(determineRate(47475.76, 390.44, 181));
System.err.println(determineRate(69691.48, 554.00, 180));
System.err.println(determineRate(39310.00, 725.00, 60));
System.err.println(determineRate(45000.00, 316.00, 180));
System.err.println(determineRate(14071.01, 45.00, 220));
System.err.println(determineRate(16875.00, 198.00, 120));
System.err.println(determineRate(66080.04, 295.00, 12));
System.err.println(determineRate(120000.00, 664.00, 120));
System.err.println(determineRate(58000.00, 387.19, 120));
System.err.println(determineRate(351213.00, 1993.11, 25));
System.err.println(determineRate(139500.00, 1000.00, 300));
System.err.println(determineRate(2400.00, 1875.00, 180));
System.err.println(determineRate(193155.00, 2002.00, 120));
System.err.println(determineRate(40800.00, 507.46, 36));
System.err.println(determineRate(198375.00, 1530.00, 240));
System.err.println(determineRate(22700.00, 450.00, 60));
System.err.println(determineRate(20000.00, 622.85, 999));
System.err.println(determineRate(629999.32, 25.00, 300));
System.err.println(determineR

Solution

Unit testing

public void testItAll() { ... }


Kind of a good start...

System.err.println(determineRate(226.34, 25.00, 12));


Eh, what? One, why are you printing out on the standard error stream? Two, how do you complete the so-called 'testing'? By manually checking the output matches?

This is where unit testing comes in! A proper unit test should let a developer:

  • Specify some test input.



  • Process the input to some output to be recorded.



  • Assert that the recorded output matches an expected result.



There are a handful of good Java unit testing frameworks, and I will use TestNG as an example below.

You start off by having annotating a test method so that the framework recognizes that it needs to run that test (this is dependent on your unit testing framework), and an underlying method that calls your method to test:

@Test
public void testOne() {
    doTest(226.34, 25.00, 12, 55.50992);
}

public void testLoanRate(double loanAmount, double payment, int termInMonths,
                            double expectedRate) {
    // ...
}


As you can see, its method signature is very similar to your method, because you need the desired inputs. The extra parameter, expectedRate, lets you assert that the calculation is correct. The body of the method can be something like:

public void testLoanRate(double loanAmount, double payment, int termInMonths,
                            double expectedRate) {
    // assuming determineRate() can be made static
    double result = determineRate(loanAmount, payment, termInMonths);
    // following static method is provided by TestNG
    // the third argument controls the absolute tolerable difference,
    // an arbitrary small amount is chosen as an example
    Assert.assertEquals(result, expectedRate, 0.0001d);
    // optionally print inputs and output when successful
    System.out.printf("Amount: %f, Payment: %f, Term in months: %d, Result: %f%n",
                        loanAmount, payment, termInMonths, result);
}


Testing a number of inputs and expected results is relatively easy with TestNG's parameterized testing feature. You need a @DataProvider, and link it up with the @Test method on the testLoanRate() method now:

@DataProvider(name = "test-cases")
public Iterator getTestCases() {
    return Arrays.asList(
        new Object[] { 226.34, 25.00, 12, 55.50992 },
        new Object[] { 5800.00, 29.00, 31, 0 }, // ???
        new Object[] { 4000.00, 25.00, 460, 6.97672 },
        /* ... */
    ).iterator();
}

@Test(dataProvider = "test-cases")
public void testLoanRate(double loanAmount, double payment, int termInMonths,
                            double expectedRate) {
    double result = determineRate(loanAmount, payment, termInMonths);
    Assert.assertEquals(result, expectedRate, 0.0001d);
    System.out.printf("Amount: %f, Payment: %f, Term in months: %d, Result: %f%n",
                        loanAmount, payment, termInMonths, result);
}


When you run this unit test, TestNG will iterate through all the test cases to assert the results. This eliminates the need to manually check every calculation.

Magic numbers and math

private double calculatePayment(double loanAmount, double rate, double termInMonths) {
    return (loanAmount * ((rate/12.0)*(Math.pow((1 + (rate/12.0)), termInMonths)))) 
                / (Math.pow((1 + (rate/12.0)), termInMonths) - 1);
}


Using just one example, 12.0 seems to be used a lot here. You should consider putting your magic numbers into an easily identifiable constant so that they can be reused in a consistent and readable manner.

Math.max(payment, calculatedPayment) - Math.min(payment, calculatedPayment) > 0.05


Another way of expressing this can be just:

Math.abs(payment - calculatedPayment) > 0.05

Code Snippets

public void testItAll() { ... }
System.err.println(determineRate(226.34, 25.00, 12));
@Test
public void testOne() {
    doTest(226.34, 25.00, 12, 55.50992);
}

public void testLoanRate(double loanAmount, double payment, int termInMonths,
                            double expectedRate) {
    // ...
}
public void testLoanRate(double loanAmount, double payment, int termInMonths,
                            double expectedRate) {
    // assuming determineRate() can be made static
    double result = determineRate(loanAmount, payment, termInMonths);
    // following static method is provided by TestNG
    // the third argument controls the absolute tolerable difference,
    // an arbitrary small amount is chosen as an example
    Assert.assertEquals(result, expectedRate, 0.0001d);
    // optionally print inputs and output when successful
    System.out.printf("Amount: %f, Payment: %f, Term in months: %d, Result: %f%n",
                        loanAmount, payment, termInMonths, result);
}
@DataProvider(name = "test-cases")
public Iterator<Object[]> getTestCases() {
    return Arrays.asList(
        new Object[] { 226.34, 25.00, 12, 55.50992 },
        new Object[] { 5800.00, 29.00, 31, 0 }, // ???
        new Object[] { 4000.00, 25.00, 460, 6.97672 },
        /* ... */
    ).iterator();
}

@Test(dataProvider = "test-cases")
public void testLoanRate(double loanAmount, double payment, int termInMonths,
                            double expectedRate) {
    double result = determineRate(loanAmount, payment, termInMonths);
    Assert.assertEquals(result, expectedRate, 0.0001d);
    System.out.printf("Amount: %f, Payment: %f, Term in months: %d, Result: %f%n",
                        loanAmount, payment, termInMonths, result);
}

Context

StackExchange Code Review Q#132882, answer score: 3

Revisions (0)

No revisions yet.