Alexander Beletsky's development blog

My profession is engineering

An Architectural Design Challenge by James Shore

This is a response to James Shore blog entry. James has started “An Architectural Design Challenge” to create a simple application, but with architecture on top of the mind.

James is a famous blogger and author of great book that I’m reading online “The art of Agile development”. I suggest you both familarize with blog and book.

So, he is proposing to took an “An Architectural Design Challenge”. It is required to create a small application that should expose some major architectural aspects of software development. I’ve decied to take part of this challange also. Task has been posted awhile ago, unfortunately I’ve been busy with another stuff, but as a saying goes “better later than never”.

Requirements are:

  1. Write the ROT-13 tool described above. Note that the input file must be loaded entirely into memory.
  2. Provide a configuration mechanism that reads and writes files from one directory when “in testing” and another directory when “in production.”
  3. Wrap your platform’s console and file I/O methods as if each call actually required a dozen lines of code. Your configuration mechanism can use file I/O directly, though.
  4. No slideware allowed. You can use diagrams to illustrate your architecture, but you must code the entire solution. This prevents seemingly-elegant solutions that don’t work in practice.
  5. Use test-driven development to code everything.


I would not say that I’m using some special technics or patterns, just following most simple implementation and tests.

Preparation



As always starting with new design I try to bring it down to 3 major piecies: Business Function, Data, Presentation. So, I’ve added new project Rot13.Core.Library with 3 folders in it - BLL, DAL, Presentation. BLL is a heart will contain implementation of all functions required by application, DAL will provide those functions with required data and Presentation is responsibe how to display information to user. Right after that I’ve added a corresponding test project that would contain all required tests, as we are following TDD strictly tests will be first we we would create. So, solution look like:



Functions



Well, what we need is function that is able to get some input stream, do transformation of the data extracted from stream and send data back to output stream. This ROT13 algorigtm is core function, without all other architectural stuff is senceless, so let’s start and implement it!

ROT13 is primitive cipher, wikipedia contail a lot info about it.

After a several interations of testing and refactoring and came up with such implementation:
Tests:

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Rot13.Core.Library.BLL;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Rot13.Core.Library.Tests.BLL_Tests
{
  /// <summary>
  /// Summary description for TransformTests
  /// </summary>

  [TestClass]
  public class Rot13Tests
  {
    private static void TestCipher(char[] input, char[] expected)
    {
      //ACT
      char[] output = Rot13Algorithm.Process(input);

      //POST
      CollectionAssert.AreEqual(expected, output);
    }


    [TestMethod]
    public void CipherSmallTest()
    {
      //INIT
      var input = new char[] {
        'a', 'b', 'c', 'd'
      };
      var expected = new char[] {
        'n', 'o', 'p', 'q'
      };

      TestCipher(input, expected);
    }

    [TestMethod]
    public void CipherFullTest()
    {
      //INIT
      var input = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
      var expected = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm";

      TestCipher(input.ToCharArray(), expected.ToCharArray());
    }

    [TestMethod]
    public void CipherTestFromJamesArcticle()
    {
      var input = "The dog barks at midnight.";
      var expected = "Gur qbt onexf ng zvqavtug.";

      TestCipher(input.ToCharArray(), expected.ToCharArray());
    }

    [TestMethod]
    public void CipherRevertTransform()
    {
      var input = "The dog barks at midnight.";
      var expected = "Gur qbt onexf ng zvqavtug.";

      TestCipher(input.ToCharArray(), expected.ToCharArray());
      TestCipher(expected.ToCharArray(), input.ToCharArray());
    }
  }
}

* This source code was highlighted with Source Code Highlighter.

Rot13 (preatty small, heh?):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Rot13.Core.Library.BLL
{
  public class Rot13Algorithm
  {
    private static string _original = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    private static string _lookup = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm";
    
    public static char[] Process(char[] input)
    {
      return (from s in input let position = _original.IndexOf(s) select ((position != -1) ? _lookup[position] : s)).ToArray();
    }
  }
}

* This source code was highlighted with Source Code Highlighter.


Configuration


Now, what I need is to make application configurable. It is OK if we put the configuration to a special config file and have a class to work with configuration data. My preference is to have configurations as singletone’s (in the same time I realize some major drawbacks of singletone’s and try to void usage of it, but let’s keep most simple implementation in this solution). Implementation uses Linq to XML to handle XML data. Let’s not forget about one of requirements.. we have several types of configuration: Test, Production. We could decide on run-time what current configuration is active.

Configuration file.

