ChatGPT解决这个技术问题 Extra ChatGPT

你如何对私有方法进行单元测试?

我正在构建一个包含一些公共和私有方法的类库。我希望能够对私有方法进行单元测试(主要是在开发时,但它也可能对未来的重构有用)。

这样做的正确方法是什么?

我可能遗漏了一些东西,或者也许只是这个问题是,嗯... pre-historic 在互联网时代,但是私有方法的单元测试现在既简单又直接,Visual Studio 生成了必要的访问器类在需要时,用非常接近于简单功能测试所需的代码片段预先填充测试逻辑。参见例如。 msdn.microsoft.com/en-us/library/ms184807%28VS.90%29.aspx
这似乎与 stackoverflow.com/questions/34571/… 几乎重复。
提问者可能没有使用 Visual Studio
不要对内部进行单元测试:blog.ploeh.dk/2015/09/22/unit-testing-internals

J
Jeroen Heijmans

如果您想对私有方法进行单元测试,则可能有问题。单元测试(一般而言)旨在测试类的接口,即其公共(和受保护)方法。您当然可以“破解”解决方案(即使只是通过公开方法),但您可能还需要考虑:

如果您要测试的方法确实值得测试,那么将它移到自己的类中可能是值得的。向调用私有方法的公共方法添加更多测试,测试私有方法的功能。 (正如评论员所指出的,只有当这些私有方法的功能确实是公共接口的一部分时,您才应该这样做。如果它们实际上执行对用户隐藏的功能(即单元测试),这可能很糟糕)。


选项 2 使单元测试必须了解函数的底层实现。我不喜欢那样做。我通常认为单元测试应该在不假设任何实现的情况下测试功能。
测试实现的缺点是,如果您对实现进行任何更改,测试将很容易被破坏。这是不可取的,因为重构与在 TDD 中编写测试一样重要。
好吧,如果您更改实现,测试应该会中断。 TDD 意味着首先更改测试。
@sleske - 我不太同意。如果功能没有改变,那么测试就没有理由中断,因为测试应该真正测试行为/状态,而不是实现。这就是 jtr 的含义,它使您的测试变得脆弱。在理想情况下,您应该能够重构您的代码并让您的测试仍然通过,从而验证您的重构没有改变您系统的功能。
为天真道歉(还没有太多的测试经验),但是单元测试的想法不是单独测试每个代码模块吗?我真的不明白为什么私有方法应该被排除在这个想法之外。
R
Raedwald

如果您使用的是 .net,则应使用 InternalsVisibleToAttribute


呸。这会编译到您发布的程序集中。
@Jay - 不能在 InternalsVisibleTo 属性周围使用 #if DEBUG 使其不适用于发布代码吗?
@Mike,你可以,但是你只能在调试代码上运行单元测试,而不是发布代码。由于发布代码经过优化,您可能会看到不同的行为和不同的时间。在多线程代码中,这意味着您的单元测试将无法正确检测竞争条件。更好的是通过下面@AmazedSaint 的建议使用反射或使用内置的 PrivateObject/PrivateType。假设您的测试工具以完全信任的方式运行(MSTest 在本地运行),这允许您在发布版本中查看私有信息
我错过了什么?当它实际上并没有回答测试私有方法的具体问题时,为什么这是公认的答案? InternalsVisibleTo 仅公开标记为内部的方法,而不公开 OP 要求的标记为私有的方法(以及我登陆这里的原因)。我想我需要继续使用七号回答的 PrivateObject 吗?
@Jay 我知道这有点晚了,但一种选择是像 Mike 建议的那样在 InternalsVisibleTo 周围使用 #if RELEASE_TEST 之类的东西,并复制定义 RELEASE_TEST 的发布构建配置。您可以通过优化来测试您的发布代码,但是当您实际构建发布时,您的测试将被省略。
P
Pang

测试私有方法可能没有用。但是,我有时也喜欢从测试方法中调用私有方法。大多数时候为了防止测试数据生成的代码重复......

Microsoft 为此提供了两种机制:

访问器

转到类定义的源代码

右键单击类的名称

选择“创建私人访问器”

选择应该在其中创建访问器的项目 => 您最终将得到一个名为 foo_accessor 的新类。该类将在编译期间动态生成,并提供所有可用的成员。

