代码之家  ›  专栏  ›  技术社区  ›  Evgeny

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

  •  5
  • Evgeny  · 技术社区  · 17 年前

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

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

    10 回复  |  直到 17 年前
        1
  •  4
  •   Peter Cardona    17 年前

    我想这取决于你对单位是什么的看法,对吧?我通常为可访问接口编写单元测试,忽略私有内容。我曾与那些为单元测试访问设置私有内容保护(java)的人合作过。我真的不喜欢这种方法,因为它牺牲了类设计的整洁性来进行测试访问。

        2
  •  3
  •   Steven A. Lowe    17 年前

    您可以通过使用 InternalsVisibleToAttribute .

        3
  •  2
  •   Andy White    17 年前

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

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

        4
  •  2
  •   Chris Missal    10 年前

    这里有一篇关于这个话题的好文章:

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

        5
  •  1
  •   NotDan    17 年前

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

        6
  •  1
  •   Jörg W Mittag    17 年前

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

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

        7
  •  0
  •   Mehmet Aras    17 年前

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

        8
  •  0
  •   Kit    17 年前

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

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

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

        9
  •  0
  •   Brian Surowiec    17 年前

    我用了几种方法来做这件事。我已经保护了我的私有方法,这样我就可以从单元测试中的类继承,并创建一个辅助类来测试这些方法。另一个是反思。国际海事组织认为,这种反思更容易,但确实违背了你的课程应该如何设计用于测试。这是我所说内容的简化版本。

    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));
    
        10
  •  -2
  •   Community Mohan Dere    9 年前

    我喜欢把我的单元测试和他们正在测试的单元测试放在同一个班上。这有两个优点,第一个优点是它解决了你遇到的问题,第二个优点是你永远不会失去或忘记它们,因为如果它们在一个单独的组件中,通常会出现这种情况。

    不是每个人都同意这种方法(见SO question 我之前问过),但到目前为止,我还没有发现或发现其中的任何缺陷。我已经这样做了四五年了。

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