Access Modifiers


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.

Protect your code as much as possible. Relax access modifiers only when necessary.


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();    } } 

Where Do the Tests Go?

You have been putting your test classes in the same package as your production classes so far; for example, StudentTest and Student both appear in the same studentinfo package. This is the easiest approach, but not the only one. Another approach is to create an equivalent test package for every production package. For example, you might have a package named test.studentinfo that contains tests for classes in studentinfo.

One advantage of putting the tests in the same package as the production code is that the tests have access to package-level details of the class they test. However, that level of visibility could be construed as a negative: As much as possible, you should design your tests to test a class using its public interfacewhat it makes publicly available. The more your test requires access to private information, the more tightly coupled, or dependent, it becomes to the actual implementation. The tight coupling means that it is more difficult to make modifications to the production class without adversely impacting the test.

You will still find occasion to assert against information from an object that you wouldn't otherwise expose publicly. You may want to combine test and production classes in the same package for that reason. If you find that this scheme results in too many classes in one directory, you can use Java's classpath to your advantage: Create the same directory structure in two different subdirectories and have the classpath refer to both subdirectories.

For example, suppose you compile your classes into c:\source\sis\bin. You can create a second class file location, c:\source\sis\test\bin. Subsequently, you can modify your build scripts (Ant makes this very easy) to compile only test classes into the c:\source\sis\test\bin directory. Everything else will compile into c:\source\sis\bin. You can then put both c:\source\sis\bin and c:\source\sis\test\bin on the classpath.

Using this scheme, the class file for Student would end up as c:\source\sis\bin\studentinfo\Student.class, and the class file for StudentTest would end up as c:\source\sis\test\bin\studentinfo\StudentTest.class. Both classes remain in the package studentinfo, yet each is in a separate directory.


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]

[6] There are other ways of managing test suites; your IDE may provide some assistance here. Also, refer to Lesson 12 for a dynamic way of gathering tests.

 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.



Agile Java. Crafting Code with Test-Driven Development
Agile Javaв„ў: Crafting Code with Test-Driven Development
ISBN: 0131482394
EAN: 2147483647
Year: 2003
Pages: 391
Authors: Jeff Langr

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net