如何提高命令解析器的可测试性(在C#控制台应用程序中)?

时间:2017-08-10 03:49:56

标签: c# unit-testing dependency-injection windows-console

我有一个程序使用控制台作为C#.NET中的GUI来解析来自用户的命令。它有不同的命令 - 其中一些必须是完全匹配,如"外观","库存"或"帮助"。其他人只需要包含部分单词或短语 - 任何带有" north"或"东"将开始向世界发展这一方向。

例如:

if(command == "help")
  { << Console.Writeline code to print the help >> }
else if (command.Contains == "inv")
  {  << code using Console.Writeline to print the inventory >> )
else if (command.Contains("north"))
  { << code to move north, then print location info with Console.Writeline >>)
<< etc. >>

由于它是一个控制台应用程序,因此许多操作代码将作为输出写入控制台。我试图弄清楚如何对它进行单元测试,而我的(不可否认的是初学者)认为我应该删除对控制台的依赖并使用依赖注入来传递控制台(或者可能是通用接口到文本流或类似的东西?)到这个解析代码,以便我可以伪造控制台,但我不知道该怎么做。

问题

依赖注入是否是在这里继续进行的正确方法 - 如果是,那么实现它的正确途径是什么?

3 个答案:

答案 0 :(得分:2)

考虑testing pyramid你应该:

集成测试

从使用脚本语言进行测试的集成测试盒测试开始,那些很少,应该是探索性的,或涉及涉及标准输入/输出的极端情况。

$process = New-Object System.Diagnostics.Process
$process.StartInfo.FileName = ".\mud.exe"
$process.StartInfo.UseShellExecute = $false
$process.StartInfo.RedirectStandardOutput = $true
$process.StartInfo.RedirectStandardInput = $true
if ( $process.Start() ) {
    # input
    $process.StandardInput.WriteLine("help");
    $process.StandardInput.WriteLine();
    # output check
    $output = $process.StandardOutput.ReadToEnd()
    if ( $output ) {
        if ( $output.Contains("this is a help") ) {
            Write "pass"
        }
        else {
            Write-Error $output
        }
    }
    $process.WaitForExit()
}

输入

使用fluent command line parser

等库来快速安排输入验证
[Flags]
enum Commands
{
    Help = 1,
    Inv = 2,
    North = 4
}
var p = new FluentCommandLineParser();
p.Setup<Commands>("c")
 .Callback(c => command= c);

输出

注入并输出您的输出,这样您就可以进行大量测试而无需太多模拟。
这意味着所有控制台编写都将由一个模块处理,您可以通过测试套件轻松伪造。

IConsoleBuilder { // actual implementation write to console
    RegisterCommand(string command, Func<string[], string> action); 
}

InventoryConsoleBuilder : ConsoleBuilderClient { 
    InventoryConsoleBuilder(IConsoleWriter writer){ _writer = writer; } 

    public override void Show(IInventory inventory) { 
        writer.RegisterCommand(inventoryComposed) ; 
    }
}

答案 1 :(得分:1)

  

由于它是一个控制台应用程序,因此许多操作代码将作为输出写入控制台。我正试图弄清楚如何对此进行单元测试

一种方法是使用 TraceListener 并将所有内容记录到文件而不是控制台。通常,我们使用TextWriterTraceListenerTraceDebug输出记录到文件中。

[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[TestClass]
public class AssemblyInitUnitTest
{
    static FileStream objStream;

    [AssemblyInitialize()]
    public static void Setup(TestContext testContext)
    {
        objStream = new FileStream(AppDomain.CurrentDomain.BaseDirectory + "\\AAA_UnitTestPerfMonitor.txt", FileMode.OpenOrCreate);
        TextWriterTraceListener objTraceListener = new TextWriterTraceListener(objStream);
        Trace.Listeners.Add(objTraceListener);
        Trace.WriteLine("===================================");
        Trace.WriteLine("App Start:" + DateTime.Now);
        Trace.WriteLine("===================================");    
    }

    [AssemblyCleanup]
    public static void TearDown()
    {
        Trace.Flush();
        objStream.Close();
    }
}

我们可以为控制台执行相同的操作,将其连接到[AssemblyInitialize()],如下所示:

ConsoleTraceListener ctl = new ConsoleTraceListener(false);
ctl.TraceOutputOptions = TraceOptions.DateTime;
Trace.Listeners.Add(ctl);

然后你可以阅读文件并Assert实际结果等于预期结果。

string[] fileLines = System.IO.File.ReadAllLines(AppDomain.CurrentDomain.BaseDirectory + "\\AAA_UnitTestPerfMonitor.txt");
Assert.IsTrue(fileLines[0] == "<< Console.Writeline code to print the help >> ");

可能还有其他方法。所以请稍等一下,看看是否还有其他人回答。

答案 2 :(得分:1)

在这里,您不必在一个地方写下您的逻辑。您可以利用命令模式。您将需要一个代表您对象状态的类。我们将其称为CustomObject。

public class CustomObject
{
    //properties that represent the state, direction, inventory, etc.
    public string Direction{get;set;}//etc.
}

public interface ICommand
{
    string Execute(CustomObject obj);
}

public class InventoryCommand: ICommand
{
    public string Execute(CustomObject obj)
    {
        //code to create the inventory string from CustomObject
        return "Inventory String";
    }
}

public class NorthCommand: ICommand
{
    public string Execute(CustomObject obj)
    {
        //code to move the object to north
        return "Command Information";
    }
}


//In your test cases, you can do

CustomObject obj = new CustomObject();
//test for inventory command
var expectedOutput = "Expected Output";
var result = (new InventoryCommand()).Execute(obj);
Assert.Equal(result, expectedOuput);

//In your console program
if(command == "help")
{  
    Console.Writeline((new HelpCommand()).Execute(obj)); 
}
else if (command.Contains == "inv")
{  
    Console.Writeline((new InventoryCommand()).Execute(obj)); 
)

您可以根据您拥有的不同命令系列进一​​步隔离命令界面。