<?xml version="1.0" encoding="utf-8" ?>
<configurations>
 <active type="test" />
 
 <configuration type="test">
  <input val="./testInput"/>
  <output val="./testOutput"/>
 </configuration>
 
 <configuration type="production">
  <input val="./input"/>
  <output val="./output"/>
 </configuration>
</configurations>

* This source code was highlighted with Source Code Highlighter.


using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Rot13.Core.Library.Tests
{
  /// <summary>
  /// Summary description for ConfigurationTests
  /// </summary>
  [TestClass]
  public class ConfigurationTests
  {
    [TestMethod]
    public void Smoke()
    {
      Configuration.Init();
    }

    [TestMethod]
    public void Init()
    {
      try
      {
        var value = Configuration.Settings["some"];
      }
      catch (Exception)
      {
        //OK
        return;
      }

      Assert.Fail();
    }

    [TestMethod]
    public void InputFolderTestMode()
    {
      TestValueInMode("configuration_test.xml", "input", "./testInput");
    }

    [TestMethod]
    public void InputFolderProductionMode()
    {
      TestValueInMode("configuration_production.xml", "input", "./input");
    }

    [TestMethod]
    public void OutputFolderTestMode()
    {
      TestValueInMode("configuration_test.xml", "output", "./testOutput");
    }

    [TestMethod]
    public void OutputFolderProductionMode()
    {
      TestValueInMode("configuration_production.xml", "output", "./output");
    }

    private void TestValueInMode(string configFile, string p, string p_2)
    {
      Configuration.ReInit(configFile);
      var value = Configuration.Settings[p];

      Assert.AreEqual(p_2, value);
    }
  }
}

* This source code was highlighted with Source Code Highlighter.


Business object


Next will be development of Business class responsible for primary application feature - transformation of input data, storing it and displaying to user. All these can be repesented as such test (I’m using Rhino mocks here as my favorite mocking framework):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rot13.Core.Library.BLL;
using Rhino.Mocks;
using Rot13.Core.Library.Presentation;
using Rot13.Core.Library.DAL;
using Rhino.Mocks.Constraints;

namespace Rot13.Core.Library.Tests.BLL_Tests
{
  [TestClass]
  public class Rot13ProcessorTests
  {
    MockRepository _mocks = new MockRepository();

    [TestMethod]
    public void Smoke()
    {
      var view = _mocks.StrictMock<IRot13ProcessorView>();
      var data = _mocks.StrictMock<IRot13ProcessorData>();

      var r = new Rot13Processor(view, data);
    }

    [TestMethod]
    public void Process()
    {
      //INIT
      var view = _mocks.StrictMock<IRot13ProcessorView>();
      var data = _mocks.StrictMock<IRot13ProcessorData>();

      With.Mocks(_mocks).Expecting(
        delegate
        {
          Expect.Call(data.Read()).Return("abcd");
          Expect.Call(delegate { data.Store(string.Empty); }).Constraints(Is.Equal("nopq"));
          Expect.Call(delegate { view.Display(string.Empty); }).Constraints(Is.Equal("nopq"));
        }
      ).Verify(
        delegate
        {
          var processor = new Rot13Processor(view, data);

          //ACT
          processor.Process();
        }
      );
    }
  }
}

* This source code was highlighted with Source Code Highlighter.


Corresponding implementation:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Rot13.Core.Library.Presentation;
using Rot13.Core.Library.DAL;

namespace Rot13.Core.Library.BLL
{
  public class Rot13Processor
  {
    public Rot13Processor(IRot13ProcessorView view, IRot13ProcessorData data)
    {
      View = view;
      Data = data;
    }

    public void Process()
    {
      var input = Data.Read();
      var output = new string(Rot13Algorithm.Process(input.ToCharArray()));

      Data.Store(output);
      View.Display(output);
    }

    #region Properties

    public IRot13ProcessorData Data { get; protected set; }
    public IRot13ProcessorView View { get; protected set; }

    #endregion
  }
}

* This source code was highlighted with Source Code Highlighter.


Context


Before implementation of Data/View classes let’s thing how those will be created and configured. One of my favorite desing pattern is Factory Method. So, both of them should be created by corresponding factories, but what about configuration? For instance Data class have to know what is input and output files? We have to have some kind of Context that would hold all reaquired information. Context itself is constructed from command line arguments of application.

