Refactor and Additional tests
To enable better testing without the need to actually run the application I am refactoring the application. This refactor will allow the command line arguments to be parsed and verify the results.
The building of the rootCommand and options is moved to an AddOptions(string[])
method. This performs the same steps as previously with the exception of indicating the Invoke method. Also it returns the RootCommand object for use in whatever context called the method.
# Program.cs
public static int Main(string[] args)
{
var rootCommand = AddOptions(args);
rootCommand.Handler = CommandHandler.Create<string>(RootCmd);
return rootCommand.Invoke(args);
}
public static RootCommand AddOptions(string[] args)
{
var rootCommand = new RootCommand()
{
Description = "Console app to demonstrate System.CommandLine"
};
var optName = new Option<string>("--name", description: "Name of person to greet") { IsRequired = true };
optName.AddAlias("-n");
rootCommand.Add(optName);
return rootCommand;
}
To leverage the refactor of Program.cs
we need to modify the tests. This will bring us a little closer to the spirit of Unit testing as well since we are not actually running the application.
The test is modified to call the AddOptions(string[])
method instead of Main
. The Parse(string[])
method is then called to parse the options without invoking a handler. Once the parsing is complete the ParseResult
is inspected to determine if there were any errors.
public void NoArgs_ReturnsFail()
{
string[] args = new string[] { };
RootCommand rootCommand = Program.AddOptions(args);
var result = rootCommand.Parse(args);
IReadOnlyCollection<ParseError> Errors = result.Errors;
Assert.Equal(1, Errors.Count);
}
Add the following additional tests into the UnitTest1 class.
# UnitTest1.cs
[Fact]
public void OptionNoArgument_ReturnsFail()
{
string[] args = new string[] { "--name" };
RootCommand rootCommand = Program.AddOptions(args);
var result = rootCommand.Parse(args);
IReadOnlyCollection<ParseError> Errors = result.Errors;
Assert.Equal(1, Errors.Count);
}
[Fact]
public void OptionWithArgument_ReturnsFail()
{
string[] args = new string[] { "--name", "Success" };
RootCommand rootCommand = Program.AddOptions(args);
var result = rootCommand.Parse(args);
IReadOnlyCollection<ParseError> Errors = result.Errors;
Assert.Equal(0, Errors.Count);
}
The first test is confirming that if you provide the --name
parameter but do not provide an argument to it that it will fail.
The second test confirms that if you provide an argument to the --name
parameter that it will pass successfully.
If we run our tests we now see that we have a total of three tests being executed.
Passed! - Failed: 0, Passed: 3, Skipped: 0, Total: 3, Duration: 38 ms - /Users/iesoftwaredeveloper/repos/SimpleCmdLine/src/SimpleCmdLine.Tests/bin/Debug/net5.0/SimpleCmdLine.Tests.dll (net5.0)
Adding an alias
A brief history as made up by me
A long time ago in a world without internet typing characters was a premium.1 To account for this premium most applications would use command line options that had a single characters. A good example of this is the "verbose" option. Tradditionally this was indicated with a -v
on the command line.
As time went by there were situations where two options had the same first character. One way around this was to just use another letter for the option. An example of this might be the option -a
. This might represent the intent to "append". Or it might represent the option for "add" or "all". Using -p
, -d
or -l
to represent an alternative just doesn't seem like a choice that makes sense to someone that isn't familiar with the options.
As resources became "cheaper" and more plentiful the practice of expanding the characters used for an option became more common. Instead of use the -v
for "verbose" the entire word might be used. The -v
would now be represented by --verbose
. The use of two dashes2 are used to indicate that the option is a full word and not a single character.
It was also common practice to use aliases when a full word could be used or the single letter could be used to indicate the same parameter. In the case of a "verbose" option it is possible to use either the -v
syntax or the --verbose
syntax to indicate the option.
Back to adding an alias
Before we add the alias write a test that will test that the alias works.
[Fact]
public void OptionWithArgumentAlias_ReturnsFail()
{
string[] args = new string[] { "-n", "Success" };
RootCommand rootCommand = Program.AddOptions(args);
var result = rootCommand.Parse(args);
IReadOnlyCollection<ParseError> Errors = result.Errors;
Assert.Equal(0, Errors.Count);
}
Adding an alias is relatively simple. The first step is to define the option. After the option has been created you use the AddAlias()
method.
var optName = new Option<string>("--name", description: "Name of person to greet") { IsRequired=true};
optName.AddAlias("-n");
rootCommand.Add(optName);
While it seems to make the most sense to add the alias before you add it to the root command it is not necessary. Adding the alias after adding it to the root command will still result in the same result.
var optName = new Option<string>("--name", description: "Name of person to greet") { IsRequired=true};
rootCommand.Add(optName);
optName.AddAlias("-n");
You must be logged in to see the comments. Log in now!