Writing tests is usually simple task. When you write tests ahead of code and even ahead of design it becomes really trivial task. Having all tests at hand even before you start coding specific functionality guaranties that you will (a) spend minimum time, (b) implement what’s really necessary, (c) have all documentation and tests ready, (d) sleep well, knowing that you can give the product away in the middle of the night as it’s already tested to be fool proof.

What do you usually do when you need to model and implement something. I think that the most common answer will be “starting to code right away”. I must admit that it’s so common that I could even not ask you. In fact, what’s most important on the first phase is to collect and define use cases. Use case is definition of how some part of application is going to be used. Having the list of use cases helps a lot to make minimal, yet sufficient, design decisions. In this essay I will try to guide you through building of a well-known multi-domain login dialog. It’s very common and very simple example of stand-alone module with the set of requirements, use cases and constraints. Through all the text I will illustrate how the tests can help us with building a flexible and robust design, help use to spend minimum time and finish being dead sure about functionality without dedicating specific phase to pure tests writing.

Module requirements and use cases

Let me begin with brief requirements outline. We need login dialog box with two drop-down lists – domain and user name – and password field. When this dialog is shown to the user for the first time it should be initialized with some default domain, first user name from that domain and empty password field. When user clicks “Login” the dialog should validate information entered and display warning message if user name isn’t selected or password is empty. Later, when it’s closed, the dialog should allow the application to examine its “Login”/“Cancel” state and all data entry fields (domain, user name and password). Looks pretty simple? Yes, it does.

And now let me outline the use cases for this dialog to get a better picture of what user can do with it:

  • Select domain. When user selects domain the list of user names available in this domain changes. At the moment of domain change there can be some user name selected and some password entered. If there’s the same user name present in newly selected domain it should not be reset and the password should not be cleared. Otherwise the first available user name should be selected and the password cleared.
  • Select user name. When user selects some other user name the password should be cleared.
  • Enter password.
  • Confirm the entry. This action calls the validation which is checking that user name selected (it may come that some domain still has no users) and password entered (empty passwords aren’t allowed). If all of these conditions are true then we continue with closing the dialog, and otherwise we display some warning and abort closing the dialog.
  • Cancel the login operation.

Now we know what it should do and what are ins and outs:

  • Inputs:
    • List of domains
    • List of user names for each domain
    • Default domain selection
  • Outputs:
    • Login/Cancel flag
    • Selected domain
    • Selected user name
    • Entered password

In this first part of essay I will concentrate on the basics of Test Driven Design. I will move on through all steps of development and provide detailed code samples and comments on what and why we do. We will concentrate on the Inputs of our future dialog box only. The second part of this writing will have less details on the process, but concentrate on specific questions and overall methodology in application to more complex part.

Building Domain Resolver

As you could see, there should be a list of users for each domain available. This situation can be handled at least twofold. First option, to create a special object Domain which will hold all available user names. The other approach, to provide user names source object which will return the list of user names for a given domain name. As one can have several domains with big number of users, it’s not a good idea to load them all each time we need to show login dialog, so we follow the second approach here.

We have an interface of the class which will be telling us lists of user names in some domain:

public interface IDomainResolver {}

Now we can write and acceptance test which will define what the callers are planned to do with this interface. For this reason, we simply define sample stub class which will be telling us two constant domains and two constant lists of users.

public class ConstantDomainResolver implements IDomainResolver {}

And now we are moving forward to the stub for our test:

public class TestDomainResolver extends TestCase
{
    private IDomainsResolver resolver;

    protected void setUp() throws Exception
    {
        super.setUp();

        resolver = new ConstantDomainResolver();
    }

    /** Tests reading of domains list. */
    public void testReadDomainsList()
    {
    }

    /** Tests reading list of user names for a given domain. */
    public void testReadUserNamesInDomain()
    {
    }
}

What you can see here is that we have two acceptance tests defined for our IDomainResolver object. The first will perform typical domains list lookup and the second will do typical fetching of users list for a given domain. I must admit that this kind of tests is somewhat different from unit tests, which you might have seen. We intend our tests to help us to put ourselves on the side of application or user of our new module. They allow us to abstract from watching from position of code and allow us to make more simple and straight forward decisions. Let’s continue with tests’ bodies?

