假调用命名空间来测试反射

时间:2015-06-11 17:21:12

标签: c# unit-testing nunit system.reflection

我有一段代码从调用程序集中获取命名空间的特定部分。现在我想对这段代码进行单元测试。 有没有办法使用NUnit伪造调用命名空间的名称而不在该特定命名空间中实现NUnit测试用例?

以下是我要测试的方法:

public static string FindCallingNameSpace()
{
   var stackTrace = new StackTrace();
   var stackFrames = stackTrace.GetFrames();
   if (stackFrames != null)
   {
       int nrFrames = stackFrames.Length;
       for (int i = 0; i < nrFrames; i++)
       {
           var methodBase = stackTrace.GetFrame(i).GetMethod();
           var Class = methodBase.ReflectedType;
           if (Class != null && Class.Namespace != null && Class.Namespace != "Foo.Common.WebService")
           {
               var Namespace = Class.Namespace.Split('.'); 
               return Namespace[1];
           }
       }
    }
    throw new Exception("Can't determine calling namespace! Need this to determine correct api url to call!");
}

一个例子是: Bar.ExampleNs.SomeMethod()调用Foo.Common.WebService.CallApi(),它自己调用上面的方法从SomeMethod()检索命名空间。结果将是&#34; ExampleNs&#34;。

现在可以创建一个在命名空间MyUnitTests.ApiTest.TestNameSpace()中编码的NUnit UnitTest,但在Foo.Common.WebService内,呼叫似乎来自Bar.ExampleNs.SomeMethod(),因此我可以测试&#34; ExampleNs&#34;

1 个答案:

答案 0 :(得分:0)

到目前为止,我认为实现您所需要的最简单方法是创建一个呼叫转发器并通过转发器调用FindCallingNamespace方法。因此,假设FindCallingNamespace方法位于类CallerStuff中,您可以创建:

namespace SomeNameSpace.ToTest {
    static class RemoteCaller {
        static public string Run() {
            return CallerStuff.FindCallingNameSpace();
        }
    }
}

然后在测试中,您拨打RemoteCaller.Run,而不是CallerStuff.FindCallingNamespace

但是,你提到了参数化测试,所以你可能最终会得到一些你想要测试的不同命名空间,这意味着更多远程调用者在不同的命名空间中,这让我觉得可能有更通用的方法。

下面的代码,实际上是通过动态编译它们然后调用它们来为你创建这些包装类。

class CodeMaker {
    static string _codesectionOne = @"
        using Foo.Common.WebService;
        namespace ";
    static string _codesectionTwo = @" {
            class RemoteCaller {
                static public string Run() {
                    return CallerStuff.FindCallingNameSpace();
                }
            }
        }";

    public static string CompileAndCall(string targetNamespace, 
                                        string referenceAssembly) {
        CompilerParameters CompilerParams = new CompilerParameters();
        string outputDirectory = Directory.GetCurrentDirectory();

        CompilerParams.GenerateInMemory = true;
        CompilerParams.TreatWarningsAsErrors = false;
        CompilerParams.GenerateExecutable = false;
        CompilerParams.CompilerOptions = "/optimize";

        string[] references = { "System.dll", referenceAssembly};
        CompilerParams.ReferencedAssemblies.AddRange(references);

        CSharpCodeProvider provider = new CSharpCodeProvider();
        var codeToCompile = _codesectionOne + targetNamespace + _codesectionTwo;

        CompilerResults compile = provider.CompileAssemblyFromSource(CompilerParams, 
                                                                     codeToCompile);

        if (compile.Errors.HasErrors) {
            string text = "Compile error: ";
            foreach (CompilerError ce in compile.Errors) {
                text += "rn" + ce.ToString();
            }
            throw new Exception(text);
        }

        Module module = compile.CompiledAssembly.GetModules()[0];
        Type mt = null;
        MethodInfo methInfo = null;

        if (module != null) {
            mt = module.GetType(targetNamespace + ".RemoteCaller");
        }

        if (mt != null) {
            methInfo = mt.GetMethod("Run");
        }

        if (methInfo != null) {
            return (string)methInfo.Invoke(null, null);
        }
        throw new InvalidOperationException("It's all gone wrong!");
    }
}

然后,您将从测试中调用该方法:

Assert.AreEqual("Fiddle", CodeMaker.CompileAndCall("Wibble.Fiddle.Con", "SO.dll"));
Assert.AreEqual("Fuddle", CodeMaker.CompileAndCall("Wibble.Fuddle.Con", "SO.dll"));

注意,上面示例中的“SO.dll”是包含CallerStuff.FindCallingNamespace

的程序集的名称

使用编译器生成调用者类可能对您所使用的内容有些过分,如果您决定使用它,则可能必须在代码中调整错误处理。如果您从不同的测试中多次调用生成的类,那么也可能值得缓存它们,可能是通过键入命名空间的字典而不是每次都编译它们。编译+通话代码基于Simeon Pilgrim的this blog post

相关问题