You have already used the keyword public for JUnit classes and methods without an understanding of the full meaning of the keyword, other than that JUnit requires that test classes and methods be declared public. You also learned that instance variables can be declared private so that objects of other classes cannot access them. The public and private keywords are known as access modifiers. You use access modifiers to control access to Java elements, including fields, methods, and classes. The access modifiers that are appropriate for a class are different than those that are appropriate for methods and fields. By declaring a class as public, you allow classes in other packages to be able to import and refer directly to the class. The JUnit framework classes are located in various packages whose name starts with junit. In order for these JUnit classes to be able to instantiate your test classes, you must declare them as public. Neither the CourseSession nor the Student class you built specified an access modifier. In the absence of an access modifier, a class has an access level of package, also known as default access. You can refer to a class with package-level access from other classes within the same package; however, classes in a different package cannot refer to the class. For "safer" programming, the preferred tactic is to start at the most restrictive level and then open up access as needed. Exposing your classes too much can mean that clients can become unnecessarily dependent on the details of how you've put the system together. If you change the details, the clients could break. Also, you open your code up to being corrupted by providing too much access.
The CourseSession and Student classes currently have package-level access. You can keep them at that level until a class in another package requires access to them. In order to get your code to compile, you will first have to add an import statement so that the compiler knows to look in the studentinfo package for the Student and CourseSession classes. The modification to RosterReporterTest is shown here: package sis.report; import junit.framework.*; import sis.studentinfo.*; public class RosterReporterTest extends TestCase { ... Add the same import statement to RosterReporter. The classes in the studentinfo package still have package-level access, so classes in the reports package will not be visible to them. Change the class declaration to be public for Student, CourseSession, and DateUtil, as shown in the example for Student: package sis.studentinfo; public class Student { ... You will also receive a compilation error for AllTests.java. It no longer recognizes the class RosterReporterTest, since RosterReporterTest has been moved to a different package. For now, comment out that line in AllTests.java: package sis.studentinfo; import junit.framework.TestSuite; public class AllTests { public static TestSuite suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(StudentTest.class); suite.addTestSuite(CourseSessionTest.class); // suite.addTestSuite(RosterReporterTest.class); suite.addTestSuite(DateUtilTest.class); return suite; } } You will soon create a new AllTests for the reports package. Be careful when commenting out codeit's easy to forget why the code is commented out. After recompiling, you will receive lots of errors for each message sent from the code in the reports package to Student and CourseSession objects. Like classes, the default access level for constructors (and methods) is package. Just as classes need to be public in order to be accessed from outside the package, methods and constructors also must be declared as public. Do so judiciouslyyou should never make blanket declarations of every method as public. As a matter of style and organization, you may also want to move public methods so they appear before the nonpublic methods in the source file. The idea is that a client developer interested in your class will find the public methodsthe methods they should be most interested infirst. With IDEs, this organization is not as necessary, as most IDEs provide a better way to organize and navigate through source for a class. When finished, the production classes in studentinfo should look something like the following. Student.java: package studentinfo; public class Student { private String name; public Student(String name) { this.name = name; } public String getName() { return name; } } CourseSession.java: package studentinfo; import java.util.*; /** * This class provides a representation of a single-semester * session of a specific university course. * @author Administrator */ public class CourseSession { private String department; private String number; private ArrayList<Student> students = new ArrayList<Student>(); private Date startDate; /** * Constructs a CourseSession starting on a specific date * @param startDate the date on which the CourseSession begins */ public CourseSession( String department, String number, Date startDate) { this.department = department; this.number = number; this.startDate = startDate; } String getDepartment() { return department; } String getNumber() { return number; } int getNumberOfStudents() { return students.size(); } public void enroll(Student student) { students.add(student); } Student get(int index) { return students.get(index); } Date getStartDate() { return startDate; } public ArrayList<Student> getAllStudents() { return students; } /** * @return Date the last date of the course session */ Date getEndDate() { GregorianCalendar calendar = new GregorianCalendar(); calendar.setTime(startDate); final int sessionLength = 16; final int daysInWeek = 7; final int daysFromFridayToMonday = 3; int numberOfDays = sessionLength * daysInWeek - daysFromFridayToMonday; calendar.add(Calendar.DAY_OF_YEAR, numberOfDays); return calendar.getTime(); } } DateUtil.java: package studentinfo; import java.util.*; public class DateUtil { public Date createDate(int year, int month, int date) { GregorianCalendar calendar = new GregorianCalendar(); calendar.clear(); calendar.set(Calendar.YEAR, year - 1900); calendar.set(Calendar.MONTH, month - 1); calendar.set(Calendar.DAY_OF_MONTH, date); return calendar.getTime(); } }
At this point, everything should compile. Your tests should also run, but don't forget that you commented out RosterReporterTest. It's time to add it back in. Create a new class named AllTests in the sis.report package. Generally you will want a test suite in each package to ensure that all classes in the package are tested.[6]
package sis.report; import junit.framework.TestSuite; public class AllTests { public static TestSuite suite() { TestSuite suite = new TestSuite(); suite.addTestSuite(RosterReporterTest.class); return suite; } } You can now remove the commented-out line from the class student-info.AllTests. Create a class named AllTests in the sis package by placing its source file in the sis directory. This class will produce the combined test suite that ensures all classes in the application are tested. package sis; import junit.framework.TestSuite; public class AllTests { public static TestSuite suite() { TestSuite suite = new TestSuite(); suite.addTest(sis.report.AllTests.suite()); suite.addTest(sis.studentinfo.AllTests.suite()); return suite; } } Instead of sending the message addTestSuite to the suite, you send the message addTest. As a parameter, you pass along the results of sending the message suite to the appropriate AllTests class. Sending a message to a class instead of to an object will result in a static method being called. I will discuss static methods in the next lesson. You will want to pass sis.AllTests to JUnit in order to run your entire test suite. |