CSC216 Lab 02: Unit Test Student
The Student
class represents an individual student record. The Student
class is a “plain old java object” (POJO) consisting mostly of getters and setters. The setters do have some complexity and you need to test Student
to ensure that your setters work correctly and that a Student
object is constructed correctly.
Create a StudentTest
Class
To create the StudentTest
class, do the following:
- Right click on
Student
and select New > JUnit Test Case. - Change the Source folder to
/PackScheduler/test
. Click Next. - Select the
Student
constructors, all public setters,hashCode()
,equals()
, andtoString()
. Click Finish. - A new class
StudentTest
will be created in theedu.ncsu.csc216.pack_scheduler.user
package in thetest/
source folder.StudentTest
will contain empty test methods for theStudent
constructors, all public setters,hashCode()
,equals()
, andtoString()
methods. Note that you will need to call the getters to see that the internal state ofStudent
has changed correctly. You’ll get coverage of the getters through other test cases.
If the StudentTest
file isn’t in the right package or source folder, move it to the appropriate location. If it’s not in the right place, your tests may not be executed on Jenkins!
Testing Strategies
When testing POJOs, you want to ensure that we can construct them correctly and that changes to internal state through setters are handled correctly. Additionally, you want to ensure that two objects are equal on the correct attributes and that the toString()
method returns the correct comma-separated list of fields that are expected for writing to an output file. Student
should meet the requirements defined in UC1.
You should use our standard strategies of test the requirements, equivalence class partitioning, boundary value analysis, and basis set testing the method control flow to develop test cases. The details below about each test method will help guide you as you develop your tests.
Your goal to to achieve at least 80% statement coverage by writing high quality tests that exercise most of the paths in your Student
class. There is 1 point of extra credit for exceeding 90% statement coverage, an additional point of extra credit for obtaining 100% statement coverage, and a third point of extra credit for achieving 100% condition coverage!
Make sure you run your tests frequently! If you find a bug in your solution, fix it!
Reminder: Resources on Writing Tests
There are several resources provided for writing tests, including sample test code:
- Dr. Heckman’s Testing Lecture Notes
- Seasons Test Example
- Dr. Heckman’s Coverage and Static Analysis Lecture Notes
- Dr. Perry’s Unit Testing Notes
- Dr. Perry’s Code and Testing Analysis
- Guided Project 1 CourseTest.java
Note that the tests for Student
are similar to the tests for Course
in Guided Project 1. You may use those tests as examples and reference for Lab 2!
Running Tests for Coverage
Before you start writing tests, make sure that you can run our tests instrumented for coverage. Right click on the test/
source folder and select Coverage As > JUnit Test.
When evaluating coverage locally, focus only on the coverage of Student
, StudentRecordIO
, and StudentDirectory
. The coverage of the test classes themselves and any user interface classes are not considered when evaluating the coverage of your tests. Jenkins is set up to exclude test classes and user interface classes, but your local EclEmma is not. Don’t panic if the numbers seem low.
One last thing to check is that EclEmma is reporting the right metrics. Click the down arrow (View Menu) option in the Coverage view. Select Line Counters for seeing statement/line coverage. If you want to see condition coverage (for extra credit!), use the Branch Counters option.
Testing Student
Constructors
Student
has two constructors: one that has parameters for all fields the other that has parameters for all fields but max credits. The bullets below describe what each method should do and the sub-bullets describe what you should test!
Student(String firstName, String lastName, String id, String email, String password, int maxCredits)
: calls the setters for each of the fields. If the setters throw anIllegalArgumentException
, the exception should pass through the constructor to the client. That means the constructor does NOT catch the exception. Just call the setters!- Test that you can construct a valid
Student
. This follows the strategy of testing requirements. Make sure that you check all the fields are correct by using the setters.
- Test that you can construct a valid
assertEquals("first", s1.getFirstName());
* Start testing invalid input for each parameter - one at a time. The standard structure of an invalid test is the following:
Student s = null; //Initialize a student reference to null
try {
s = new Student(null, "last", "id", "email@ncsu.edu", "hashedpassword");
//Note that for testing purposes, the password doesn't need to be hashedpassword
fail(); //If we reach this point a Student was constructed when it shouldn't have been!
} catch (IllegalArgumentException e) {
//We should get here if the expected IllegalArgumentException is thrown, but that's not enough
//for the test. We also need to make sure that the reference s is still null!
assertNull(s);
}
Student(String firstName, String lastName, String id, String email, String password)
: calls the other constructor with the default max credits value of 18.- Test in a similar manner to the other
Student
constructor. The difference is that after calling this constructor, themaxCredits
are always 18.
- Test in a similar manner to the other
Testing Student
Getters
All getters for the Student
fields are straightforward; they return the field. Since the getters simply return the field, there is no need to write a distince test method for the getters. You’ve already tested them by testing the Student
constructors!
Testing Student
Setters
The setters are more complex because they check to make sure that the Student
fields are not invalid as defined in [UC1]. If a value is invalid, an IllegalArgumentException
should thrown. You may be wondering why you’re testing the setters after you’ve tested them through the Student
constructors. The reason is because while you may have achieved coverage through the constructors, you haven’t tested that the setters work correctly AFTER an object is constructed. You evaluate that in the setter test methods.
The strategy for testing the setters is the same for all the setters:
#. Test that the setter works correctly by changing the valid value and making sure the change is there through calling a getter.
#. Test that the setter throws an IllegalArgumentException
when passed an invalid value using a structure similar to the one below:
//Construct a valid Student
Student s = new Student("first", "last", "id", "email@ncsu.edu", "hashedpassword");
try {
s.setFirstName(null);
fail(); //We don't want to reach this point - an exception should be thrown!
} catch (IllegalArgumentException e) {
//We've caught the exception, now we need to make sure that the field didn't change
assertEquals("first", s.getFirstName());
}
setFirstName(String firstName)
: throws anIllegalArgumentException
if the parameter is null or an empty string.setLastName(String lastName)
: throws anIllegalArgumentException
if the parameter is null or an empty string.setId(String id)
: throws anIllegalArgumentException
if the parameter is null or an empty string.setEmail(String email)
: throws anIllegalArgumentException
if:- the parameter is null or an empty string
- email doesn’t contain an ‘@’ character
- email doesn’t contain a ‘.’ character
- the index of the last ‘.’ character in the email string is earlier than the index of the first ‘@’ character (for example, first.last@address would be invalid)
setPassword(String password)
: throws anIllegalArgumentException
if the parameter is null or an empty string.setMaxCredits(int maxCredits)
: throws anIllegalArgumentException
if the parameter is less than 3 or greater than 18.
Note that in the design, setId()
is listed as a private
method. This is because a Student
’s id shouldn’t change after creation - it’s the unique identifier in the StudentDirectory
. Don’t forget to make setId()
private to meet the design!
Testing Student
’s hashCode()
and equals()
The Student
class has an equals()
and hashCode()
method generated by Eclipse. Assuming that you selected the correct fields when generating the methods, you can be pretty confident that equals()
and hashCode()
work correctly (lots of people use Eclipse and work on its code). However, you still need to test equals()
and hashCode()
to ensure greater than 80% statement coverage for full credit (or extra credit)!
The strategies for testing equals()
and hashCode()
are similar:
- Create three or more objects. Two of the objects have the EXACT same state and the rest have at least one piece of different state.
- For
equals()
, test that the two objects with the same state are equal and that two objects with different state are not equal. - For
hashCode()
, compare the generated hashcodes.hashCode()
methods guarantee that if two objects are equal, they have the same hashcode.
Use the [Guided Project 1 CourseTest.testEquals() and CourseTest.testHashCode()]https://pages.github.ncsu.edu/engr-csc216/guided-projects/gp1/files/CourseTest.java) methods as guides for writing your tests for equals()
and hashCode()
.
Note that achieving 100% statement or condition coverage for the equals()
and hashCode()
methods, as generated, is impossible with the null
checks on Student
fields during construction. For example, the email
field should never be null
. You may modify the equals()
and hashCode()
methods to remove null
checks to achieve 100% statement and condition coverage for extra credit!
Testing Student
’s toString()
The last item to test is Student.toString()
. You want to make sure that the generated String
is appropriate for output to the Student records file as per the requirements.
The steps for testing toString()
are these:
- Construct a valid
Student
. - Use the parameters to
Student
to create aString
containing the expected comma-separated results. - Compare the expected results
String
to theString
generated byStudent
.
Run Your Tests
Run your tests instrumented for coverage. Make sure that your tests execute at least 80% of the statements in Student
. Remember there is extra credit for more coverage!
Javadoc your Code
Make sure that you Javadoc the StudentTest
class, state (if any, including constants), and methods.
Run CheckStyle to ensure that your Javadoc has all elements.
We do expect that all test classes are commented!
Push to GitHub
Push your PackScheduler
project to GitHub
- Add the unstaged changes to the index.
- Commit and push changes. Remember to use a meaningful commit message describing how you have changed the code.
Reminder: Staging and Pushing to GitHub
GitHub Resources:
Check Jenkins
At this point, your project should build on Jenkins with a Yellow ball. That is because you are not yet meeting coverage for StudentRecordIO
and StudentDirectory
. Make sure there are no regressions of functionality from the teaching staff tests. If there are, use the feedback to go back and fix your code (and maybe your tests).
Reminder: Interpreting Jenkins
Check the following items on Jenkins for your last build and use the results to estimate your grade: