Lab 4: Car Class and Test¶
In this lab, you will implement a Car
class and corresponding CarTest
.
You will learn about non-static attributes and methods.
And you will gain more experience with JUnit and code coverage.
Learning Objectives¶
After completing this lab, you should be able to:
- Interpret a UML class diagram and stub out the corresponding code.
- Write JUnit tests that create objects and assert non-static methods.
Step 1. Getting Started¶
Do the following steps in VS Code:
- Create a
lab04
folder under yoursrc/labs
folder. - Add two new files:
Car.java
andCarTest.java
. - Write the
package
andpublic class
statements. - Write a Javadoc comment with your name and date.
When finished, your Car.java
and CarTest.java
should look like this:
Step 2. UML Class Diagram¶
Based on the diagram below, declare each attribute and write a stub for each method.
Do not implement any methods yet, but make the code compile.
For example, if a method returns a String
, write return "";
as a stub (placeholder).
Show your code to the instructor or TA before proceeding to the next step.
classDiagram
class Car {
- make: String
- speed: double
- year: int
+ Car(make: String, year: int)
+ getMake() String
+ getSpeed() double
+ getYear() int
+ accelerate() String
+ brake()
+ equals(obj: Object) boolean
+ toString() String
}
Note
The brake()
method is void
(does not return a value).
The word void
is usually omitted in UML class diagrams.
Step 3. Constructor, Getters¶
The purpose of a constructor is to initialize the attributes of this
object.
Notice the Car
class has three attributes: make
, speed
, and year
.
The constructor has two parameters: make
and year
.
Use the parameters to initialize this
object's make
and year
.
Initialize this
object's speed
to zero.
Then write the Javadoc comment for the constructor.
Note
When writing documentation for constructors, do not use the word create.
Constructors do not "create" objects; the new
operator creates an object.
Once an object has been created, the constructor is called to initialize the object.
The purpose of a getter method is to return the current value of an attribute.
Notice that the attributes are private
, meaning they can be accessed only in the Car
class.
Other classes, like CarTest
, will need to use the public
getters to access the attributes.
Implement each getter by returning the corresponding attribute.
Javadoc comments are not required for most getter methods. However, a Javadoc comment is required if a getter does more than simply return an attribute.
Step 4. Test the Constructor¶
Before implementing the other methods in Car.java
, let's make sure the constructor and getter methods work correctly.
Open CarTest.java
and add the following imports to the top:
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
Then add the following method:
@Test
public void testConstructor() {
}
Note
The @Test
annotation tells JUnit that the following method is a test method.
Test classes sometimes have other methods, such as helper methods, that are not run as tests.
Add the following code to testConstructor()
:
- Create two
Car
objects.- Each car should have a different make and year.
- Write three
assertEquals
statements for each car:getMake()
should return the make you chose.getSpeed()
should return zero (at this point).getYear()
should return the year you chose.
Run the test by clicking the green play () button next to the method. If the test fails, make whatever changes are necessary in your code. After the test passes, click the Watch button (bottom left) to show code coverage.
In Car.java
, the constructor and getters should be highlighted in green, meaning those lines were executed during the test.
The other methods should be highlighted in red, meaning they were not executed during the test.
Click the Watch button again to hide code coverage.
Step 5. Accelerate and Brake¶
The accelerate()
method increases the current speed of the car by 5 miles per hour.
After increasing the speed, accelerate()
returns a string describing the change.
Use the format "15.0 + 5.0 = 20.0"
where 15.0
is the old speed and 20.0
is the new speed.
Note
The String.format()
method is useful for formatting double
values.
For example, you can format the speed values to one decimal place as follows:
return String.format("%.1f + 5.0 = %.1f", speed - 5, speed);
Your code must not allow the speed to increase above 150 miles per hour.
If the speed is already 150, and accelerate()
is called, the speed should remain 150.
In that case, the method should return the string "150.0 + 0.0 = 150.0"
.
The brake()
method should decrease the current speed by 5 miles per hour.
Your code must not allow the speed to decrease below 0 miles per hour.
Write Javadoc comments for accelerate()
and brake()
before moving on to the next step.
Step 6. Test Driving the Car¶
Add the following method to CarTest
:
@Test
public void testAcceleration() {
}
This test should do the following:
- Create a
Car
object. - Call the
accelerate()
method.- Assert that the method returned
"0.0 + 5.0 = 5.0"
. - Assert that the
Car
object's speed is now 5.
- Assert that the method returned
- Call the
brake()
method.- Assert that the
Car
object's speed is now 0.
- Assert that the
Run the test and make sure the test passes.
Click the Watch button to show code coverage.
Some lines of Car.java
are green, some are yellow, and some are red.
You still need to test accelerating when speed is 150 and braking when speed is 0.
Add the following method to CarTest
:
@Test
public void testSpeedLimits() {
}
This test should do the following:
- Create a
Car
object. - Call the
brake()
method.- Assert that the
Car
object's speed is 0.
- Assert that the
- Call the
accelerate()
method 30 times, and then:- Assert that the method returned
"145.0 + 5.0 = 150.0"
. - Assert that the
Car
object's speed is now 150.
- Assert that the method returned
- Call the
accelerate()
method one more time.- Assert that the method returned
"150.0 + 0.0 = 150.0"
. - Assert that the
Car
object's speed is still 150.
- Assert that the method returned
Step 7. The equals() method¶
The purpose of the equals()
method is to determine if two objects are equivalent.
Replace your equals()
method with the following solution:
@Override
public boolean equals(Object obj) {
if (obj instanceof Car) {
Car car = (Car) obj;
return car.make.equals(this.make) && car.year == this.year;
}
return false;
}
The above code:
- Overrides the default implementation of
equals()
.- By default,
equals()
returns true ifthis
andobj
are the same object.
- By default,
- Checks if
obj
, which can be any type of object, is actually aCar
object.- If not, then
equals()
returns false at the end.
- If not, then
- Declares a
Car
variable to reference the same object asobj
. - Determines whether
car
has the same make and year asthis
object.
Step 8: The toString() method¶
The purpose of the toString()
method is to return a string that represents the object.
Replace your toString()
method with the provided solution:
@Override
public String toString() {
return String.format("A %d %s that is going %.1f mph",
year, make, speed);
}
The above code:
- Overrides the default implementation of
toString()
.- By default,
toString()
returns the class name and the memory location of the object. For example,"labs.lab04.Car@5b6f7412"
.
- By default,
- Uses the
String.format()
method to build a string based on the object's attributes.
Note
Javadoc comments are not required for @Override
methods, because the
documentation is already defined by the default implementation.
In VS Code, you can hover the mouse over a method's name to see the documentation.
Step 9. Final Test and Submit¶
Add the following test methods to CarTest
:
@Test
public void testEquals() {
Car car1 = new Car("Nissan", 2003);
Car car2 = new Car("Nissan", 2003);
Car car3 = new Car("Honda", 2003);
Car car4 = new Car("Nissan", 2024);
assertTrue(car1.equals(car2));
assertFalse(car1.equals(car3));
assertFalse(car1.equals(car4));
assertFalse(car1.equals("car5"));
}
@Test
public void testString() {
Car car = new Car("Nissan", 2003);
assertEquals("A 2003 Nissan that is going 0.0 mph", car.toString());
}
Notice that testEquals()
compares car1
with four different objects:
car2
has the same make and year.car3
has a different make.car4
has a different year."car5"
(in quotes) is a string.
Calling the equals()
method four times is necessary to cover all the branches.
In contrast, the toString()
method does not have any branches.
So only one method call is necessary to get full coverage of toString()
.
As a final step, click the green play () button next to public class CarTest
.
All five tests should pass, and you should have 100% code coverage of the Car
class.
Submit both Car.java
and CarTest.java
to Gradescope.
Points will be allocated as follows:
Criterion | Points | Details |
---|---|---|
Compile | 0 pts | Success Required |
CompileOfficialTests | 0 pts | Success Required |
Style | 0 pts | Success Required |
SelfTests | 2 pts | Partial Credit Possible |
Coverage | 3 pts | Partial Credit Possible |
OfficialTests | 5 pts | Partial Credit Possible |