C# 如何为运行时程序集位置编写测试用例

C# 如何为运行时程序集位置编写测试用例,c#,unit-testing,C#,Unit Testing,下面是我创建新应用程序实例的类: public interface IApplicationInstanceProvider { bool CreateNewProcess(); } public class ApplicationInstanceProvider : IApplicationInstanceProvider { public bool CreateNewProcess() { Process p = new Process();

下面是我创建新应用程序实例的类:

public interface IApplicationInstanceProvider
{
    bool CreateNewProcess();
}

public class ApplicationInstanceProvider : IApplicationInstanceProvider
{
    public bool CreateNewProcess()
    {
        Process p = new Process();
        p.StartInfo.FileName = System.Reflection.Assembly.GetEntryAssembly().Location;
        return p.Start();
    }
}
以下是我的测试用例:

[TestMethod]
public void TestMethodForAppInstance()
{
    IApplicationInstanceProvider provider = new ApplicationInstanceProvider();
    bool isCreated = provider.CreateNewProcess();

    Assert.AreEqual(isCreated,true);
}
问题是:
System.Reflection.Assembly.GetEntryAssembly()
在执行测试用例时为空。但它在应用程序运行时运行良好。
请帮忙

无法按原样测试类,因为您正在测试环境中执行未知的新进程。这是不可行的,因为:

  • Assembly.GetEntryAssembly()
    将返回
    null
  • 不管它返回什么,你都会执行这个过程,比如说,
    devenv.exe
    chrome.exe
    或者其他什么
  • CreateNewProcess()
    方法做了很多事情:它确定要执行的程序路径并运行它。此外,它的返回值告诉调用者是否启动了一个新的进程,或者是否重用了一个现有进程。一个方法有太多的东西,这使得它很难测试。幸运的是,至少有两种方法可以使代码可测试:创建一个专门的
    ApplicationInstanceProvider
    类进行测试,或者为其创建一个单独的类

    让我们看看第一种方法:

    public class ApplicationInstanceProvider : IApplicationInstanceProvider {
        public bool CreateNewProcess() {
            Process process = new Process();
            process.StartInfo.FileName = ResolveApplicationPath();
            return process.Start();
        }
    
        protected virtual string ResolveApplicationPath() {
            return System.Reflection.Assembly.GetEntryAssembly().Location;
        }
    }
    
    您将创建一个用于测试的派生类:

    sealed class TestApplicationInstanceProvider : ApplicationInstanceProvider {
        protected override string ResolveApplicationPath() {
            // path to your assembly or a well-known executable executable
            // Like %WINDIR%\notepad.exe
            return "...";
        }
    }
    
    sealed class TestAssemblyResolver : IAssemblyResolver {
        public string GetEntryAssemblyPath() {
            // Return path of a well-known test application,
            // for example an "empty" console application. You can also
            // reuse it to, for example, return different error codes
            return Assembly.Load(...);
        }
    }
    
    然后在您的测试方法中使用如下方法:

    [TestMethod]
    public void TestMethodForAppInstance() {
        var provider = new TestApplicationInstanceProvider();
        bool isCreated = provider.CreateNewProcess();
    
        Assert.AreEqual(isCreated, true);
    }
    
    [TestMethod]
    public void TestMethodForAppInstance() {
        var resolver = new TestAssemblyResolver();
        var provider = new ApplicationInstanceProvider(resolver);
        bool isCreated = provider.CreateNewProcess();
    
        Assert.AreEqual(isCreated, true);
    }
    
    请注意,无法测试
    Assembly.GetEntryAssembly()
    ,但可以测试所有内容。请注意,现在您正在测试是否创建了一个新流程实例,但没有检查是否正确启动了一个实例;这将增加代码覆盖率,但实际上您几乎没有进行任何测试,因为对于可执行文件,
    Process.Start()
    将始终返回true(运行的进程可能会被文档重用)。这就是为什么您必须拆分
    CreateNewProcess()
    责任(不仅为了清晰,而且为了测试)。测试后不要忘记关闭清理方法中的流程实例

    让我们看看第二种方法:第二种方法稍微复杂一点,但更通用:

    public interface IAssemblyResolver {
        string GetEntryAssemblyPath();
    }
    
    public sealed class DefaultAssemblyResolver : IAssemblyResolver {
        public string GetEntryAssemblyPath() {
            return System.Reflection.Assembly.GetEntryAssembly().Location;
        }
    }
    
    public class ApplicationInstanceProvider : IApplicationInstanceProvider {
        public ApplicationInstanceProvider(IAssemblyResolver resolver) {
            _resolver = resolver;
        }
    
        public bool CreateNewProcess() {
            Process process = new Process();
            process.StartInfo.FileName = _resolver.GetEntryAssemblyPath();
            return process.Start();
        }
    
        private readonly IAssemblyResolver _resolver;
    }
    
    现在您必须为测试创建一个模拟:

    sealed class TestApplicationInstanceProvider : ApplicationInstanceProvider {
        protected override string ResolveApplicationPath() {
            // path to your assembly or a well-known executable executable
            // Like %WINDIR%\notepad.exe
            return "...";
        }
    }
    
    sealed class TestAssemblyResolver : IAssemblyResolver {
        public string GetEntryAssemblyPath() {
            // Return path of a well-known test application,
            // for example an "empty" console application. You can also
            // reuse it to, for example, return different error codes
            return Assembly.Load(...);
        }
    }
    
    试验方法:

    [TestMethod]
    public void TestMethodForAppInstance() {
        var provider = new TestApplicationInstanceProvider();
        bool isCreated = provider.CreateNewProcess();
    
        Assert.AreEqual(isCreated, true);
    }
    
    [TestMethod]
    public void TestMethodForAppInstance() {
        var resolver = new TestAssemblyResolver();
        var provider = new ApplicationInstanceProvider(resolver);
        bool isCreated = provider.CreateNewProcess();
    
        Assert.AreEqual(isCreated, true);
    }
    
    你的假申请可能是什么样子的

    static class Program {
        static int Main(string[] args) {
            if (args.Length == 0)
                return 0;
    
            return Int32.Parse(args[0]);
        }
    }
    

    这取决于你想测试什么。您可以将ApplicationInstanceProvider中的所有共享代码抽象到一个基类中,并创建一个用于测试的模拟,其中您创建了一个已知可执行文件的实例…@AdrianoRepetti我想测试
    CreateNewProcess()
    返回true或not..我不确定这是什么意思。@AnkushMadankar不清楚您在评论中的意思。你的类运行一个新的entry assembly实例,显然它对于测试是不可行的(因为你的应用程序没有执行代码,你甚至不知道谁是你的主机),那么你应该做的是重构(是的!)。首先将启动新流程的代码提取到单独的方法中,并引入基类。然后创建一个派生类,在该类中使用entry assembly解析程序路径。它是不可测试的,然后引入一个新的派生类(仅用于测试目的)来执行(然后关闭!!!)notepad.exe。显然,我的意思是不能测试整个方法。然而,它做了两件事(我不是建议你做一个完整的人,但如果你想做单元测试,那么你必须做):1)执行一个进程,2)确定程序路径。现在1)可以确定测试,而2)你可能无法。。。