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

发布于 2024-08-03 12:06:19 字数 821 浏览 2 评论 0原文

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

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 范围,我不能这样做。处理这种情况的正确方法是什么?

Let's say I have a project that is a class library. I have a class in that library and this class has some methods that are used inside the class only. Like this:

public class MyClass
{
  public void MyPublicMethod
  {
    int k

    // do something ...

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

  }

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

  }
}

Now I want to write unit tests for these methods. I would create a "Unit Tests" project, reference the nunit from it and write something like this

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

Of course, I can not do this, because of the MyInternalMethod scope. What would be the proper way to handle this situation?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

我做我的改变 2024-08-10 12:06:19

我猜这取决于您对单位是什么的看法,对吗?我通常为可访问的接口编写单元测试并忽略私有的东西。我曾与一些人合作,他们将私有内容保护(java)以进行单元测试访问。我真的不喜欢这种方法,因为它牺牲了测试访问的类设计的简洁性。

Guess it depends on your idea of what a unit is, right? I generally write unit tests for the accessible interface and ignore the private stuff. I've worked with people who will make private things protected (java) for unit test access. I really dislike that approach because it sacrifices the cleanness of the class design for test access.

秋叶绚丽 2024-08-10 12:06:19

您可以使用 InternalsVisibleToAttribute 使内部结构对某些程序集可见。

You can make internals visible to certain assemblies by using the InternalsVisibleToAttribute.

日裸衫吸 2024-08-10 12:06:19

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

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

i just test the public methods (and no i don't care about coverage metrics, i care about features that work).

note that if the public methods don't use the internal methods then the internal methods do not need to exist!

奢欲 2024-08-10 12:06:19

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

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

我个人只是避免编写任何执行真正复杂操作的私有方法。还有其他方法可以封装您不想公开的行为,同时仍然让您自己能够测试应该隐藏的内容。我认为完美封装和可测试性之间需要权衡。完美的封装很难实现,让自己对类有更多的了解通常会更有益。这可能是有争议的。

Here's a good article on that topic:

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

I personally just avoid writing any private methods that do anything really complex. There are other ways to encapsulate behaviors that you don't want to expose, while still giving yourself the ability to test the things that should be hidden. I think there is a tradeoff between perfect encapsulation and testability. Perfect encapsulation is hard to achieve, and it's usually more beneficial to give yourself more insight into the classes. This might be debatable.

何以心动 2024-08-10 12:06:19

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

Many people will say that you shouldn't test the internal methods, but instead test them through the public API. Regardless, you can use reflection if you really want to access these private members.

缱倦旧时光 2024-08-10 12:06:19

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

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

There's two cases: either your private methods get called from some public method, in which case you can test them through that method. Or, they don't get called from some public method, in which they cannot be called at all, are dead code, and should be deleted, not tested.

Note that if you are doing TDD, private methods can only spring into existence by extracting them from public methods, in which case they are already tested automatically.

拍不死你 2024-08-10 12:06:19

Visual Studio 可以为您生成私有访问器。查看 MSDN 上的私有、内部和友元方法的单元测试 。我相信 VS2005 只是生成私有访问器类并将其添加到您的单元测试项目中。所以当事情发生变化时你必须重新生成它们。然而,VS2008 生成一个私有访问器程序集并使其可供单元测试项目使用。你正在使用 NUnit,但我认为应该没问题。一探究竟。这样您就可以使您的实际代码免受任何与测试相关的代码和/或黑客攻击。

Visual Studio can generate private accessors for you. Check out Unit Tests for Private, Internal, and Friend Methods on MSDN. I believe VS2005 simply generated and added private accessor classes to your unit test project. So you had to regenerate them when things changed. However, VS2008 generates a private accessor assembly and makes it available to the unit test project. You're using NUnit but I think it should be ok. Check it out. This way you can keep your actual code free of any testing related code and/or hacks.

孤者何惧 2024-08-10 12:06:19

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

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

在构建时,我们可以在“调试”或“发布”模式下运行单元测试,并且如果需要,我们可以从任一构建中删除测试代码,因此将测试代码与被测代码放在一起没有什么害处;如果有的话,它的参数类似于 code-and-data-together = object 或 object-and-doc-comments = Documented-object。换句话说:代码、数据、测试和文档注释在一起 = 内聚单元。

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

In the past I have created test fixtures in the same namespace and assembly as the class under test to test internal methods. I'm not saying whether internal methods should be tested or not. Pragmatically, you might test them then refactor later.

I have also created partial classes to test private methods and used a compiler directive around the entire part (which was in its own file). Again, not saying this is best, but sometimes you need to move forward.

At build time we could run the unit tests in Debug or Release mode, and we could strip the test code from either build if desired, so there was no harm in putting the test code with the code under test; if anything, it's similar in argument to code-and-data-together = object or object-and-doc-comments = documented-object. In other words: code-and-data-and-test-and-doc-comments-together = cohesive-unit.

An extra time during the build was negligible.

深海蓝天 2024-08-10 12:06:19

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

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

I've used a couple methods for doing this. I've made my private methods protected so that way I can inherit from the class in my unit test and make a “helper” class to test those methods. The other is reflection. The reflection is strait up easier IMO but does go against how your classes should be designed for testing. Here’s a simplified version of what I’m talking about.

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

Given a class such as this

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

You'd use these as so

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));
屋顶上的小猫咪 2024-08-10 12:06:19

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

并非每个人都同意这种方法(请参阅此问题< /a> 我之前问过)但到目前为止我还没有发现它或有任何缺陷向我指出。我已经用这种方式进行单元测试四五年了。

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

I like to put my unit tests in the same class as the one they are testing. This has two advantages, the first is that it solves the issue you are hitting, the second is that you never lose or forget about them as often ends up being the case if they are in a separate assembly.

Not everyone agrees with that sort of approach (see this SO question I asked previously) but as yet I haven't found or had any flaws in it pointed out to me. I've been doing unit tests that way for going on 4 or 5 years now.

#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
 }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文