Sunday, July 24, 2011

Model-Based Integration Testing – Part II (State encapsulation)

Last time we saw how we could perform Model-Based Integration Testing of a FileSystem model and a Network model using extension models. One problem with this approach was that the first model had to expose its internal state to the other model. In this post I’m going to talk about model state space encapsulation.

A step in the right direction is to do model encapsulation. Let’s start out by obtaining the same model results but having the FileSystem model encapsulating its state. Simply change accessibility of all internal state variables to private:
        private static int drives, currentDirectory;
        private static SequenceContainer<int> directories = new SequenceContainer<int>();

Now of course our Network model won’t compile, as it was referencing the FileSystem state variables directly. We have to realize what the common concept is here – the Network layer is supposed to mimic a directory structure and to do this it must have an interface to create a drive in the FileSystem models state space. One easy way to obtain this is to increase the accessibility of the CreateDrive rule to public, so the Network model can directly call a rule in the FileSystem model:
        [Rule(Action = "MapNetworkDrive()")]
        static void MapNetworkDrive()
        {
            FileSystem.CreateDrive();
        }

This works fine as long as the FileSystem model actually supports the required functionality, but going back to the original implementation, we never designed the FileSystem to support the removal of a drive – so this approach does not work for the DisconnectNetworkDrive rule. One way to solve this would of course be to implement one or more rules in the FileSystem model, that allows us to remove a drive, but why should we do so if it is never used? Well, alternatively we can make helper methods (that are not model rules) that allow for these modification:
        public static void DeleteDrive()
        {
            Condition.IsTrue(FileSystem.drives > 0);
            drives--;
            directories.Clear();
        }

And call these:
        [Rule(Action = "DisconnectNetworkDrive()")]
        static void DisconnectNetworkDrive()
        {
            FileSystem.DeleteDrive();
        }

Now we can build the project and explore the union model and recover the exploration results, but now the internal state space is encapsulated for the FileSystem model!

Okay that’s nice, but can we take it a step further? I don’t quite like having to expose my model rules as public and calling them from other models, as I may decide to change how the initial rules work later on. Well, yes we can do even better! We can start by encapsulating all the state variables into a class:
        public class FileSystemState
        {
            private int drives, currentDirectory;
            private SequenceContainer<int> directories = new SequenceContainer<int>();
        }

        public static FileSystemState state = new FileSystemState();

Now the cool thing is that even though the FileSystemState class is defined as public inside the FileSystem model (you can reference it through FileSystem.state) it doesn’t matter because all the logic is going to be encapsulated inside the class itself! (drives, currentDirectory and directories are all private to the FileSystemState class). Of course nothing will compile anymore, so we need to update references – but you will notice that FileSystemState is even encapsulated towards FileSystem.

Now, we move all the required logic to the FileSystemState class, which ends up with the following structure:
        public class FileSystemState
        {
            private int drives;
            private int currentDirectory;
            private SequenceContainer<int> directories = new SequenceContainer<int>();

            public int Drives { get { return drives; } }
            public int CurrentDirectory { get { return currentDirectory; } }
            public int DirectoryCount { get { return directories.Count(); } }
            public int FileCount() { return directories.Sum(); }

            public void AddDirectory()
            public void AddFile(int i)
            public int DirectoryFileCount(int i)
            public void CreateDrive()
            public void DeleteDrive()
            public void SetCurrentDirectory(int i)
        }

After patching up all references in FileSystem and Network models (see reference [1] for source code), the nice thing is that both models have to adhere to the same rules. The Network model is update to the following:
    static class Network
    {
        [Rule(Action = "MapNetworkDrive()")]
        static void MapNetworkDrive()
        {
            Condition.IsTrue(FileSystem.state.Drives == 0);
            FileSystem.state.CreateDrive();
        }

        [Rule(Action = "DisconnectNetworkDrive()")]
        static void DisconnectNetworkDrive()
        {
            Condition.IsTrue(FileSystem.state.Drives > 0);
            FileSystem.state.DeleteDrive();
        }
    }

It doesn’t matter that we have additional functionality built into the FileSystemState class, because we need not model everything. It is in fact also possible to introduce model conditioning inside the FileSystemState class, however, I strongly prefer to build the logic in the state class while retaining all conditions in the model rules.

The benefit of the updated structure is that it any change to the internal state variables are performed inside the FileSystemState class which is a nested class of FileSystem. Later we will see how model state encapsulation becomes handy when multiple models are working together.

[1] Encapsulated State

No comments:

Post a Comment