Click here to Skip to main content
15,880,392 members
Articles / Programming Languages / C#

From Untestable to Testable Console App

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
2 Mar 2012CPOL2 min read 13.6K   6  
How to make testable something that (at least me) usually left as untested

In this post, I will show how to make testable something that (at least me) usually left as untested. I’m talking about the preparing phase of a console app, the checking arguments error reporting and so on. That logic is usually so simple that any good cowboy programmer would probably leave outside any unit testing. Unfortunately, we should at least do some manual check that proves that logic is working, and doing things manually is always silly. In this post, we assume we have a working command line parsing library, and a mocking framework. Let's see the cowboy code:

C#
static void Main(string[] args)
        {
            string optA,optB;
            optA = optB = null;
            bool done = false;
            OptionSet set = new OptionSet();
            set.Add("a=", (k) => optA = k);
            set.Add("b=", (k) => optB = k);
            set.Add("h", (k) => { LongHelp(); done = true; });
            set.Parse(args);
            if (done)
                return;

            if (string.IsNullOrEmpty(optA) || string.IsNullOrEmpty(optB))
            {
                ShortHelp();
                return;
            }

            DoTheJob(optA,optB);
        }

        private static void DoTheJob(string optA, string optB)
        {
            //something interesting here
        }

        private static void LongHelp()
        {
            Console.Error.WriteLine("Long help here...");
        }

        private static void ShortHelp()
        {
            Console.Error.WriteLine("Short help here");
        }
    }

So nothing special, the example is actually very simple. We have two mandatory parameters, a command line switch to print a long help. If one argument is missing, a short help line must be presented. If all the parameters are provided, the DoTheJob() method should be called with the correct values.

Current code is not testable without hosting the console application as a process, and looking at the stdout to see what happen. Even by this strategy, we cannot punctually check what is passed to DoTheJob. So we want to refactor the code, without adding any complexity to the app. So here below is the proposed refactoring:

C#
public class Program
    {
        static void Main(string[] args)
        {
              new Program().Run(args);
        }
        public virtual void Run(string[] args)
        {
            string optA, optB;
            optA = optB = null;
            bool done = false;
            OptionSet set = new OptionSet();
            set.Add("a=", (k) => optA = k);
            set.Add("b=", (k) => optB = k);
            set.Add("h", (k) => { LongHelp(); done = true; });
            set.Parse(args);

            if (done)
                return;

            if (string.IsNullOrEmpty(optA) || string.IsNullOrEmpty(optB))
            {
               ShortHelp();
                return;
            }

            DoTheJob(optA, optB);
        }

        public virtual void DoTheJob(string optA, string optB)
        {
            //something interesting here
        }

        public virtual void LongHelp()
        {
            Console.Error.WriteLine("Long help here...");
        }

        public virtual void ShortHelp()
        {
            Console.Error.WriteLine("Short help here");
        }
    }

So pretty easy, we provide a non static method Run(), and all the internal functions are declared virtual. This is a five minutes modification we could probably apply to any other code like this we have. The difference is that we can write some unit test, let's see how:

C#
[TestMethod]
public void ShouldDisplayShortHelp()
{
    var moq = new Mock<Program>();
    moq.CallBase = true;
    moq.Setup(k=>k.DoTheJob(It.IsAny<string>(),It.IsAny<string>()))
        .Throws(new InvalidProgramException("Should not call"));
    moq.Object.Run(new string[0]);
    moq.Verify(k => k.ShortHelp());
}
[TestMethod]
public void ShouldDisplayLongHelp()
{
    var moq = new Mock<Program>();
    moq.CallBase = true;
    moq.Setup(k => k.DoTheJob(It.IsAny<string>(), It.IsAny<string>()))
        .Throws(new InvalidProgramException("Should not call"));
    moq.Object.Run(new string[]{"-h"});
    moq.Verify(k => k.LongHelp());
}
[TestMethod]
public void ShouldInvokeWithProperParameters()
{
    var moq = new Mock<Program>();
    moq.CallBase = true;
    moq.Setup(k => k.DoTheJob("p1", "p2")).Verifiable();
    moq.Object.Run(new string[] { "-a=p1","-b=p2" });
    moq.Verify();
}

I used the MoQ library, please note the Callbase set to true, because we are using the same object for driving and for expect calls. So in conclusion, we achieve a real unit test of something we sometimes left apart, we did that in memory, and even if the example is really trivial, the concept can be used in complex scenarios too. What about testing the inside part of DoTheJob()? Well, if a good testing strategy is used, the internal part should be testable outside somewhere else, here we are proving we can test the shell.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Italy Italy
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --