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

时间:2009-09-02 00:22:32

标签: .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)
  {
        // 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范围,我无法做到这一点。处理这种情况的正确方法是什么?

10 个答案:

答案 0 :(得分:4)

猜猜这取决于你对单位是什么的看法,对吧?我通常为可访问的接口编写单元测试并忽略私有东西。我曾与那些将私有物品保护(java)以进行单元测试访问的人合作。我真的不喜欢这种方法,因为它牺牲了类设计的清晰度以便进行测试访问。

答案 1 :(得分:3)

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

请注意,如果公共方法不使用内部方法,则内部方法不需要存在!

答案 2 :(得分:2)

您可以使用InternalsVisibleToAttribute

使某些装配对内部可见

答案 3 :(得分:2)

这是一篇关于该主题的好文章:

http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx

我个人只是避免编写任何非常复杂的私有方法。还有其他方法可以封装您不想公开的行为,同时还能让您自己测试应该隐藏的内容。我认为完美的封装和可测试性之间存在权衡。完美的封装很难实现,通常更有利于让自己更深入地了解这些类。这可能是值得商榷的。

答案 4 :(得分:1)

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

答案 5 :(得分:1)

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

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

答案 6 :(得分:0)

Visual Studio可以为您生成私有访问器。在MSDN上查看Unit Tests for Private, Internal, and Friend Methods。我相信VS2005只是为您的单元测试项目生成并添加了私有访问器类。因此,当事情发生变化时,你必须重新生成它们。但是,VS2008会生成一个私有访问器程序集,并使其可用于单元测试项目。你正在使用NUnit,但我认为它应该没问题。看看这个。这样,您可以保持实际代码不受任何测试相关代码和/或黑客攻击。

答案 7 :(得分:0)

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

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

在构建时,我们可以在调试或发布模式下运行单元测试,如果需要,我们可以从任一构建中剥离测试代码,因此在测试代码中没有 harm 被测代码;如果有的话,它在代码和数据 - 一起= object或object-and-doc-comments = documentation-object的论证中是相似的。换句话说:代码和数据以及测试和文档注释一起=内聚单元。

构建期间的额外时间可以忽略不计。

答案 8 :(得分:0)

我使用了几种方法来做到这一点。我已经使我的私有方法受到保护,这样我可以在单元测试中继承该类,并创建一个“帮助器”类来测试这些方法。另一个是反思。 IMO的反思更容易,但是我们的课程应该如何设计用于测试。这是我正在谈论的简化版本。

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;
    }
}

给出一个像这样的课程

public class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    internal string GetPrettyName()
    {
        return string.Concat(FirstName, " ", LastName);
    }

    static internal int GetSystemId(string userName)
    {
        // some magic here
        return 13;
    }
}

你可以这样使用

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));

答案 9 :(得分:-2)

我喜欢把我的单元测试放在他们测试的同一个类中。这有两个好处,第一个是它解决了你遇到的问题,第二个是你永远不会丢失或忘记它们,因为如果它们在一个单独的组件中,往往最终会出现这种情况。

不是每个人都同意这种方法(参见我之前提到的SO question)但是我还没有找到或有任何瑕疵向我指出。我一直在进行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
 }