"模拟"单元测试中的命令行参数

时间:2016-04-14 14:48:10

标签: c# unit-testing

我有一些功能,这取决于命令行参数,不同的参数应该导致不同的结果。

我不能直接"模拟"这个参数,因为存在某种链依赖性 - 我需要对一些xaml控件进行单元测试,这取决于视图模型,它依赖于某些额外的类,它使用Environment.GetCommandLineArgs获取命令行参数,而我不能直接影响最后一个类来手动设置参数,而不是使用GetCommandLineArgs

所以,我想知道,有没有办法让Environment.GetCommandLineArgs返回值,我希望它返回,以进行某些单元测试。

4 个答案:

答案 0 :(得分:3)

你需要抽象Bar4或者最终在你可以模拟的东西之后调用它

Environment.GetCommandLineArgs

最终可以在具体的类中实现,如

public interface ICommandLineInterface {
    string[] GetCommandLineArgs();
}

可以使用public class CommandInterface : ICommandLineInterface { public string[] GetCommandLineArgs() { return Environment.GetCommandLineArgs(); } } Moq

进行测试
FluentAssertions

答案 1 :(得分:1)

由于您正在处理环境变量,为什么我们不将外部依赖项包装到一个EnvironmentHelper类中,然后注入依赖项?

这是我的建议:

public class EnvironmentHelper
{
    Func<string[]> getEnvironmentCommandLineArgs; 

       // other dependency injections can be placed here

       public EnvironmentHelper(Func<string[]> getEnvironmentCommandLineArgs)
       {
            this.getEnvironmentCommandLineArgs = getEnvironmentCommandLineArgs;
       }

       public string[] GetEnvironmentCommandLineArgs()
       {
            return getEnvironmentCommandLineArgs();
       }
}

这是Mock方法:

public static string[] GetFakeEnvironmentCommandLineArgs()
{
    return new string[] { "arg1", "arg2" };
}

在您的源代码中:

EnvironmentHelper envHelper = new EnvironmentHelper(Environment.GetCommandLineArgs);
string[] myArgs = envHelper.GetEnvironmentCommandLineArgs();

在您的单元测试代码中:

EnvironmentHelper envHelper = new EnvironmentHelper(GetFakeEnvironmentCommandLineArgs);
string[] myArgs = envHelper.GetEnvironmentCommandLineArgs();

答案 2 :(得分:0)

如果你想要一些可单元测试的东西,它应该依赖于一个至少和它的实现一样严格的抽象。

通常,您将通过类的构造函数或属性方法获取依赖项。通常,构造函数是首选,因为现在您的类的使用者在编译时知道需要哪些依赖项。

public void int Main(string[] args)
{
    // Validate the args are valid (not shown).

    var config = new AppConfig();
    config.Value1 = args[0];
    config.Value2 = int.Parse(args[1]);
    // etc....
}

public class MyService()
{
    private AppConfig _config;

    public MyService(AppConfig config)
    {
        this._config = config;
    }
}

我通常不会在接口后面放置一个配置对象,因为它只有数据 - 可以序列化。只要它没有方法,那么我就不需要用具有override - d行为的子类替换它。我也可以在我的测试中直接new

另外,当我想依赖于命令行参数本身对服务的抽象时,我从未遇到过这种情况 - 为什么它需要知道它在命令行后面?我得到的最接近的是使用PowerArgs来轻松解析,但我会在Main中使用该权限。我通常做的就是在命令行参数上读取Web服务器的端口号(我让应用程序的用户选择这样我可以在同一台机器上运行我的Web服务器的多个副本 - 可能不同版本左右我可以在调试时运行自动化测试,而不是冲突端口),直接在我的Main类中解析它们。然后在我的Web服务器中,我依赖于解析的命令行参数,在本例中为int。这样,配置来自命令行的事实是无关紧要的 - 如果我愿意,我可以稍后将其移动到App.config文件(这也基本上绑定到流程的生命周期) - 然后我就可以了将常用配置提取到configSource个文件。

不是依赖于命令行的抽象(如果保持纯粹,每个服务消耗都必须重新解析),我通常会将命令行和App.config依赖项抽象为强-typed对象 - 可能是app级配置类和测试级配置类,并根据需要引入多个配置对象 - (应用程序不一定会关心这一点,而E2E测试基础设施需要在单独的部分中App.config:我从哪里获取客户端静态文件,在哪里获取测试或开发人员环境中的构建脚本以自动生成/自动更新index.html文件等。)。 p>

答案 3 :(得分:0)

使用an array可以更轻松地完成此任务。 它允许不仅模拟接口,所以。看看:

[TestMethod, Isolated]
public void TestFakeArgs()
{
    //Arrange
    Isolate.WhenCalled(() => Environment.GetCommandLineArgs()).WillReturn(new[] { "Your", "Fake", "Args" });

    //Act
    string[] args = Environment.GetCommandLineArgs();

    //Assert
    Assert.AreEqual("Your", args[0]);
    Assert.AreEqual("Fake", args[0]);
    Assert.AreEqual("Args", args[0]);
}

模拟Environment.GetCommandLineArgs()只占用一行:

Isolate.WhenCalled(() => Environment.GetCommandLineArgs()).WillReturn(new[] { "Your", "Fake", "Args" });

您无需创建新接口并更改生产代码。

希望它有所帮助!