但是,当涉及到更改原始类的接口时,该机制有时会有些棘手。所以,大多数时候我避免使用它。

PrivateObject 类 另一种方法是使用 Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject

// Wrap an already existing instance
PrivateObject accessor = new PrivateObject( objectInstanceToBeWrapped );

// Retrieve a private field
MyReturnType accessiblePrivateField = (MyReturnType) accessor.GetField( "privateFieldName" );

// Call a private method
accessor.Invoke( "PrivateMethodName", new Object[] {/* ... */} );

你如何调用私有静态方法?
私有访问器是 deprecated in Visual Studio 2012
从 VS 2011 起,测试私有方法的访问器方法已被弃用。 blogs.msdn.com/b/visualstudioalm/archive/2012/03/08/…
阅读 Microsoft 网站 here 上的文档,我没有看到任何提及已弃用 PrivateObject 类的内容。我正在使用 MSVS 2013,它按预期工作。
@RyanGates 您引用的网站上的第一个解决方案指出,访问私有成员的解决方案是“使用 PrivateObject 类来帮助访问代码中的内部和私有 API。这可以在 Microsoft.VisualStudio.QualityTools.UnitTestFramework 中找到。 dll 程序集。”
D
Darrell Plank

我不同意“你应该只对测试外部接口感兴趣”的理念。这有点像说汽车修理店应该只测试车轮是否转动。是的,最终我对外部行为感兴趣,但我喜欢我自己的、私人的、内部测试更具体、更切题。是的,如果我重构,我可能不得不更改一些测试,但除非它是一个大规模的重构,否则我只需要更改一些并且其他(未更改的)内部测试仍然有效的事实是一个很好的指标重构已经成功。

您可以尝试仅使用公共接口覆盖所有内部案例,理论上可以完全使用公共接口测试每个内部方法(或至少每个重要的方法),但您可能最终不得不站在头上才能实现这以及通过公共接口运行的测试用例与它们设计用于测试的解决方案的内部部分之间的联系可能很难或无法辨别。已经指出,保证内部机器正常工作的个别测试非常值得重构带来的微小测试更改 - 至少这是我的经验。如果每次重构都必须对测试进行重大更改,那么这可能没有意义,但在这种情况下,也许您应该完全重新考虑您的设计。一个好的设计应该足够灵活,可以在不进行大规模重新设计的情况下进行大多数更改。


恐怕我还是不同意你的观点。将每个组件视为黑匣子,可以毫无问题地换入/换出模块。如果您有一个必须执行 XFooService,那么您应该关心的是它确实在请求时执行了 X如何它应该不重要。如果类中存在无法通过接口识别的问题(不太可能),它仍然是有效的 FooService。如果 is 通过界面可见是一个问题,那么对公共成员的测试应该可以检测到它。重点应该是,只要轮子转动得当,就可以当轮子使用。
一种通用的方法是,如果您的内部逻辑足够复杂以至于您觉得它需要进行单元测试,那么可能需要将其提取到某种具有可以进行单元测试的公共接口的辅助类中。然后你的“父”类可以简单地使用这个助手,每个人都可以适当地进行单元测试。
@Basic:此答案中的逻辑完全错误。需要私有方法的经典案例是当您需要一些代码被公共方法重用时。你把这段代码放到一些 PrivMethod 中。此方法不应公开,但需要测试以确保依赖 PrivMethod 的公共方法可以真正依赖它。
@Dima 当然,如果 PrivMethod 有问题,对 PubMethod 调用 PrivMethod 的测试应该暴露它吗?当您将 SimpleSmtpService 更改为 GmailService 时会发生什么?突然之间,您的私人测试指向的代码不再存在或可能以不同的方式工作并且会失败,即使应用程序可能按设计完美运行。如果有适用于两个电子邮件发件人的复杂处理,也许它应该在两个都可以使用并单独测试的 EmailProcessor 中?
@miltonb 我们可能会从不同的开发风格来看待这个问题。 WRT 内部,我不倾向于对它们进行单元测试。如果有问题(通过接口测试确定),要么通过附加调试器很容易追踪,要么类太复杂,应该拆分(使用新类的公共接口进行单元测试)恕我直言
J
Jason Jackson

在极少数情况下我想测试私有函数,我通常将它们修改为受保护,并且我编写了一个带有公共包装函数的子类。

