65.9K
CodeProject 正在变化。 阅读更多。
Home

如何测试控制台应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (7投票s)

2007年2月15日

CPOL

3分钟阅读

viewsIcon

101096

downloadIcon

619

本文演示了一种自动化测试控制台应用程序的非常简单的方法。

引言

所有 UI 工具通常都有方便的命令行来执行自动化。当您处理一个小型应用程序时,您通常根本不需要用户界面。
自动化测试成本很高,但当涉及到控制台应用程序时,有一种方法可以避免在实现自动化上花费大量时间和精力。欢迎来到控制台测试的世界。

定义

Solution、project 和 assembly 在 Visual Studio/.NET 中的含义。

  • 被测程序集/项目 – 被测试程序集引用的项目,需要进行单元测试
  • 测试程序集/项目 – 执行被测程序单元测试的项目

Visual Studio 测试通用建议

  • 将所有输出保留在同一个文件夹中。如果您有两个可执行项目,它们明显不适合放在一起,那么可能是时候拆分解决方案了。
  • 如果您有测试数据,请将其保留在测试程序集的资源中,或放在一个单独的文件中并复制到输出文件夹。
  • 如果您有一个单独的文件,那么复制它的明显方法是使用后置构建事件。例如:

    xcopy "$(SolutionDir)TestData\SampleExcelTable.xls" $(SolutionDir)bin\Debug /r /y 

Using the Code

所以您所需要的只是 Visual Studio Team Edition 或 NUnit 中的常规测试夹。
我通常将这些夹命名为 ProgramTests,并将它们保留在与项目中其他单元测试相同的测试项目中。

有一些类成员被使用

  • TextWriter m_normalOutput; 用于替换控制台输出

另外两个用于累积应用程序执行期间检索到的所有消息是:

  • StringWriter m_testingConsole;
  • StringBuilder m_testingSB;

用于替换输出和截取错误代码的标准测试夹方法如下:

  • TestFixtureSetUp – 设置工作文件夹并将输出替换为模拟流。模拟流是普通的字符串构建器,可用于分析输出。例如,如果您知道您的应用程序应该输出消息“in progress”,您可以在执行后进行检查。
  • TestFixtureTearDown – 在处理完整个测试夹后恢复正常输出。
  • SetUp – 在运行每个测试之前清除字符串构建器。
  • TearDown – 将字符串构建器中缓存的内容打印到正常控制台。这不是必须的,但在开发中经常需要。
namespace MyConsoleApp.Tests
{
    /// <summary>
    /// Testing application BuildAdvantage.Console.exe
    /// </summary>
    [TestFixture]
    public class ProgramTests
    {

        TextWriter m_normalOutput;
        StringWriter m_testingConsole;
        StringBuilder m_testingSB;

        [TestFixtureSetUp]
        public void TestFixtureSetUp()
        {
            // Set current folder to testing folder
            string assemblyCodeBase = 
                System.Reflection.Assembly.GetExecutingAssembly().CodeBase;

            // Get directory name
            string dirName = Path.GetDirectoryName(assemblyCodeBase);

            // remove URL-prefix if it exists
            if (dirName.StartsWith("file:\\"))
                dirName = dirName.Substring(6);

            // set current folder
            Environment.CurrentDirectory = dirName;

            // Initialize string builder to replace console
            m_testingSB = new StringBuilder();
            m_testingConsole = new StringWriter(m_testingSB);

            // swap normal output console with testing console - to reuse 
            // it later
            m_normalOutput = System.Console.Out;
            System.Console.SetOut(m_testingConsole);
        }

        [TestFixtureTearDown]
        public void TestFixtureTearDown()
        {
            // set normal output stream to the console
            System.Console.SetOut(m_normalOutput);
        }

        [SetUp]
        public void SetUp()
        {
            // clear string builder
            m_testingSB.Remove(0, m_testingSB.Length);
        }

        [TearDown]
        public void TearDown()
        {
            // Verbose output in console
            m_normalOutput.Write(m_testingSB.ToString());
        }

Private 方法 StartConsoleApplication 用于在“单元测试”中启动进程。它会等待正在运行的控制台被测应用程序返回被测应用程序的错误代码。所有输出都保存在本地成员 testingSB 中。

/// <summary>
/// Starts the console application.
/// </summary>
/// <param name="arguments">The arguments for console application. 
/// Specify empty string to run with no arguments
/// <returns>exit code</returns>
private int StartConsoleApplication(string arguments)
{
    // Initialize process here
    Process proc = new Process();
    proc.StartInfo.FileName = "MyConsoleApp.exe";
    // add arguments as whole string
    proc.StartInfo.Arguments = arguments;

    // use it to start from testing environment
    proc.StartInfo.UseShellExecute = false;

    // redirect outputs to have it in testing console
    proc.StartInfo.RedirectStandardOutput = true;
    proc.StartInfo.RedirectStandardError = true;

    // set working directory
    proc.StartInfo.WorkingDirectory = Environment.CurrentDirectory;
    
    // start and wait for exit
    proc.Start();
    proc.WaitForExit();
    
    // get output to testing console.
    System.Console.WriteLine(proc.StandardOutput.ReadToEnd());
    System.Console.Write(proc.StandardError.ReadToEnd());

    // return exit code
    return proc.ExitCode;
}

最简单的测试示例如下:

[Test]
public void ShowCmdHelpIfNoArguments()
{
    // Check exit is normal
    Assert.AreEqual(0, StartConsoleApplication(""));

    // Check that help information shown correctly.
    Assert.IsTrue(m_testingSB.ToString().Contains(
    Program.COMMAND_LINE_HELP));
}

第一行检查应用程序是否正确执行并返回零错误代码。后者检查控制台输出是否包含命令行帮助。这是任何纯控制台应用程序的不变行为。空引号表示没有参数,否则您可以像在普通命令提示符中一样放置参数:StartConsoleApplication("argument /switcher options")。

警告:以空格分隔的参数将被识别为不同的参数,引号本身(")将被忽略。但当前的框架可以轻松扩展以接受多个单词的参数。

负面测试可以这样进行:在此框架中,您可以通过使用 m_testingSB.ToString().Contains() 构造来检查输出消息是否正确。

[Test]
public void StartWithNonExistingPath()
{
    // Throw file not found. Exit code is 1
    Assert.AreEqual(1, StartConsoleApplication(
        "NonExistingMetricsTable.xls"));

    Assert.IsTrue(m_testingSB.ToString().Contains("File not found"));

    // Error code in console
    Assert.IsTrue(m_testingSB.ToString().Contains("11")); 
 
    // File name
    Assert.IsTrue(m_testingSB.ToString().Contains("NonExistingMetricsTable.xls")); 
}

最终注释

请注意以下事项:

  • 您无需引用被测项目即可执行此类测试:您只需使用命令行运行可执行文件。
  • 这对于 .NET 1.1 和 .NET 2.0 都适用(.NET 1.1 的唯一缺点是:您无法引用被测的可执行程序集来获取其资源,例如 Program.COMMAND_LINE_HELP)。
  • 您可以使用本文附带的代码片段。
  • ProgramTest 相当慢,肯定会破坏您的单元测试时间统计数据,但您仍然可以将其与其他单元测试分开。
  • 最好遵循与单元测试相同的建议:使程序测试独立于环境、相对路径、运行顺序等。
  • 这样的程序测试可以轻松地集成到持续集成中。
  • 该项目的未来发展计划是实现测试超时,以防止整个构建过程停滞。

历史

  • 2007 年 2 月 15 日:初始发布
© . All rights reserved.