» Home and Blogs

  » Essays

  » Software

  » Prime Brokerage

  » Web Page Optimisation

  » Interviewing

  » Publications

  » Resources

  » Book Reviews

  » About Me




.

..

(1) Singletons and testing

(1) Singletons and testing

 

Returning to last weeks topic, lets look at Singletons in the concept of testing.

 

There’s a lot of on-line pontificating on how Singletons are the curse of test driven software development and should be banished. There’s almost as much boosting of the entirely opposite view point – that they make testing easier.

 

I not convinced I subscribe to either view – but what I can say is that code using Singletons is no harder to test that code that does not use them.

 

The following three examples show different ways to “configure” a Singleton at run-time so your tests can generate specific situations without modifying any code that consumes the singletons.

 

 

(1.1) System Properties

 

A classic example of the Singleton is a logger used by all components in a system. This example has some merit as just that, and example of a Singleton, but in practice such a simple approach to logging is insufficient in any modern system.

 

That said, consider this:

 

package logging;

 

/**

 * Configurable logger.

 * <p>

 * Set the system property:

 * <ul>

 * <li>"logging.Logger.handler"

 * </ul>

 * to the name of an implementation of Handler to modify the

 * logging behaviour.

 */

public final class Logger

{

    private static Handler handler= createHandler();

    

    private static Handler createHandler() {

(3)     String clazz= System.getProperty(

            "logging.Logger.handler", "logging.DefaultHandler");

        Handler h= null;

        try {

            h= (Handler) Class.forName(clazz).newInstance();

        } finally {

            return h != null ?  h : new DefaultHandler();

        }

    }

 

(1) private Logger() { }

 

    /**

     * Dispatch <tt>message</tt> to the configured <tt>Handler</tt>.

     * No additional formatting is done on <tt>message</tt>.

     */

(2) public static void log(String message) {

        handler.log(message);

    }

}

 

(1)      Private constructor. Bit of a give away for a Singleton

(2)      public static methods on the class – by some accounts this is not a true singleton (I tend to think it is) but is an example of how to implement the pattern

(3)      A system property is examined to determine how to handle log messages.

 

So, in normal operation, the Logger will use a DefaultHandler.

 

In test mode, we may not want this, so we can implement our own Handler implementation and use that:

 

package logging;

 

import junit.framework.TestCase;

 

public class TestLogger

    extends TestCase

{

    public static class TestHandler extends Handler {

        public void log(String message) {

            logMessages += message +", ";

        }

    }

 

    private static String logMessages;

 

    protected void setUp() throws Exception {

        System.setProperty(

            "logging.Logger.handler",

            "logging.TestLogger$TestHandler");

        logMessages = "";

    }

 

    public void testLogger() {

        Logger.log("hello world");

        Logger.log("hello mum");

        Logger.log("hello kitty");

        assertTrue(logMessages.equals(

            "hello world, hello mum, hello kitty, "));

    }

}

 

In this case the custom handler is used to assist in the test, in other cases a “null” handler could be used to simply discard the messages.

 

 

(1.2) Mock Objects

 

The custom handler in the above example is, essentially, a mock object. If we want to be more explicit about things, we can provide programmatic methods for assigning the mock.

 

For example, if we have a singleton Database. How to we mock its persistence layer? This is a very common thing to do in testing.

 

Take this example:

 

package data;

 

public final class Database

{

(4) private static Persistence persistence= new FilePersistence();

 

    private static Database instance;

 

    static void setPersistence(Persistence persistence) {

        Database.persistence= persistence;

    }

 

(5) public static synchronized Database getInstance() {

        if (instance == null)

            instance= new Database();

        return instance;

    }

 

(1) private Database() { }

 

    /**

     * @param  id the user id

     * @return the user identified by <tt>id</tt>, <tt>null</tt>

     * if not found.

     */

    public User getUser(int id) {

(3)     return persistence.getUser(id);

    }

}

 

(1)      Private constructor again – certainly smells like a Singleton

(2)      Public static lazily instantiated accessor for the single instance.

(3)      Persistence delegate is used here

(4)      defaulted here

(5)      and re-assigned here

 

So, we have the production configuration that uses a real persistence mechanism, but we have the opportunity to use a mock one.

 

The following test case does just that (the logic class it tests and associated classes can be found in the attached .zip file):

 

package logic;

 

import junit.framework.TestCase;

 

import data.User;

import data.MockPersistence;

 

public class TestIsHeTheDevilInDisguise

    extends TestCase