Tests:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Rot13.Core.Library.Tests
{
  [TestClass]
  public class ContextTests
  {
    [TestMethod]
    public void Smoke()
    {
      //INIT
      var args = new string[] { "in.txt", "out.txt" };

      //ACT
      var context = new Context(args);

      //POST
      Assert.IsNotNull(context);
    }

    [TestMethod]
    [ExpectedException(typeof(ArgumentException), "Insufficient number of arguments")]
    public void InsuffientArguments()
    {
      //INIT
      var args = new string[] { "in.txt" };

      //ACT / POST
      var context = new Context(args);
    }

    [TestMethod]
    [ExpectedException(typeof(ArgumentNullException))]
    public void NullArguments()
    {
      //INIT / ACT / POST
      var context = new Context(null);
    }

    [TestMethod]
    [ExpectedException(typeof(ArgumentException), "Too many of arguments")]
    public void TooManyArguments()
    {
      //INIT
      var args = new string[] { "in.txt", "out.txt", "fail.txt" };

      //ACT / POST
      var context = new Context(args);
    }

    [TestMethod]
    public void ContextCorreclyInitialized()
    {
      //INIT
      Configuration.Init();
      var args = new string[] { "in.txt", "out.txt" };

      //ACT
      var context = new Context(args);

      //POST
      Assert.AreEqual("./testInput/in.txt", context.InputFIlename);
      Assert.AreEqual("./testOutput/out.txt", context.OutputFilename);
    }
  }
}

* This source code was highlighted with Source Code Highlighter.


Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Rot13.Core.Library
{
  public class Context
  {
    public Context(string[] arguments)
    {
      if (arguments == null)
        throw new ArgumentNullException("Arguments parameter is null");

      if (arguments.Count() < 2)
        throw new ArgumentException("Insufficient number of arguments");

      if (arguments.Count() > 2)
        throw new ArgumentException("Too many of arguments");

      InputFIlename = Configuration.Settings["input"] + "/" + arguments[0];
      OutputFilename = Configuration.Settings["output"] + "/" + arguments[1];
    }

    #region Properties

    public string InputFIlename { get; private set; }
    public string OutputFilename { get; private set; }
    
    #endregion
  }
}

* This source code was highlighted with Source Code Highlighter.


Data object


Now, go back to data class. It is rather simple, main resposibility is to be able to read string from file and to store string to file.

Tests:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rot13.Core.Library.DAL;
using Rhino.Mocks;
using System.IO;

namespace Rot13.Core.Library.Tests.DAL_Tests
{
  [TestClass]
  public class Ro13ProcessDataTests
  {
    MockRepository _mocks = new MockRepository();

    [TestInitialize]
    public void Setup()
    {
      Configuration.ReInit("configuration_test.xml");
    }

    [TestMethod]
    public void Smoke()
    {
      var context = new Context(new string[] {"in.txt", "out.txt"} );
      var data = Rot13ProcessDataFactory.Create(context);

      Assert.IsNotNull(data);
    }

    [TestMethod]
    [ExpectedException(typeof(ArgumentNullException))]
    public void ContextIsNull()
    {
      //INIT / ACT / POST
      var data = Rot13ProcessDataFactory.Create(null);
    }

    [TestMethod]
    public void Read()
    {
      //INIT
      var context = new Context(new string[] { "in.txt", "out.txt" });
      var data = Rot13ProcessDataFactory.Create(context);

      //ACT
      var input = data.Read();

      //POST
      Assert.AreEqual(input, "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.");
    }

    [TestMethod]
    public void Store()
    {
      //INIT
      var context = new Context(new string[] { "in.txt", "out.txt" });
      var data = Rot13ProcessDataFactory.Create(context);
      var testContent = "tests,tests,tests";

      //ACT
      data.Store(testContent);

      //POST
      var actual = File.ReadAllText(context.OutputFilename);
      Assert.AreEqual(testContent, actual);
    }
  }
}

* This source code was highlighted with Source Code Highlighter.


Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Rot13.Core.Library.DAL.Impl
{
  class Rot13FileDataImpl : IRot13ProcessorData
  {
    private string _outputFilename;
    private string _inputFilename;

    public Rot13FileDataImpl(Context context)
    {
      if (context == null)
        throw new ArgumentNullException();

      _inputFilename = context.InputFIlename;
      _outputFilename = context.OutputFilename;
    }

    public string Read()
    {
      return File.ReadAllText(_inputFilename);
    }

    public void Store(string content)
    {
      File.WriteAllText(_outputFilename, content);
    }
  }
}

* This source code was highlighted with Source Code Highlighter.


View object


After the data View is something we need to care about. On our application View is rather simple, so its just pust value on console out put. As with a data class I’m going to use Factory to produce View instance. Factory method will recieve Console object to put values on it. It will allow us to use mocks and make testing of View class more easy.

