Introduction to TestApi – Part 2: Command-Line Parsing APIs

Series Index

+++

Command-line parsers remind me of linked lists in C++: everybody has written several at various points in their careers. While everybody should write each of those at least once, I doubt that many people out there are particularly excited about writing and re-writing fundamental data structures on a regular basis – it gets old very quickly. Not to mention that doing so is error-prone and decreases the maintainability of a world that’s already hard to maintain.

That’s why modern-day frameworks such as .NET provide standard implementations of the common data structures. And that’s why TestApi provides a reusable command-line parsing APIs via the CommandLineDictionary and CommandLineParser classes, the latter being a type-safe layer on top of the former. Obviously, these are not test APIs per se – they are general utility APIs that happen to be more often used when writing tests.

A few quick examples follow.

Simple Command-Line Parsing

As seen from the first example below, extracting command-line parameters that are primitives is easy. Primitive command-line parameters are either boolean (e.g. the “verbose” flag below), or a key-value pair, that one can extract with the indexer of the CommandLineDictionary instance (see the “testId” key below), just as one would expect from a Dictionary.

 // 
// EXAMPLE #1: Parsing a command-line such as "RunTests.exe /verbose /testId=123"
// 

using System;
using System.Linq;
using Microsoft.Test.CommandLineParsing;

public class Program 
{ 
    public static void Main(string[] args) 
    { 
        CommandLineDictionary d = new CommandLineDictionary(args);

        bool verbose = d.ContainsKey("verbose");
        int testId = Int32.Parse(d["testId"]);
                
        // use the parsed command-line parameters
    }
}

By default flags/keys are indicated with the forward slash (“/”) character and values are indicated with the equals character (“=”), but the user can override that upon initialization of of the CommandLineDictionary object:

 // 
// EXAMPLE #1b: Parsing a command-line such as "RunTests.exe –verbose –testId:123"
// 

...
        CommandLineDictionary d = new CommandLineDictionary(args, '-', ':');
...

Finally, you one can use the ToString method to get a string representation of the command-line arguments.

 

Command-Line Argument Structures

Another common pattern when dealing with command-line arguments is populating a structure which contains all parsed arguments. The CommandLineParser class makes this easy:

 // EXAMPLE #2:
// Sample for parsing the following command-line:
// Test.exe /verbose /runId=10
// This sample declares a class in which the strongly typed arguments are populated
class CommandLineArguments
{
   public bool? Verbose { get; set; }
   public int? RunId { get; set; }
}

...

CommandLineArguments a = new CommandLineArguments();
CommandLineParser.ParseArguments(a, args);

...

 

Type-Safe Commands

A third common approach is forming strongly-typed commands from the command-line parameters. This is common for cases when the command-line looks as follows:

some-exe  COMMAND  parameters-to-the-command

The parsing in this case is a little bit more involved:

  1. Create one class for every supported command, which derives from the Command abstract base class and implements an expected Execute method.

  2. Filter out the COMMAND part of the command-line arguments (note that Skip method used below is an extension method brought in by LINQ, so you’ll need to use System.Linq as shown above). Create an instance of the appropriate Command-derived class, based on the passed COMMAND argument.

  3. Pass the created Command-derived object to along with the rest of the command-line arguments to  CommandLineParser.ParseArguments – the method will initialize appropriately the  strongly-typed Command instance based on the the rest of the command-line.

  4. Execute the initialized Command instance.

 // EXAMPLE #3:
// Sample for parsing the following command-line:
// Test.exe run /runId=10 /verbose 
// In this particular case we have an actual command on the command-line (“run”), 
// which we want to effectively de-serialize and execute.
public class RunCommand : Command
{
   public bool? Verbose { get; set; }
   public int? RunId { get; set; }

   public override void Execute()
   {
      // Implement your "run" execution logic here.
   }
}

public class Program
{
   public static void Main(string[] args)
   {
      if (String.Compare(args[0], "run", StringComparison.InvariantCultureIgnoreCase) == 0)
      {
         Command c = new RunCommand();
         c.ParseArguments(args.Skip(1)); 
         // or CommandLineParser.ParseArguments(c, args.Skip(1))
         c.Execute();
      }
   }
}

Besides the parsing logic, CommandLineParser provides a few additional helper methods. One of them is CommandLineParser.PrintCommandUsage, which prints the usage for specific commands (or all supported commands) to the console.

 

In Conclusion

The command-line parsing APIs released with TestApi provide a simple and layered access to the command-line. Strictly speaking these APIs are not test APIs, but have nevertheless been included in TestApi as tests often have a need of parsing parameters on the command-line.