.net 单元测试和对象范围-如何测试私有/内部方法等?

.net 单元测试和对象范围-如何测试私有/内部方法等?,.net,unit-testing,nunit,.net,Unit Testing,Nunit,假设我有一个项目是类库。我在那个库中有一个类,这个类有一些只在类内部使用的方法。像这样: public class MyClass { public void MyPublicMethod { int k // do something ... int z = MyInternalMethod(k); // do something else ... } internal int MyInternalMethod(int i) {

假设我有一个项目是类库。我在那个库中有一个类,这个类有一些只在类内部使用的方法。像这样:

public class MyClass
{
  public void MyPublicMethod
  {
    int k

    // do something ...

    int z = MyInternalMethod(k);
    // do something else ...

  }

  internal int MyInternalMethod(int i)
  {
        // do something ...

  }
}
现在我想为这些方法编写单元测试。我将创建一个“单元测试”项目,从中引用nunit并编写如下内容

[TestFixture]
public class UnitTests
{
  private MyClass myClass;

  [SetUp]
  public void SetupTest
  {
    myClass = new MyClass();
  }

  [Test]
  public void TestMyInternalMethod
  {
    int z = 100;
    int k = myClass.MyInternalMethod(z); //CAN NOT DO THIS!
    Assert.AreEqual(k, 100000);
  }

  [TearDown]
  public void TearDown
  {
    myClass = null;
  }
}

当然,我不能这样做,因为MyInternalMethod的作用域。处理这种情况的正确方法是什么?

您可以通过使用使内部构件对某些组件可见。

我喜欢将我的单元测试与它们正在测试的单元测试放在同一个类中。这有两个优点,第一个是它解决了您遇到的问题,第二个是您永远不会丢失或忘记它们,因为如果它们位于单独的部件中,往往会出现这种情况

并不是每个人都同意这种方法(我之前问过这个问题),但到目前为止,我还没有发现或向我指出其中的任何缺陷。我已经用这种方式做了4到5年的单元测试了

#if UNITTEST
using NUnit.Framework;
#endif

public class MyBlackMagic
{
    private int DoMagic()
    {
        return 1;
    }

    #if UNITTEST

    [TestFixture]
    public class MyBlackMagicUnitTest
    {
         [TestFixtureSetUp]
         public void Init()
         {
             log4net.Config.BasicConfigurator.Configure();
         }

         [Test]
         public void DoMagicTest()
         {
             Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name);
             Assert.IsTrue(DoMagic() == 1, "You are not a real magician!");
         }
     }

     #endif
 }

我猜这取决于你对单位的理解,对吧?我通常为可访问的接口编写单元测试,而忽略私有的东西。我曾与一些人合作过,他们会为单元测试访问提供私有事物保护(java)。我真的不喜欢这种方法,因为它牺牲了类设计的清洁度来进行测试访问


就我个人而言,我只是避免编写任何真正复杂的私有方法。还有其他方法可以封装您不想公开的行为,同时还可以让您自己测试应该隐藏的东西。我认为在完美封装和可测试性之间有一个折衷。完美的封装是很难实现的,让自己对类有更多的了解通常更有益。这可能是有争议的。

Visual Studio可以为您生成私有访问器。查看MSDN。我相信VS2005只是生成了私有访问器类并将其添加到单元测试项目中。所以,当情况发生变化时,你必须使它们再生。但是,VS2008生成一个私有访问器程序集,并使其可用于单元测试项目。你用的是NUnit,但我觉得应该没问题。过来看。通过这种方式,您可以使实际代码免受任何测试相关代码和/或黑客攻击

过去,我在与被测类相同的命名空间和程序集中创建了测试装置,以测试内部方法。我不是说是否应该测试内部方法。实际上,您可以测试它们,然后在以后重构

我还创建了分部类来测试私有方法,并在整个部分(在它自己的文件中)使用了编译器指令。再说一次,不要说这是最好的,但有时你需要前进

在构建时,我们可以在调试或发布模式下运行单元测试,如果需要,我们可以从任何一个构建中剥离测试代码,因此将测试代码与待测试代码放在一起是无害的;如果有什么区别的话,它的参数类似于代码和数据一起=对象或对象,文档注释=文档对象。换句话说:代码和数据以及测试和文档注释一起=内聚单元


构建过程中的额外时间可以忽略不计。

我只是测试公共方法(不,我不关心覆盖率指标,我关心的是有效的功能)


注意,如果公共方法不使用内部方法,那么内部方法就不需要存在

许多人会说,不应该测试内部方法,而应该通过公共API测试它们。无论如何,如果您真的想访问这些私有成员,您可以使用反射。

我使用了几种方法来实现这一点。我已经对我的私有方法进行了保护,这样我就可以在单元测试中从类继承,并创建一个“helper”类来测试这些方法。另一个是反射。在我看来,反射是有问题的,但它确实违背了类的测试设计。这是我所说内容的简化版本

public static class ReflectionHelper
{
    public static object RunStaticMethod<TInstance>(string methodName, params object[] methodParams)
    {
        var methodType = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
        return RunMethod<TInstance>(null, methodName, methodType, methodParams);
    }

    public static object RunInstanceMethod<TInstance>(this TInstance instance, string methodName, params object[] methodParams)
    {
        var methodType = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
        return RunMethod<TInstance>(instance, methodName, methodType, methodParams);
    }

    private static object RunMethod<TInstance>(object instance, string methodName, BindingFlags methodType, params object[] methodParams)
    {
        var instanceType = typeof(TInstance);
        var method = instanceType.GetMethod(methodName, methodType);
        if (method == null)
        {
            throw new ArgumentException(string.Format("There is no method '{0}' for type '{1}'.", methodName, instanceType));
        }

        var result = method.Invoke(instance, methodParams);

        return result;
    }
}
你就这么用吧

var user = new User { FirstName = "Peter", LastName = "Gibbons" };

var name = user.RunInstanceMethod("GetPrettyName");
Assert.That(name, Is.EqualTo("Peter Gibbons"));

var id = ReflectionHelper.RunStaticMethod<User>("GetSystemId", "tester");
Assert.That(id, Is.EqualTo(13));
var user=new user{FirstName=“Peter”,LastName=“Gibbons”};
var name=user.RunInstanceMethod(“GetPrettyName”);
断言(名称为.EqualTo(“彼得·吉本斯”);
var id=ReflectionHelper.RunStaticMethod(“GetSystemId”、“tester”);
断言(id,Is.EqualTo(13));

有两种情况:要么您的私有方法从某个公共方法调用,在这种情况下,您可以通过该方法测试它们。或者,它们不会从某个公共方法中被调用,在该方法中它们根本无法被调用,它们是死代码,应该被删除,而不是测试


请注意,如果您正在进行TDD,私有方法只能通过从公共方法中提取它们而存在,在这种情况下,它们已经被自动测试。

糟糕,糟糕,糟糕-请不要这样做。但是您需要从您拥有的任何项目中引用nunit.framework。我不确定这是否会产生诸如更长的构建时间、更大的可执行文件之类的后果,但这似乎有点笨拙。@Corehpf-我们这里不讨论宗教。如果您有反对某件事的论点,请提供它。@Evgeny-it从未引起任何问题,在构建时间上也没有明显的差异。如果需要,可以轻松关闭UNITTEST预编译器指令并删除引用。或者,如果它是一些非常需要的东西,您可以在VS.Net中创建不同的构建配置……然后您使用模拟框架,还必须部署该库。如果您想使用类似IoC容器的任何东西,您必须确保您没有在生产中使用测试配置。条件编译——不寒而栗。我敦促其他人不要这样做!同意。我将使用InternalsVisibles来
var user = new User { FirstName = "Peter", LastName = "Gibbons" };

var name = user.RunInstanceMethod("GetPrettyName");
Assert.That(name, Is.EqualTo("Peter Gibbons"));

var id = ReflectionHelper.RunStaticMethod<User>("GetSystemId", "tester");
Assert.That(id, Is.EqualTo(13));