2015-06-11 2 views
1

У меня есть часть кода, которая получает определенную часть пространства имен из вызывающей сборки. Теперь я хочу, чтобы модуль тестировал этот код. Есть ли способ подделать имя вызывающего пространства имен с помощью 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(). Тогда результатом будет «ExampleNs».

Теперь можно создать NUnit UnitTest, который закодирован в пространстве имен MyUnitTests.ApiTest.TestNameSpace() но внутри Foo.Common.WebService вызов поступает не из Bar.ExampleNs.SomeMethod() так что я могу проверить на «ExampleNs»?

+1

Не знаете, что мешает вам помещать ваш тестовый класс в пространство имен, которое вы хотите ... (обратите внимание, если вы из фона Java - пространства имен в C# не имеют отношения к имени сборки). –

+0

В основном я хочу, чтобы мой тестовый код был чистым. Также было бы неплохо использовать Parameterized Tests. Мой OCD запускается, если у меня есть несколько пространств имен в одном файле. – mkeil

+0

Я отредактировал ваш заголовок. Пожалуйста, смотрите: «Если вопросы включают« теги »в их названиях?] (Http://meta.stackexchange.com/questions/19190/), где консенсус« нет, они не должны ». –

ответ

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

Использование компилятор для генерации классов вызывающего абонента, вероятно, слишком завышен для того, что вам нужно, и вам может потребоваться настроить обработку ошибок в коде, если вы решите его использовать. Если вы вызываете сгенерированные классы несколько раз из разных тестов, то также может быть целесообразно кэшировать их, возможно, используя словарь, исключающее пространство имен, а не компиляцию их каждый раз. Компиляция + Код звонка основан на this blog post от Simeon Pilgrim.

Смежные вопросы