The primary goal of today's lab is practice unit testing, with JUnit. As time permits, we will also practice using Exceptions (including Try and Catch), and learn a bit more about Javadoc (eg running it from within an IDE).

0. Get started with Junit. We will use version 4. Instead of creating a main method in one or more of the class files, for testing, we create a file called ClassnameTest.java. (See PersonTest.java.) It begins with importing Junit.

import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test;

When you first go to build the file, IntelliJ may complain that junit is not found, such as with the red lightbulb icon: see image.

This is usually a path error in finding the junit library. When you click on the caret, it should offer a choice such as the figure below. Select the option sequence that adds JUnit4 to the classpath. Sometimes this process can be quirky. If necessary, close your open projects, quit IntelliJ, and try again. It is also possible to run junit from the command line and from VSCode, but we won't talk about those options today. see image.

Files containing versions of Person, Book, and PersonTest are provided. However, these "might" have a few bugs! Your job is to get everything working! Sometimes, the best solution is to fix a test that was written in a way that does not match the Class being tested. Sometimes, you may decide that the Class itself has something wrong that needs to be corrected. At the end of the day, the Class must agree with the Test for that Class, or unit testing has failed. When the tests are run, you will get a count of passes and failures, as well as detailed information about each test that failed.

Notice the notations used to create JUnit tests. There is usually a void "@Before" method that sets up instances of the Class being tested. Then there is a series of void @Test Classes, with calls such as:

assertEquals("John", john.getFirstName());

which "passes" if the two arguments are equal and fails otherwise. There are many other types of tests you can use. You can learn more here: https://www.vogella.com/tutorials/JUnit/article.html#usingjuni4 and here: https://www.youtube.com/watch?v=Bld3644bIAo

1. Practice with Exceptions. Today you will work on extending your Fraction.java class from last week's lab. Your initial version had an integer numerator and an integer denominator; it simply assumed that no one would try to create an instance of class Fraction with a zero denominator. Further, lets require the denominator to be a positive integer, since either both are negative (= positive/positive) or the numerator can represent a any negative value.) You also wrote a public Boolean method isEqualTo that return true if the fractions were equal and false otherwise. (HINT: the easiest way to determine this is by cross-multiplication.)

