Testing Logging


Now your problem is figuring out how to test the logging. First, however, as I promised, you must throw away the spike code in Student. Discard the log method and update the constructor by removing the call to log.

 public Student(String fullName) {    this.name = fullName;    credits = 0;    List<String> nameParts = split(fullName);    if (nameParts.size() > MAX_NAME_PARTS) {       String message =          String.format(Student.TOO_MANY_NAME_PARTS_MSG,                        fullName, MAX_NAME_PARTS);       throw new StudentNameFormatException(message);    }    setName(nameParts); } 

Don't forget that writing code without tests is a good way of getting into trouble. Make sure you specify what you're going to code first.

While you have successfully logged a message, its output went to the console. How are you going to intercept this message for the purpose of writing a test?

The logging facility lets you direct messages to destinations other than the console. You can also direct logging to more than one destination at a time.[7] The Logger stores handler objects that it uses to determine the logging destinations. A handler object is a subclass of java.util.logging.Handler. The class ConsoleHandler represents the behavior of sending output to the console. Most shops prefer to redirect logging output to files, using a FileHandler, so that developers can later peruse the files. You can tell the logger to route messages to alternate destinations by sending it Handler objects.

[7] The UML diagram in Figure 8.2 shows that a Logger can have multiple Handler objects by using an asterisk (*) at the end of the navigable association between the two classes. The * is a multiplicity indicator. The absence of a multiplicity indicates either 1 or that the multiplicity is uninteresting or irrelevant. The relationship between Logger and Handler is a one-to-many relationship.

Unfortunately, you wouldn't be able to write test code that reads a log file, since you haven't learned about Java IO yet (you will in Lesson 11). Instead, you will create a new handler class, TestHandler. Within this handler class, you will trap all messages being logged. You will provide a getter method that returns the last message that was logged. Once you have built this handler class, you can pass an instance of it to the logger.

For each message you log, the Logger object calls the method publish on the Handler object, passing it a java.util.logging.LogRecord parameter object.

It is the publish method that you will override in your Handler subclass to trap the message being logged. You must also supply definitions for the other two abstract methods in Handler, flush and close. They can be empty.

 package sis.studentinfo; import java.util.logging.*; class TestHandler extends Handler {    private LogRecord record;    public void flush() {}    public void close() {}    public void publish(LogRecord record) {       this.record = record;    }    String getMessage() {       return record.getMessage();    } } 

In testBadlyFormattedName, you first obtain the same logger that Student will use. Since both the test and the code in Student pass the Student class name to getLogger, they will both reference the same Logger object. Subsequently, you construct an instance of TestHandler and add it as a handler to the logger.

 public void testBadlyFormattedName() {    Logger logger = Logger.getLogger(Student.class.getName());    TestHandler handler = new TestHandler();    logger.addHandler(handler);    ... } 

Any messages you log will be routed to the TestHandler object. To prove that Student code indeed logs a message, you can ask the handler for the last message it received.

 public void testBadlyFormattedName() {    ...    final String studentName = "a b c d";    try {       new Student(studentName);       fail("expected exception from 4-part name");    }    catch (StudentNameFormatException expectedException) {       String message =          String.format(Student.TOO_MANY_NAME_PARTS_MSG,                        studentName, Student.MAX_NAME_PARTS);       assertEquals(message, expectedException.getMessage());       assertTrue(wasLogged(message, handler));    } } private boolean wasLogged(String message, TestHandler handler) {    return message.equals(handler.getMessage()); } 

On a stylistic note, I prefer coding the test as follows:

 public void testBadlyFormattedName() {    Logger logger = Logger.getLogger(Student.class.getName());    Handler handler = new TestHandler();    logger.addHandler(handler);    final String studentName = "a b c d";    try {       new Student(studentName);       fail("expected exception from 4-part name");    }    catch (StudentNameFormatException expectedException) {       String message =          String.format(Student.TOO_MANY_NAME_PARTS_MSG,                        studentName, Student.MAX_NAME_PARTS);       assertEquals(message, expectedException.getMessage());       assertTrue(wasLogged(message, (TestHandler)handler));    } } 

Instead of assigning the new instance of TestHandler to a TestHandler reference, you assign it to a Handler reference. This can clarify the test, so you understand that the addHandler method expects a Handler as a parameter (and not a TestHandler). The wasLogged method needs to use the getMessage method you created in TestHandler, so you must cast back to a TestHandler reference.

The test should fail, particularly since you should have no production code in Student to meet the specification of the test (you deleted it, correct?). Demonstrate the failure before proceeding. For all you know, the new test code might not do anything, and if you get a green bar, alarms should go off in your head. (It happens. Sometimes it means you forgot to compile your code.) Sticking to the method will help you keep your cool and make fewer mistakes.

Another reason you should have deleted the spike code is that you will produce a better, refactored version this time around. You currently have duplicate code: Both StudentTest and Student include a complex line of code to retrieve a Logger object.

In addition to using a Student class variable for the logger, the modified test inlines the wasLogged method:

 public void testBadlyFormattedName() {    Handler handler = new TestHandler();    Student.logger.addHandler(handler);    final String studentName = "a b c d";    try {       new Student(studentName);       fail("expected exception from 4-part name");    }    catch (StudentNameFormatException expectedException) {       String message =          String.format(Student.TOO_MANY_NAME_PARTS_MSG,                        studentName, Student.MAX_NAME_PARTS);       assertEquals(message, expectedException.getMessage());       assertEquals(message, ((TestHandler)handler).getMessage());    } } 

Here's the resurrected, refactored Student code:

 package sis.studentinfo; import java.util.*; import java.util.logging.*; public class Student {    ...    final static Logger logger = Logger.getLogger(Student.class.getName());    ...    public Student(String fullName) {       this.name = fullName;       credits = 0;       List<String> nameParts = split(fullName);       if (nameParts.size() > MAX_NAME_PARTS) {          String message =             String.format(Student.TOO_MANY_NAME_PARTS_MSG,                           fullName, MAX_NAME_PARTS);          Student.logger.info(message);          throw new StudentNameFormatException(message);       }       setName(nameParts);    }    ... } 



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