{

    /**

     * The real devil should NOT be identified as being in disguise. He is

     * who he is, and proud of it, no doubt.

     */

    public void testTheDevil() {

        User user= new User(666, "The Devil");

(1)     new MockPersistence(new User[] { user });

        assertFalse(new LogicBean().isHeTheDevilInDisguise(user.getId()));

    }

 

    /**

     * Even if he changes his name, the real devil should not be identified

     * as being in disguise.

     */

    public void testFakeDevil() {

        User user= new User(666, "A Fake Devil?");

(1)     new MockPersistence(new User[] { user });

        assertFalse(new LogicBean().isHeTheDevilInDisguise(user.getId()));

    }

 

    /**

     * The well known alias "Lucifer" should be identified as being the

     * devil in disguise.

     */

    public void testLucifer() {

        User user= new User(999, "Lucifer");

(1)     new MockPersistence(new User[] { user });

        assertTrue(new LogicBean().isHeTheDevilInDisguise(user.getId()));

    }

 

    /**

     * The well known alias "Beelzebub" should also be identified as being

     * the evil one in a wig.

     */

    public void testBeelzebub() {

        User user= new User(333, "Beelzebub");

(1)     new MockPersistence(new User[] { user });

        assertTrue(new LogicBean().isHeTheDevilInDisguise(user.getId()));

    }

 

    /**

     * Sir Cliff (bless him) is rarely identified as Satan.

     */

    public void testCliffRichard() {

        User user= new User(111, "Cliff Richard");

(1)     new MockPersistence(new User[] { user });

        assertFalse(new LogicBean().isHeTheDevilInDisguise(user.getId()));

    }

}

 

(1)      Here a mock persistence object is created (it attaches itself to the Database). This mock has exactly what we want in it so the test fails or works as expected each time.

 

 

 

(1.3) Configuration Singleton

 

Ok, both the two earlier methods are easy to use and flexible to various degrees. However, they do introduce a security problem.

 

They leave testing artefacts in the production code.

 

These artefacts could introduce security problems!

 

For code running inside the bank, this is not a big issue, but for the code that runs outside the bank, on client machines, we should really try to minimise this risk.

 

So, what to do?

 

If we assume good deployment practice that requires heavy obfuscation plus jar sealing and signing. We can have production code query configuration Singletons that do not delegate and always return the same values.

 

In a testing scenario, one-for-one replacements for the configuration singletons can be placed on the class path ahead of the real ones.

 

In production, the fact that the jars have been sealed prevents third parties from dong this themselves.

 

Take this example, where a socket factor would normally return secure sockets can be configured to return plain ones for testing:

 

package net;

 

import java.net.Socket;

 

import java.io.IOException;

 

public final class SecureSocketFactory {

 

    private SecureSocketFactory() { }

 

   /**

     * Create a secure socket.

     *

     * @param  host       machine name or ip address of the machine an SSL

     *                    secured server is running on.

     * @param  port       port the server is listening on.

     * @param  pkcs12file absolute or relative pathname to a PKCS12 encoded

     *                    certificate chain to use as the client certificate.

     * @param  passwd     plain text password for the PKCS12 certificate file.

     * @return an secure socket

     * @throws java.io.IOException if the PKCS12 file could not be read, a

     *         valid certificate could not be extracted from it or the socket

     *         create failed.

     */

    public static Socket createSecureSocket(

        String host, int port, String pkcs12file, String passwd)

    throws IOException {

 

        Socket socket = null;

 

        if (Config.usePlainSockets()) {

            socket = new Socket(host, port);

        } else {

            // create a real SSL socket

        }

 

        return socket;

    }

}

 

Where the Config class is:

 

package net;

 

final class Config {

    static boolean usePlainSockets() {

        return false;

    }

}

 

If we have a test that wants to use plain sockets, we do something like this:

 

package net;

 

import java.net.Socket;

import java.net.ServerSocket;

 

import junit.framework.TestCase;

 

public class TestSecureSocketFactory

    extends TestCase

{

    public void testCreateSecureSocket() throws Exception {

        new Thread() {

            public void run() {

                try {

                    new ServerSocket(666).accept();

                } finally {

                    return;

                }

            }

        }.start();

 

        Thread.sleep(500);

 

        Socket socket= SecureSocketFactory.createSecureSocket(

            "localhost", 666,

            "some-file-that-does-not-exist.p12", "foo");

 

        assertTrue(socket != null);

    }

}

 

 

final class Config {

    static boolean usePlainSockets() {

        return true;

    }

}

 

 

NOTE:

 

Taking this approach is not fail-safe. Following this pattern, obfuscating the deployed code, sealing and signing the jars are all important steps, but all they do is “raise the bar” to a level where it would take a highly skilled practitioner to break into the client code.

 

If we assume that the client side can be subverted, we must ensure the server is secure – which has less to do with software and more to do with procedure and correct separation of responsibilities in the organisation.

 

 















This site is © Copyright BenStopford.com 2003-2005, All Rights Reserved