The first extension is to modify the Constructor for Fraction such that it throws IllegalArgumentException if the caller tries to create an instance with a denominator that is 0 or negative. You should have a getter method for the numerator, a getter method for the denominator, a toDouble method that returns the value of the fraction as a double ("scientific value"), a toString method that returns a String representation of the fraction as a whole number, proper fraction, or mixed numeral, as appropriate. (HINTs: you will need to use Java's remainder operator to represent improper fractions; the String.format method will also be helpful.)

The next extension involves writing a method to return a new Fraction instance that is the reciprocal of the given fraction. It should throw IllegalStateException if the numerator is 0.

Write a method that creates several fractions, including trying to create one with a 0 denominator. Use try and catch around the code that creates these examples. Write a similar method but without the try and catch protection; it should complain accordingly. Now create the beginnings of a JUnit test class, FractionTest.java. To check that the exception occurs when it should, you can use @test(IllegalArgumentException). The test should pass if and only if the denominator is non-positive. BookTest.java illustrates how to test for expected exceptions with JUnit4.

2. Make a copy of your Person.java file in a separate project folder (so as to avoid accidentally breaking your working code) and replace the traditional commentary by Javadoc-formatted documentation. (Not all comments need to be replaced, and not all tags need to be used, but please use:

  • Top of file comment in Javadoc format /** */
  • @param for each parameter to each method
  • @return for value returned from each method
  • @throws for each method that throws an exception
  • @author with your own name The @author tag is controversial, but helps our TAs in grading

It would be best to learn to create Javadoc html output using the Tools -> Generate Javadoc menu item within IntelliJ. We previously looked at running it from the command line. (It is also possible to generate Javadocs from within VSCode; a handout is planned soon.)

Book.java


/**
* A book is something that has a title, price, and author.
*
* @author Me
*/
public class Book {

/**
* Book title.
*/
private String title;

/**
* Book cost.
*/
private double price;

/**
* Book author.
*/
private Person author;

/**
* Create a new book.
*
* @param title Book title
* @param price Book price
* @param author Book author
*/
public Book(String title, double price, Person author) {
this.title = title;
this.price = price;
this.author = author;
}

/**
* Create a new book.
*
* @param title Book title
* @param author Book author
*/
public Book(String title, Person author) {
this.title = title;
this.author = author;
}

/**
* Return the author of the book.
*
* @return Author
*/
public Person getAuthor() {
return this.author;
}

/**
* Return the price of the book.
*
* @return Price
*/
public double getPrice() {
return this.price;
}

/**
* Initialize the price of the book.
*
* @param price Price
*/
public void setPrice(double price) {
this.price = price;
}

/**
* Create a new book with a discounted price.
*
* @param discount Discount rate.
* @return Discounted book
* @throws IllegalArgumentException When the discount rate is negative
*/
public Book discountBook(double discount) throws IllegalArgumentException {
if (discount < 0) {
throw new IllegalArgumentException("Discount must not be negative!");
}

double newPrice = this.price * (1.0 - discount);
return new Book(this.title, newPrice, this.author);
}

/**
* Create a new book that holds the details of this book but its price is
* the price of another given book.
*
* @param anotherBook Other book to get price from
* @return Book
*/
public Book discountBook(Book anotherBook) {
return new Book(this.title, anotherBook.price, this.author);
}

/**
* Return a string representation of a book.
*
* @return String
*/
@Override
public String toString() {
return title + " " + author.toString();
}

/**
* Test book by observing output.
*
* @param args Unused arguments
*/
public static void main(String[] args) {
Person p = new Person(1989, "Tom", "Cheng");
Book book = new Book("Java Programming", 20.50, p);

Person author = book.getAuthor();
System.out.println(author.getName());

System.out.println("Before discount: " + Double.toString(book.getPrice()));
Book discountedBook = book.discountBook(0.15);
System.out.println("After discount: " + Double.toString(discountedBook.getPrice()));

Book book1 = new Book("C++ Programming", 40.0, p);
System.out.println("Before discount: " + Double.toString(book1.getPrice()));
Book discountedBook1 = book.discountBook(discountedBook);
//Book discountedBook1 = book.discountBook(book);
System.out.println("After discount: " + Double.toString(discountedBook1.getPrice()));

System.out.println(book1);

System.out.println("Before discount: " + Double.toString(book.getPrice()));

try {
Book discountedBook2 = book.discountBook(-0.15);
System.out.println("After discount: " + Double.toString(discountedBook2.getPrice()));
} catch (IllegalArgumentException e) {
System.out.println(e);
}
}
}

Person.java


/**
* A person is someone who has a birth date and a name.
*
* @author Hp Envy
*/
public class Person {

/**
* Year of birth
*/
private int yob;

/**
* Given name
*/
private String firstName;

/**
* Surname
*/
private String lastName;

/**
* Original first name before update
*/
private String origFirstName;

/**
* Initialize a new person.
*
* @param yob Year of birth
* @param firstName Given name
* @param lastName Surname
*/
public Person(int yob, String firstName, String lastName) {
this.yob = yob;
this.firstName = firstName;
this.lastName = lastName;
}

/**
* Initialize a person.
*
* @param firstName Given name
* @param lastName surname
* @param yob Year of birth
*/
public Person(String firstName, String lastName, int yob) {
this.yob = yob;
this.firstName = firstName;
this.lastName = lastName;
}

/**
* Return the full name of the person
*
* @return Full name
*/
public String getName() {
return firstName + " " + lastName;
}

/**
* Initialize the first name of the person.
*
* @param newFirstName First name
*/
public void setFirstName(String newFirstName) {
origFirstName = this.firstName;
this.firstName = newFirstName;
}

/**
* Access to the original first name.
*
* @return Original first name before update
*/
public String getOrigFirstName() {
return origFirstName;
}

/**
* Access to the first name property.
*
* @return First name
*/
public String getFirstName() {
return firstName;
}

/**
* Access to the year of birth property.
*
* @return Year of birth
*/
public int getYearOfBirth() {
return yob;
}

/**
* Access to the last name property.
*
* @return Last name
*/
public String getLastName() {
return lastName;
}

/**
* Return a string representation of a person.
*
* @return Full name
*/
@Override
public String toString() {
return getName();
}

/**
* Test the person class through observing the output.
*
* @param args Unused arguments
*/
public static void main(String[] args) {
int a = 1;
int b = 5;

// create an object/instance out of class
// instantiate a class
Person person1 = new Person(1989, "Tom", "Cheng");
Person person2 = new Person(2000, "Tom1", "Cheng1");

/*
System.out.println(person1.yob);
System.out.println(person2.yob);

person1.yob = 1920;
System.out.println(person1.yob);
*/
System.out.println(person1.getName());

person1.setFirstName("Jack");

System.out.println(person1.getName());
System.out.println(person1.getOrigFirstName());
}
}
Academic Honesty!
It is not our intention to break the school's academic policy. Posted solutions are meant to be used as a reference and should not be submitted as is. We are not held liable for any misuse of the solutions. Please see the frequently asked questions page for further questions and inquiries.
Kindly complete the form. Please provide a valid email address and we will get back to you within 24 hours. Payment is through PayPal, Buy me a Coffee or Cryptocurrency. We are a nonprofit organization however we need funds to keep this organization operating and to be able to complete our research and development projects.