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:
- Write the ROT-13 tool described above. Note that the input file must be loaded entirely into memory.
- Provide a configuration mechanism that reads and writes files from one directory when “in testing” and another directory when “in production.”
- 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.
- 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.
- 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
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:
I would not say that I’m using some special technics or patterns, just following most simple implementation and tests.
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:
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:
Rot13 (preatty small, heh?):
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.
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):
Corresponding implementation:
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:
Code:
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:
Code:
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:
Code:
Interface and default implementation of Console:
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.
Let’s create an Application class that will be responsible for application behavior.
Tests:
Code:
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.
Source code of application.
I’ve inited github repository, so sources are available here:
http://github.com/alexbeletsky/JamesShoreRot13
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:
- Write the ROT-13 tool described above. Note that the input file must be loaded entirely into memory.
- Provide a configuration mechanism that reads and writes files from one directory when “in testing” and another directory when “in production.”
- 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.
- 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.
- 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