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

C# 假调用命名空间以测试反射,c#,unit-testing,nunit,system.reflection,C#,Unit Testing,Nunit,System.reflection,我有一段代码,它从调用程序集获取名称空间的特定部分。现在我想对这段代码进行单元测试。 有没有一种方法可以使用NUnit伪造调用名称空间的名称,而不在该特定名称空间中实现NUnit testcase 以下是我要测试的方法: public static string FindCallingNameSpace() { var stackTrace = new StackTrace(); var stackFrames = stackTrace.GetFrames(); if (sta

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

以下是我要测试的方法:

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!");
}
public静态字符串FindCallingNameSpace()
{
var stackTrace=新的stackTrace();
var stackFrames=stackTrace.GetFrames();
if(stackFrames!=null)
{
int nrFrames=stackFrames.Length;
对于(int i=0;i
例如:
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的。

我认为目前实现您所追求的最简单的方法是创建一个调用转发器,并通过转发器调用
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编写的。

不确定是什么阻止了您将测试类放入所需的命名空间中。。。(请注意,如果您来自Java背景-C#中的名称空间与程序集名称没有关系)。另外,使用参数化测试也很好。如果我在一个文件中有多个名称空间,我的OCD就会生效。我已经编辑了你的标题。请参阅“”,其中一致意见是“不,他们不应该”。不确定是什么阻止您将测试类放入所需的命名空间中。。。(请注意,如果您来自Java背景-C#中的名称空间与程序集名称没有关系)。另外,使用参数化测试也很好。如果我在一个文件中有多个名称空间,我的OCD就会生效。我已经编辑了你的标题。请参阅“”,其中一致意见是“不,他们不应该”。不确定是什么阻止您将测试类放入所需的命名空间中。。。(请注意,如果您来自Java背景-C#中的名称空间与程序集名称没有关系)。另外,使用参数化测试也很好。如果我在一个文件中有多个名称空间,我的OCD就会生效。我已经编辑了你的标题。请参阅“”,其中的共识是“不,他们不应该”。