Tests:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;
using Rot13.Core.Library.Presentation;
using Rhino.Mocks.Constraints;

namespace Rot13.Core.Library.Tests.Presentation_Tests
{
  [TestClass]
  public class Rot13ProcessorViewTests
  {
    MockRepository _mocks = new MockRepository();

    [TestMethod]
    public void Smoke()
    {
      var console = _mocks.StrictMock<IConsole>();
      var view = Rot13ProcessorViewFactory.Create(console);

      Assert.IsNotNull(view);
    }

    [TestMethod]
    public void Display()
    {
      //INIT
      var console = _mocks.StrictMock<IConsole>();
      var view = Rot13ProcessorViewFactory.Create(console);
      var content = "some sort of string";

      // ACT/POST
      With.Mocks(_mocks).Expecting(
        delegate
        {
          Expect.Call(delegate { console.Write(string.Empty); }).Constraints(Is.Equal(content));
        }
      ).Verify(
        delegate
        {
          view.Display(content);
        }
      );
    }
  }
}

* This source code was highlighted with Source Code Highlighter.


Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Rot13.Core.Library.Presentation.Impl
{
  class Rot13ConsoleViewImpl : IRot13ProcessorView
  {
    private IConsole _console;

    public Rot13ConsoleViewImpl(IConsole console)
    {
      _console = console;
    }

    public void Display(string output)
    {
      _console.Write(output);
    }
  }
}

* This source code was highlighted with Source Code Highlighter.


Interface and default implementation of Console:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Rot13.Core.Library.Presentation
{
  public interface IConsole
  {
    void Write(string value);
  }

  public class Console : IConsole
  {
    public void Write(string value)
    {
      System.Console.Write(value);
    }
  }
}

* This source code was highlighted with Source Code Highlighter.


And.. Thats actually it! Everything is ready to be integrated. Let’s re-run all tests to make sure that everything is still as expected. So far we’ve got 23 tests and all are green.

Integration


Let’s create an Application class that will be responsible for application behavior.

Tests:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;

namespace Rot13.Core.Library.Tests
{
  [TestClass]
  public class ApplicationTests
  {
    [TestMethod]
    public void Smoke()
    {
      var app = new Application(new string[] { "in.txt", "out.txt" });

      Assert.IsNotNull(app);
    }

    [TestMethod]
    public void RunApplication()
    {
      //INIT
      var app = new Application(new string[] { "in.txt", "out.txt" });

      //ACT
      app.Run();

      //POST
      var outputFilename = "./output/out.txt";
      Assert.IsTrue(File.Exists(outputFilename));

      var expectedContent = "Yberz vcfhz qbybe fvg nzrg, pbafrpgrghe nqvcvfvpvat ryvg, frq qb rvhfzbq grzcbe vapvqvqhag hg ynober rg qbyber zntan nyvdhn.";
      var actualContent = File.ReadAllText(outputFilename);
      Assert.AreEqual(expectedContent, actualContent);
    }
  }
}

* This source code was highlighted with Source Code Highlighter.


Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Rot13.Core.Library.Presentation;
using Rot13.Core.Library.BLL;
using Rot13.Core.Library.DAL;

namespace Rot13.Core.Library
{
  public class Application
  {
    private string[] _arguments;

    public Application(string[] args)
    {
      _arguments = args;
    }

    public void Run()
    {
      //initialize configuration
      Configuration.Init();
      //initialize console
      var console = new Rot13.Core.Library.Presentation.Console();
      //initialize context
      var context = new Context(_arguments);
      //intialize business object
      var processor = new Rot13Processor(Rot13ProcessorViewFactory.Create(console), Rot13ProcessDataFactory.Create(context));
      //run..
      processor.Process();
    }
  }
}

* This source code was highlighted with Source Code Highlighter.


Console application



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Rot13.Core.Library;

namespace Rot13.Application
{
  class Program
  {
    static void Main(string[] args)
    {
      try
      {
        var app = new Rot13.Core.Library.Application(args);

        app.Run();
      }
      catch (Exception e)
      {
        Console.WriteLine(e.Message);
      }
    }
  }
}

* This source code was highlighted with Source Code Highlighter.


It has no sence to create a separate test cases on that class, since we’ve got preatty good coverage following TDD rules implementing core of application.

Conclusions



  • Follow path from business requirements to implementation
  • Keep implementation as simple as possible
  • Test everything, because TDD rules


Source code of application.

Updated


I’ve inited github repository, so sources are available here:

http://github.com/alexbeletsky/JamesShoreRot13