1. Logging
Use logging instead of system.out.print
public static void main( String[] args ) {
Logger logger = Logger.getLogger( Main.class.getName() ); (1)
logger.log( Level.FINE, (2)
()-> String.format("Hello %1$s args length= %2$d ", "world",args.length) (3)
);
}
1 | Create a specific logger for this class. |
2 | This log statement only doe 'work' when the log level is set to FINE or higher (FINER, FINEST) |
3 | This lambda has the shape of a supplier and is only evaluated or computed when the logging level is active. |
1.1. Replace soutv by using a log macro.
When you use a supplier to supply the message that is logged, when logging is active, logging can become cheap. To make this work, you might want to teach your IDE to help a bit here, by adding a keyboard macro that adds logging instead of simple System.out.println(…).
logv
definition for NetBeans IDElogger.fine( ()->"${EXP instanceof="<any>" default="exp"} = " + ${EXP});
Now logging becomes simpler than ever.
-
First create a logger with the already define macro
logr
tab and then -
inside the methods use logv tab instead of soutv where ever you would like to output stuff for debugging.
In your logging.properties file add the proper level for you class or as global and you can turn the logging on or off when ever you need to.
1.2. Configure logging
When you copy the file logging.properties from its default location $JAVA_HOME/conf/logging.properties
to your projects
directory, preferably under a conf
directory, you can modify and adapt that file to make logging effective in a very precise way,
by specifying what level you want for what class.
#.properties
handlers= java.util.logging.ConsoleHandler
.level=FINE (1)
java.util.logging.ConsoleHandler.level=FINE
logdem.LogDem.level=FINE (2)
1 | specify global level |
2 | specify for one class Then in the java startup (for instance in a run script or in one of the IDE settings), make sure this java property specifies the file. |
For NetBeans add -Djava.util.logging.config.file=conf/logging.properties
to the java run actions.
Say the class that needs debugging is called logging.Main
. Then in the logging properties file (see above) add
logging.Main.level = FINE
, and make sure that you start the JVM with the proper login properties file.
You can also set the parameter in the 'run-configuration' of your project. Add the appropriate option there, e.g. -Djava.util.logging.config.file=conf/logging.properties ![]() Figure 1. point to project specific log file.
|
2. Squeezing the last coverage out
The example code below is given as an illustration to squeeze the last bit of coverage out of your code in case your code catches exceptions that never occur in a normal situation, but you have to deal with them anyway, because the exceptions are checked exceptions, and you do not have the option to pass them on in a throws clause, because some code up in the call stack does not accept that. We ran into this situation while developing the templating engine.
In normal production code squeezing the last drop of coverage out of your test code is rarely worth the effort. In cases where your project manager or architect insists on high coverage, here is how to deal with such request. Don’t make a habit out of it, because it adds little business value. |
You have some code that uses a resource-method that throws an exception. The way to go about to cover the catch block too is to mock the resource for that test case and let the mock throw the to be caught exception.
void flush( char c ) {
if ( c == '\0' ) {
return;
}
try {
out.append( c );
} catch ( IOException ex ) { (1)
Logger.getLogger( Engine.class.getName() )
.log( Level.SEVERE, null, ex ); (2)
}
}
1 | In normal cases this catch block is not reached. |
2 | Typically an IDE generated logiing statement inside a catch block. This also generates a stack-trace, which you may not always want. |
If you want to avoid the stack-trace in particular cases, you should replace the null
with a message (a String)
and remove the ex. You can of course get the message form the exceptions with ex.getMessage()
.
@Test
public void tFlush() throws IOException {
Appendable out = mock( Appendable.class ); (1)
Logger engineLogger =
Logger.getLogger( Engine.class.getName() ); (2)
Handler handler = mock( Handler.class );
engineLogger.addHandler( handler ); (3)
Mockito.doThrow( new IOException( "test" ) )
.when( out )
.append( 'o' ); (4)
Engine engine = new Engine( Map.of() )
.readingString( "Hello world" )
.writing( out );
engine.run(); (5)
verify( handler, atLeast( 1 ) ).publish( any() ); (6)
}
1 | Mock the output resource that should throw the exception |
2 | Get the a logger, making sure you get the right one for the class whose log you want to inspect and |
3 | give it a mock handler as proof that a logging statement on the logger occurred. |
4 | Make the output mock throw an exception on a typical output, the character 'o' in this test. |
5 | Run the SUT |
6 | Make it a real test by ensuring that logging occurred. Since the publish method is the abstract method to implement, that is the candidate method to verify in a custom handler. |
The code above is from the template engine, an exercise in week 9. Using a few variants of this code covers it all.
There are cases, where you have to deal with a checked exception because it is checked, but the code is called in a situation where the exception can never occur. Mark that situation clearly in your code, and accept that you can’t reach the catch block. Add a comment to the maintainer so he or she will not wonder why this code is not covered in the coverage report. If you add the comment in the code, that comment will be visible in e.g. a jacoco coverage report. |
Bulk validator
Quite often when dealing with user input, validation has to be done. This validation is often repetitious work for the programmer. How about validating all inputs of a form in one go, and at the same time apply necessary conversions.
The bulkvalidator project is exactly fit for this purpose.
It takes all inputs as a map, validates and converts (e.g. parses to Integer
, double
, long
, LocalDate
etc.)
and returns them as result.
While checking the validity of the values in the input map, it checks for correctness. In the inner loop, it catches and collects the exceptions that can occur when a validation fails.
class BusinessLogic {
static BulkValidator bv = new BulkValidator().addValidations( (1)
Map.of( "customer.id", ( String s ) -> Integer.valueOf( s ),
"customer.someInt",
validateWith( Integer::valueOf,
BusinessException::new, "Invalid integer" ),
"customer.dob", LocalDate::parse,
"customer.postcode", BusinessLogic::validatePC
)
);
void submit( Map<String,String> inputs ) throws BulkException { (2)
Map<String,Object> validInputs=bv.validate( inputs );
process( validInputs ); (3)
}
private void process(Map<String,Object> validInputs) { (4)
// use inputs. With a mapper and some
customer = new Customer( validInputs.get( "name" ), validInputs.get( "dob" ));
// do something with customer.
}
}
1 | Give the validator validation rules. You typically do that only once per client class, like the customer controller. |
2 | This method will fail with an exception if validation fails. |
3 | Validate, but notice that when an error occurs, the submit method stops with a BulkException , which is passed to the caller, typically
a GUI class in a JavaFX app.If the validation runs without issues, process the result, which are now valid key-value pairs. |
4 | Do the normal processing on the now known to be correct values. |
Bulk validation can be used in any form based UI, where you collect the data from the UI Controller. In a JavaFX based project that would be a JavaFX controller, such as a CustomerController.
// injected logic
final BusinessLogic bl;
FXUIScraper scraper = () -> root; (1)
@FXML
private void submit( ActionEvent event ) { (2)
Map<String, String> keyValues = scraper.getKeyValues( x -> true );
try {
bl.submit( keyValues );
} catch ( BulkException bex ) { (3)
displayerrors( bex );
}
}
/**
* The exception passed in contains a map with name of the failing controls
* and the exception thrown.
* @param bex exception information
*/
void displayerrors( BulkException bex ) {
Map<String, Throwable> causes = bex.getCauses();
// do something smart with the map, like asking for cause of problem
// maybe get internionalized message
// maybe change style or toiltip of the control.
}
1 | FXUIScraper is a small library that can be used as companion to the BulkValidator. It is a dependency in the BulkValidator project and there is testcode too. The project is hosted at homberghp/fxuiscraper. |
2 | The GUI code submits to the business logic, whatever it found when scraping. |
3 | When the business code refuses with an exception, update the UI with the error info. |
In all cases it is the business code that does the validation, and the UI that does the input collecting and display. Proper separation of concerns.
If compilation does not work because maven can’t find a dependency, you may have to change the dependency on genericmapper to version 3.0.2. |
Bulk Validation
The work in the BulkValidator project should be obvious. Complete the test and implementation code.
The hardest part is in the tests.
Let the IDE guide you. AssertJ is very powerful but as with all haystacks, it is sometimes hard to find
what you want. For instance after you type
assertThatThrownBy(code).isExactlyInstanceOf( BulkException.class )
.
and then force completion, you see a whole slew of completions. The ones named extractingXXX
open new possibilities.

assertThatThrownBy(…)
This should be sufficient to complete the puzzle.
Also ensure that the validator logs with level info and that the log messages contain the correct 'words'.
Present There is NO work in the bulkexample project, it only serves as an example. It might have relevance for project 2. Enjoy. |
3. Reading
-
Horstmann Core Java, Ed 11, Vol I, Ch 7