班上:

...

protected void APrivateFunction()
{
    ...
}

...

用于测试的子类:

...

[Test]
public void TestAPrivateFunction()
{
    APrivateFunction();
    //or whatever testing code you want here
}

...

您甚至可以将该子类放在您的单元测试文件中,而不是把真正的类弄得乱七八糟。 +1 狡猾。
如果可能,我总是将所有与测试相关的代码放在单元测试项目中。这只是伪代码。
这个函数不是私有的,它是受保护的,最终结果......你让你的代码不那么安全/暴露给子类型私有功能
B
Big Kahuna

我认为应该问一个更基本的问题是,您为什么要首先尝试测试私有方法。这是一种代码味道,您试图通过该类的公共接口测试私有方法,而该方法是私有的,因为它是一个实现细节。人们应该只关心公共接口的行为,而不是它是如何在幕后实现的。

如果我想测试私有方法的行为,通过使用常见的重构,我可以将其代码提取到另一个类中(可能具有包级别的可见性,因此确保它不是公共 API 的一部分)。然后我可以单独测试它的行为。

重构的产物意味着私有方法现在是一个单独的类,它已成为原始类的协作者。它的行为将通过它自己的单元测试得到很好的理解。

然后,当我尝试测试原始类时,我可以模拟它的行为,这样我就可以专注于测试该类的公共接口的行为,而不必测试公共接口的组合爆炸及其所有私有方法的行为.

我认为这类似于驾驶汽车。当我开车时,我不会开着引擎盖开车,所以我可以看到引擎在工作。我依靠汽车提供的界面,即转速表和速度表来了解发动机是否在工作。我相信当我踩下油门踏板时汽车实际上会移动。如果我想测试引擎,我可以单独对其进行检查。 :D

当然,如果您有遗留应用程序,直接测试私有方法可能是最后的手段,但我更希望重构遗留代码以实现更好的测试。 Michael Feathers 就这个主题写了一本很棒的书。 http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052


这个答案的逻辑完全错误。需要私有方法的经典案例是当您需要一些代码被公共方法重用时。你把这段代码放到一些 PrivMethod 中。此方法不应公开,但需要测试以确保依赖 PrivMethod 的公共方法可以真正依赖它。
在初始开发过程中是有道理的,但您是否希望在标准回归套装中测试私有方法?如果是这样,如果实现发生变化,它可能会破坏测试套件。 OTOH,如果您的回归测试仅关注外部可见的公共方法,那么如果私有方法稍后中断,回归套件仍应检测到错误。然后,如有必要,您可以根据需要清除旧的私人测试。
不同意,您应该只测试公共接口,否则为什么需要私有方法。在这种情况下将它们全部公开并测试所有这些。如果您正在测试私有方法,那么您正在破坏封装。如果你想测试一个私有方法并且它在多个公共方法中使用,那么它应该被移动到它自己的类并单独测试。然后所有公共方法都应该委托给那个新类。这样你仍然可以测试原始类的接口,您可以验证行为没有改变,并且您对委托的私有方法有单独的测试。
@Big Kahuna - 如果您认为不需要对私有方法进行单元测试,那么您从未使用过足够大/复杂的项目。很多时候,像客户端特定验证这样的公共函数最终只有 20 行,只是调用非常简单的私有方法以使代码更具可读性,但您仍然必须测试每个单独的私有方法。对公共功能进行 20 次测试,当单元测试失败时,将很难首次亮相。
我在一家富时 100 指数公司工作。我想我在我的时间里见过几个复杂的项目,谢谢。如果您需要测试到该级别,那么每个私有方法作为单独的协作者,都应该单独测试,因为这意味着它们具有需要测试的个人行为。对主要中介对象的测试就变成了交互测试。它只是测试是否调用了正确的策略。您的场景听起来像是有问题的课程没有遵循 SRP。它没有一个改变的理由,而是 20 => SRP 违规。读一读 GOOS 书或鲍勃叔叔。 YMWV
a
amazedsaint

由于某种原因,私有类型、内部结构和私有成员都是如此,而且您通常不想直接与它们打交道。如果你这样做了,你以后很可能会崩溃,因为不能保证创建这些程序集的人会保留私有/内部实现。