public class TestDomainResolver extends TestCase
{
    ...

    /** Tests reading of domains list. */
    public void testReadDomainsList()
    {
        String[] domains = resolver.getRegisteredDomains();

        assertEquals("We have 2 domains initialized in our sample resolver.",
            2, domains.length);
        assertEquals("A", domains[0]);
        assertEquals("B", domains[1]);
    }

    ...
}

In this test we do typical operation of reading registered domains list through getRegisteredDomains() method call. We don’t have this method defined in the interface yet, but our test says that it’s one of the possible ways to accomplish our goal. The other strategic decision is that we use String as domain identifier which is also playing role of domain name. If we were creating application from the different side, we might end up with creation of some complex object for holding domain information and spend some valuable time on designing, implementing, documenting and testing it. For now, we don’t have any requirements, telling us that we need that monster. We just need an object which holds the name of domain and identifies it uniquely – it’s our String.

Now let’s fill the other test method with some code:

public class TestDomainResolver extends TestCase
{
    ...

    /** Tests reading list of user names for a given domain. */
    public void testReadUserNamesInDomain()
    {
        String[] userNames;

        userNames = resolver.getUserNamesInDomain("A");
        assertEquals("We have two users defined in domain A.", 2, userNames.length);
        assertEquals("User A", userNames[0]);
        assertEquals("User C", userNames[1]);

        userNames = resolver.getUserNamesInDomain("B");
        assertEquals("We have two users defined in domain B.", 2, userNames.length);
        assertEquals("User B", userNames[0]);
        assertEquals("User C", userNames[1]);

        userNames = resolver.getUserNamesInDomain("C");
        assertEquals("We don't have users in domain C as it's not defined.", 0, userNames.length);    }
    ...
}

In this test we read the list of users from domains A and B, and also try to fetch users list for unregistered domain C. The picture with method getUserNamesInDomain() is similar to that with getRegisteredDomains() and motivation behind using String is the same.

Now we have an example of how our future objects will be used on practice and even have some minor tests suite to check that they are doing right. But the real value is that you haven’t spent time on designing ultra-flexible all-covering module, but did only what was really required. Now we fill in the interface definition with our two new methods and update the class with information necessary to pass our tests.

public interface IDomainsResolver
{
    /**
     * Returns the list of all registered domains.
     *
     * @return registered domains.
     */
    String[] getRegisteredDomains();

    /**
     * Returns the list of user names in given domain.
     *
     * @param domain domain.
     *
     * @return list of registered user names.
     */
    String[] getUserNamesInDomain(String domain);
}

public class ConstantDomainResolver implements IDomainsResolver
{
    /**
     * Returns the list of all registered domains.
     *
     * @return registered domains.
     */
    public String[] getRegisteredDomains()
    {
        return new String[] { "A", "B" };
    }

    /**
     * Returns the list of user names in given domain.
     *
     * @param domain domain.
     *
     * @return list of registered user names.
     */
    public String[] getUserNamesInDomain(String domain)
    {
        String[] userNames;

        if ("A".equals(domain))
        {
            userNames = new String[] { "User A", "User C" };
        } else if ("B".equals(domain))
        {
            userNames = new String[] { "User B", "User C" };
        } else
        {
            userNames = new String[0];
        }

        return userNames;
    }
}

What we already have?

  • Interface of domain resolver (IDomainResolver)
  • Sample implementation of domain resolver (ConstantDomainResolver)
  • Acceptance test for domain resolver interface (TestDomainResolver), which defines the contract for interface, and verifies our sample implementation.

If you were reading attentively, you could notice that we were talking about use cases in first order. For small domain Domain Resolver module all use cases are now hard coded into the tests and we are absolutely sure that the design allows us to do with this module what we really need. Just think about it!

OK, that’s enough for now. In the next part we will continue with the rest of our login dialog module. We have much more fun ahead as it’s only starting to get interesting. At this moment you should have enough information to start looking at the things you do daily at quite different angle. Try to follow my examples on practice with your favorite IDE to let your hands and mind remember the sequence. It’s a good time investment as it saves you your time, your money and makes you really confident in what you do.

See you in the next part!