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

本文关键字:测试 反射 命名空间 调用 | 更新日期: 2023-09-27 17:55:50

我有一段代码,它从调用程序集获取命名空间的特定部分。现在我想对这段代码进行单元测试。有没有办法使用 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()中检索命名空间。然后结果将是"示例"。

现在是否可以创建一个在命名空间中编码的 NUnit UnitTest MyUnitTests.ApiTest.TestNameSpace()但在Foo.Common.WebService内部调用似乎来自Bar.ExampleNs.SomeMethod()以便我可以测试"ExampleNs"?

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

我认为到目前为止,实现您所追求的目标的最简单方法是创建一个呼叫转发器并通过转发器调用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 的这篇博文。