但是,有时,在对已编译或第三方程序集进行一些破解/探索时,我自己最终想要初始化一个私有类或具有私有或内部构造函数的类。或者,有时,在处理我无法更改的预编译遗留库时——我最终会针对私有方法编写一些测试。

因此诞生了 AccessPrivateWrapper - http://amazedsaint.blogspot.com/2010/05/accessprivatewrapper-c-40-dynamic.html - 它是一个快速包装类,使用 C# 4.0 动态特性和反射使工作变得容易。

您可以创建内部/私有类型,例如

    //Note that the wrapper is dynamic
    dynamic wrapper = AccessPrivateWrapper.FromType
        (typeof(SomeKnownClass).Assembly,"ClassWithPrivateConstructor");

    //Access the private members
    wrapper.PrivateMethodInPrivateClass();

U
Unknown

那么您可以通过两种方式对私有方法进行单元测试

您可以创建 PrivateObject 类的实例,语法如下 PrivateObject obj= new PrivateObject(PrivateClass); //现在有了这个obj就可以调用PrivateCalss的私有方法了。 obj.PrivateMethod("参数");您可以使用反射。 PrivateClass obj = new PrivateClass(); // 包含私有 obj 的类 Type t = typeof(PrivateClass); var x = t.InvokeMember("PrivateFunc", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, obj, new object[] { 5 });


很好的答案,但是对于#1,您的语法是错误的。您需要先声明一个 PrivateClass 的实例并使用它。 stackoverflow.com/questions/9122708/…
p
philsquared

我还使用了 InternalsVisibleToAttribute 方法。还值得一提的是,如果您不愿意将以前的私有方法设置为内部来实现这一点,那么也许它们不应该成为直接单元测试的主题。

毕竟,您正在测试您的类的行为,而不是它的具体实现——您可以更改后者而不更改前者,并且您的测试仍然应该通过。


我喜欢关于测试行为而不是实现的观点。如果您将单元测试与实现(私有方法)联系起来,那么测试将变得脆弱,并且需要在实现更改时进行更改。
A
Adam Wenger

有两种类型的私有方法。静态私有方法和非静态私有方法(实例方法)。以下 2 篇文章通过示例解释了如何对私有方法进行单元测试。

单元测试静态私有方法单元测试非静态私有方法


提供一些例子,不只给个链接
看起来很丑。没有智能。来自 MS 的错误解决方案。我很震惊!
测试私有静态方法的最简单方法
M
Marcus King

MS Test 内置了一个很好的功能,通过创建一个名为 VSCodeGenAccessors 的文件,使私有成员和方法在项目中可用

[System.Diagnostics.DebuggerStepThrough()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
    internal class BaseAccessor
    {

        protected Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject m_privateObject;

        protected BaseAccessor(object target, Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
        {
            m_privateObject = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject(target, type);
        }

        protected BaseAccessor(Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
            :
                this(null, type)
        {
        }

        internal virtual object Target
        {
            get
            {
                return m_privateObject.Target;
            }
        }

        public override string ToString()
        {
            return this.Target.ToString();
        }

        public override bool Equals(object obj)
        {
            if (typeof(BaseAccessor).IsInstanceOfType(obj))
            {
                obj = ((BaseAccessor)(obj)).Target;
            }
            return this.Target.Equals(obj);
        }

        public override int GetHashCode()
        {
            return this.Target.GetHashCode();
        }
    }

使用从 BaseAccessor 派生的类

[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class SomeClassAccessor : BaseAccessor
{

    protected static Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType m_privateType = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType(typeof(global::Namespace.SomeClass));

    internal SomeClassAccessor(global::Namespace.Someclass target)
        : base(target, m_privateType)
    {
    }

    internal static string STATIC_STRING
    {
        get
        {
            string ret = ((string)(m_privateType.GetStaticField("STATIC_STRING")));
            return ret;
        }
        set
        {
            m_privateType.SetStaticField("STATIC_STRING", value);
        }
    }

    internal int memberVar    {
        get
        {
            int ret = ((int)(m_privateObject.GetField("memberVar")));
            return ret;
        }
        set
        {
            m_privateObject.SetField("memberVar", value);
        }
    }

    internal int PrivateMethodName(int paramName)
    {
        object[] args = new object[] {
            paramName};
        int ret = (int)(m_privateObject.Invoke("PrivateMethodName", new System.Type[] {
                typeof(int)}, args)));
        return ret;
    }

生成的文件仅存在于 VS2005 中。 2008 年,它们在幕后产生。他们令人厌恶。并且相关的影子任务在构建服务器上是不稳定的。
在 VS2012-2013 中也弃用了访问器。
P
Pedro

在 CodeProject 上有一篇文章简要讨论了测试私有方法的优缺点。然后它提供了一些反射代码来访问私有方法(类似于上面 Marcus 提供的代码)。我在示例中发现的唯一问题是代码没有考虑重载方法。

你可以在这里找到这篇文章:

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


J
James Curran

声明它们 internal,然后使用 InternalsVisibleToAttribute 允许您的单元测试程序集看到它们。


我不喜欢使用 InternalsVisibleTo,因为我将方法设为私有是有原因的。
s
swilliams

我倾向于不使用编译器指令,因为它们很快就会使事情变得混乱。如果您确实需要它们,一种减轻它的方法是将它们放在一个部分类中,并让您的构建在制作生产版本时忽略该 .cs 文件。


您将在生产版本中包含测试访问器(以测试编译器优化等),但在发布版本中排除它们。但是我正在分裂头发并且无论如何我都赞成这个,因为我认为将这些东西放在一个地方是个好主意。谢谢你的主意。
c
cda01

首先,您不应该测试代码的私有方法。您应该测试“公共接口”或 API,即类的公共事物。 API 是您向外部调用者公开的所有公共方法。

原因是一旦你开始测试你的类的私有方法和内部结构,你就会将你的类的实现(私有的东西)耦合到你的测试中。这意味着当您决定更改实现细节时,您还必须更改测试。

出于这个原因,您应该避免使用 InternalsVisibleToAtrribute。

这是 Ian Cooper 的精彩演讲,涵盖了这个主题:Ian Cooper: TDD, where did it all go wrong


E
Erick Stone

对于任何想要运行私有方法而没有所有麻烦和混乱的人。这适用于任何单元测试框架,只使用良好的旧反射。

public class ReflectionTools
{
    // If the class is non-static
    public static Object InvokePrivate(Object objectUnderTest, string method, params object[] args)
    {
        Type t = objectUnderTest.GetType();
        return t.InvokeMember(method,
            BindingFlags.InvokeMethod |
            BindingFlags.NonPublic |
            BindingFlags.Instance |
            BindingFlags.Static,
            null,
            objectUnderTest,
            args);
    }
    // if the class is static
    public static Object InvokePrivate(Type typeOfObjectUnderTest, string method, params object[] args)
    {
        MemberInfo[] members = typeOfObjectUnderTest.GetMembers(BindingFlags.NonPublic | BindingFlags.Static);
        foreach(var member in members)
        {
            if (member.Name == method)
            {
                return typeOfObjectUnderTest.InvokeMember(method, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, typeOfObjectUnderTest, args);
            }
        }
        return null;
    }
}

然后在您的实际测试中,您可以执行以下操作:

Assert.AreEqual( 
  ReflectionTools.InvokePrivate(
    typeof(StaticClassOfMethod), 
    "PrivateMethod"), 
  "Expected Result");

Assert.AreEqual( 
  ReflectionTools.InvokePrivate(
    new ClassOfMethod(), 
    "PrivateMethod"), 
  "Expected Result");

L
Ludovic Dubois

有时,测试私有声明可能会很好。从根本上说,编译器只有一个公共方法:Compile(string outputFileName, params string[] sourceSFileNames)。我敢肯定你明白,如果不测试每个“隐藏”声明,就很难测试这样的方法!

这就是我们创建 Visual T#: 以简化测试的原因。它是一种免费的 .NET 编程语言(兼容 C# v2.0)。

我们添加了 '.-' 运算符。它的行为就像'。运算符,除非您还可以访问测试中的任何隐藏声明,而无需更改测试项目中的任何内容。

查看我们的网站:download免费


r
rube

我很惊讶还没有人这么说,但我采用的一个解决方案是在类中创建一个静态方法来测试自己。这使您可以访问要测试的所有公共和私有内容。

此外,在脚本语言(具有 OO 能力,如 Python、Ruby 和 PHP)中,您可以让文件在运行时自行测试。确保您的更改不会破坏任何东西的好方法。这显然为测试所有类提供了一个可扩展的解决方案:只需运行它们。 (你也可以在其他语言中使用 void main 来执行此操作,它也总是运行它的测试)。


虽然这很实用,但它不是很优雅。这可能会造成代码库的混乱,也不允许您将测试与真实代码分开。外部测试的能力开启了脚本自动化测试的能力,而不是手动编写静态方法。
这并不妨碍您进行外部测试……只要您喜欢调用静态方法即可。代码库也不混乱……您可以相应地命名方法。我使用“runTests”,但任何类似的作品。
你的权利,它不排除外部测试,但它确实生成更多的代码,即使代码库混乱。每个类可能有很多私有方法来测试哪个在它的一个或多个构造函数中初始化它的变量。要进行测试,您将必须编写至少与要测试的方法一样多的静态方法,并且测试方法可能必须很大才能初始化正确的值。这将使代码的维护更加困难。正如其他人所说,测试类的行为是一种更好的方法,其余的应该足够小以进行调试。
我使用与其他人相同的行数进行测试(实际上会更少,因为您稍后会读到)。您不必测试所有私有方法。只是那些需要测试的 :) 你也不需要用单独的方法测试每一个。我一个电话就搞定了。这实际上使代码的维护变得更加困难,因为我所有的类都有相同的单元测试方法,它逐行运行所有私有和受保护的单元测试。然后,整个测试工具在我的所有类上调用相同的方法,并且维护都驻留在我的类中 - 测试和所有。
D
Damon Hogan

我想在这里创建一个清晰的代码示例,您可以在要测试私有方法的任何类上使用它。

在您的测试用例类中只包含这些方法,然后按照指示使用它们。

  /**
   *
   * @var Class_name_of_class_you_want_to_test_private_methods_in
   * note: the actual class and the private variable to store the 
   * class instance in, should at least be different case so that
   * they do not get confused in the code.  Here the class name is
   * is upper case while the private instance variable is all lower
   * case
   */
  private $class_name_of_class_you_want_to_test_private_methods_in;

  /**
   * This uses reflection to be able to get private methods to test
   * @param $methodName
   * @return ReflectionMethod
   */
  protected static function getMethod($methodName) {
    $class = new ReflectionClass('Class_name_of_class_you_want_to_test_private_methods_in');
    $method = $class->getMethod($methodName);
    $method->setAccessible(true);
    return $method;
  }

  /**
   * Uses reflection class to call private methods and get return values.
   * @param $methodName
   * @param array $params
   * @return mixed
   *
   * usage:     $this->_callMethod('_someFunctionName', array(param1,param2,param3));
   *  {params are in
   *   order in which they appear in the function declaration}
   */
  protected function _callMethod($methodName, $params=array()) {
    $method = self::getMethod($methodName);
    return $method->invokeArgs($this->class_name_of_class_you_want_to_test_private_methods_in, $params);
  }

$this->_callMethod('_someFunctionName', array(param1,param2,param3));

只需按照它们在原始私有函数中出现的顺序发出参数


C
Carl Bergquist

MbUnit 有一个很好的包装器,叫做 Reflector。

Reflector dogReflector = new Reflector(new Dog());
dogReflector.Invoke("DreamAbout", DogDream.Food);

您还可以从属性中设置和获取值

dogReflector.GetProperty("Age");

关于“私人测试”,我同意……在完美世界中。进行私人单元测试是没有意义的。但在现实世界中,您最终可能想要编写私有测试而不是重构代码。


仅供参考,在 Gallio/MbUnit v3.2 中,Reflector 已被更强大的 Mirror 取代。 (gallio.org/wiki/doku.php?id=mbunit:mirror)
J
Johnny_D

这是关于私有方法的单元测试的好article。但我不确定哪个更好,让您的应用程序专门为测试而设计(就像为测试创建测试一样)或使用反射进行测试。很确定我们大多数人会选择第二种方式。


j
jpchauny

在我看来,你应该只对你的类的公共 API 进行单元测试。

公开一个方法,以便对其进行单元测试,会破坏暴露实现细节的封装。

一个好的公共 API 解决了客户端代码的直接目标并完全解决了该目标。


这应该是国际海事组织的正确答案。如果您有很多私有方法,那么这可能是因为您有一个隐藏类,您应该将其分解为它自己的公共接口。
v
vsapiha

我使用 PrivateObject 类。但如前所述,最好避免测试私有方法。

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(retVal);

S
Samuel Liew
CC -Dprivate=public

“CC”是我使用的系统上的命令行编译器。 -Dfoo=bar 相当于 #define foo bar。因此,此编译选项有效地将所有私有内容更改为公共内容。


这是什么?这适用于 Visual Studio 吗?
“CC”是我使用的系统上的命令行编译器。 “-Dfoo=bar”相当于“#define foo bar”。因此,此编译选项有效地将所有私有内容更改为公共内容。哈哈!
在 Visual Studio 中,在构建环境中设置定义。
C
Chuck Savage

这是一个示例,首先是方法签名:

private string[] SplitInternal()
{
    return Regex.Matches(Format, @"([^/\[\]]|\[[^]]*\])+")
                        .Cast<Match>()
                        .Select(m => m.Value)
                        .Where(s => !string.IsNullOrEmpty(s))
                        .ToArray();
}

这是测试:

/// <summary>
///A test for SplitInternal
///</summary>
[TestMethod()]
[DeploymentItem("Git XmlLib vs2008.dll")]
public void SplitInternalTest()
{
    string path = "pair[path/to/@Key={0}]/Items/Item[Name={1}]/Date";
    object[] values = new object[] { 2, "Martin" };
    XPathString xp = new XPathString(path, values);

    PrivateObject param0 = new PrivateObject(xp);
    XPathString_Accessor target = new XPathString_Accessor(param0);
    string[] expected = new string[] {
        "pair[path/to/@Key={0}]",
        "Items",
        "Item[Name={1}]",
        "Date"
    };
    string[] actual;
    actual = target.SplitInternal();
    CollectionAssert.AreEqual(expected, actual);
}

k
kiriloff

一种方法是使用您的方法 protected 并编写一个测试夹具,该夹具继承您要测试的类。这样,您也不会转动您的方法 public,而是启用测试。


我不同意这一点,因为您还将允许您的消费者从基类继承并使用受保护的函数。这是您首先要通过将这些功能设为私有或内部来防止的事情。
a
anuj

1)如果您有遗留代码,那么测试私有方法的唯一方法是通过反射。

2)如果是新代码,那么您有以下选择:

使用反射(复杂)

在同一个类中编写单元测试(通过在其中包含测试代码使生产代码变得丑陋)

重构并在某种 util 类中公开该方法

使用 @VisibleForTesting 注释并删除私有

我更喜欢注解的方法,最简单,最不复杂。唯一的问题是我们提高了知名度,我认为这不是一个大问题。我们应该总是对接口进行编码,所以如果我们有一个接口 MyService 和一个实现 MyServiceImpl ,那么我们可以有相应的测试类,即 MyServiceTest(测试接口方法)和 MyServiceImplTest(测试私有方法)。无论如何,所有客户端都应该使用该接口,因此即使私有方法的可见性已经增加,它也并不重要。


A
Alex H

您还可以在调试模式下构建时将其声明为公共或内部(使用 InternalsVisibleToAttribute):

    /// <summary>
    /// This Method is private.
    /// </summary>
#if DEBUG
    public
#else
    private
#endif
    static string MyPrivateMethod()
    {
        return "false";
    }

它使代码膨胀,但在发布版本中它将是 private


S
Sarath

您可以从 Visual Studio 2008 为私有方法生成测试方法。当您为私有方法创建单元测试时,测试引用文件夹会添加到您的测试项目中,并且访问器会添加到该文件夹中。在单元测试方法的逻辑中也提到了访问器。此访问器允许您的单元测试调用您正在测试的代码中的私有方法。详情请看

http://msdn.microsoft.com/en-us/library/bb385974.aspx


C
Community

另请注意,InternalsVisibleToAtrribute 要求您的程序集为 strong named,如果您正在使用以前没有该要求的解决方案,这会产生它自己的一组问题。我使用访问器来测试私有方法。有关此示例,请参见 this question


不,InternalsVisibleToAttribute 要求您的程序集被强命名。我目前在一个并非如此的项目中使用它。
为了澄清这一点:“当前程序集和朋友程序集都必须是未签名的,或者都必须使用强名称签名。” - 来自 MSDN