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


479

我正在建立一个具有一些公共和私有方法的类库。我希望能够对私有方法进行单元测试(主要是在开发过程中,但它对将来的重构很有用)。

正确的方法是什么?


3
我可能会遗漏一些东西,或者仅仅是这个问题,pre-historic就Internet年而言,但是对私有方法的单元测试现在既简单又直接,Visual Studio在需要时生成必要的访问器类,片段预填充了测试逻辑,这简直太接近简单功能测试可能需要的片段了。参见例如。msdn.microsoft.com/zh-CN/library/ms184807%28VS.90%29.aspx
mjv 2011年


发问者可能未使用Visual Studio
Dave 2014年

3
不要对内部构件进行
Mark Seemann

Answers:


122

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


86
uck 这将被编译到您发布的程序集中。
杰伊

14
@Jay-无法使用#if DEBUGInternalsVisibleTo属性使它不适用于发布代码吗?
mpontillo 2010年

20
@Mike,可以,但是然后只能在调试代码上运行单元测试,而不能发布代码。由于发布代码已优化,因此您可能会看到不同的行为和不同的时间。在多线程代码中,这意味着您的单元测试将无法正确检测竞争条件。更好的方法是通过下面的@AmazedSaint的建议使用反射,或使用内置的PrivateObject / PrivateType。假设您的测试工具以完全信任的方式运行(本地运行的MSTest),这使您可以在Release版本中查看私有版本
Jay 2010年

121
我想念什么?当它实际上没有回答测试私有方法的特定问题时,为什么会被接受呢?InternalsVisibleTo仅公开标记为Internal的方法,而不公开OP请求的那些私有方法(以及我登陆此处的原因)。我想我需要继续使用PrivateObject,答案是Seven?
达伦·刘易斯

6
@Jay我知道这是有点晚了,来了,但一个选择是使用类似#if RELEASE_TEST周围InternalsVisibleTo像迈克建议,并让你的发布版本配置的副本定义RELEASE_TEST。您可以通过优化来测试发布代码,但是当您实际构建发布时,将省略测试。
Shaz

349

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

  1. 如果您要测试的方法确实值得测试,则可能值得将其移入自己的类。
  2. 向调用私有方法的公共方法添加更多测试,以测试私有方法的功能。(正如评论员所指出的,只有在这些私有方法的功能确实是公共接口的一部分时,才应该这样做。如果它们实际上执行了对用户隐藏的功能(即单元测试),则可能是不好的)。

38
选项2使单元测试必须具有该功能的基础实现的知识。我不喜欢那样做。我通常认为单元测试应该在不假设实现的情况下测试功能。
爱马仕

15
测试实现的缺点是,如果对实现进行任何更改,测试将很难打破。这是不可取的,因为重构与在TDD中编写测试一样重要。
JtR

30
好吧,如果您更改实现,则测试应该会失败。TDD将意味着首先更改测试。
sleske

39
@sleske-我不太同意。如果功能未更改,则没有理由应该终止测试,因为测试实际上应该是在测试行为/状态而不是实现。这就是jtr的含义,它使您的测试变得脆弱。在理想的情况下,您应该能够重构代码并让测试仍然通过,从而验证重构没有改变系统的功能。
2009年

26
为幼稚而道歉(到目前为止还没有太多的测试经验),但是不是单元测试的概念可以单独测试每个代码模块吗?我真的不明白为什么应该从这个想法中排除私有方法。
2011年

118

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

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[] {/* ... */} );

2
您如何调用私有静态方法?
StuperUser 2012年

18

3
从VS 2011开始不推荐使用测试私有方法的访问器方法。blogs.msdn.com/b/visualstudioalm/archive/2012/03/08/…–
Sanjit Misra

1
这里阅读在Microsoft网站上找到的文档时,我看不到任何提及不推荐使用PrivateObject类的内容。我正在使用MSVS 2013,它可以按预期工作。
stackunderflow 2013年

3
@RyanGates您引用的站点上的第一个解决方案指出,访问私有成员的解决方案是“使用PrivateObject类来帮助访问代码中的内部和私有API。可以在Microsoft.VisualStudio.QualityTools.UnitTestFramework中找到它。 dll程序集。”
stackunderflow

78

我不同意“您应该只对测试外部接口感兴趣”的理念。这有点像说汽车维修店应该只进行测试,以检查车轮是否转动。是的,最终我对外部行为感兴趣,但是我更喜欢自己的,私有的内部测试,更具体一点。是的,如果我进行重构,则可能必须更改一些测试,但是除非进行大规模重构,否则我只需要更改一些测试,而其他(未更改)内部测试仍然有效的事实就说明了这一点。重构已经成功。

您可以尝试仅使用公共接口来涵盖所有内部案例,并且理论上可以通过使用公共接口来全面测试每种内部方法(或至少重要的每种方法),但是您可能最终不得不站起来以实现这以及通过公共接口运行的测试用例与旨在测试的解决方案的内部之间的联系可能很难辨认或无法辨别。指出了,确保内部机器正常运行的单个测试非常值得重构带来的微小测试更改-至少这是我的经验。如果您必须在每次重构时都对测试进行重大更改,那么也许这没有意义,但是在那种情况下,也许您应该完全重新考虑设计。


20
恐怕我还是不同意你的看法。将每个组件都视为黑匣子,可以无问题地将模块交换进/交换出。如果您有一个FooService必须要做的事情X,那么您只需要关心的是它确实会X在需要时执行。它怎么做并不重要。如果类中的问题无法通过接口辨别(不太可能),则该问题仍然有效FooService。如果通过界面可见的问题,对公共成员的测试应该可以检测到它。关键在于,只要车轮正确旋转,就可以用作车轮。
2012年

5
一种通用的方法是,如果您的内部逻辑足够复杂,以至于您认为它需要进行单元测试,则可能需要将其提取到带有可进行单元测试的公共接口的某种帮助程序类中。然后,您的“父级”类可以简单地使用此帮助器,并且可以对每个人进行适当的单元测试。
Mir

9
@Basic:此答案中的逻辑完全错误。需要私有方法的典型情况是需要一些代码由公共方法重用。您将此代码放入某些PrivMethod。该方法不应公开,但需要进行测试以确保依赖PrivMethod的公共方法确实可以依赖它。
Dima

6
@Dima当然,如果有问题PrivMethod,应该检查PubMethod哪个调用PrivMethod可以暴露它?更改SimpleSmtpService为a后会GmailService怎样?突然之间,您的私人测试指向的是不再存在的代码,或者可能以不同的方式工作并且可能会失败,即使该应用程序可以按设计完美运行​​。如果对两个电子邮件发件人都需要进行复杂的处理,那么也许应该在一个EmailProcessor可以供两个人使用并分别进行测试的文件中进行处理?
基本的

2
@miltonb我们可能会从不同的开发风格着眼。WRT内部,我不倾向于对其进行单元测试。如果有问题(通过接口测试确定),则可以通过附加调试器来轻松跟踪,或者该类太复杂并且应该拆分(使用已测试的新类单元的公共接口)IMHO
基本

51

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

班级:

...

protected void APrivateFunction()
{
    ...
}

...

测试子类:

...

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

...

1
您甚至可以将该子类放在单元测试文件中,而不用弄乱实际类。+1狡猾。
蒂姆·阿贝尔

如果可能,我总是将所有与测试相关的代码放在单元测试项目中。这只是伪代码。
杰森·杰克逊

2
此功能不是私有的,受保护的是最终结果……您使代码的安全性降低/暴露于子类型的私有功能
战争

22

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

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

重构的结果意味着私有方法现在是一个单独的类,已经成为原始类的协作者。通过自己的单元测试,它的行为将变得众所周知。

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

我看到这类似于开车。当我开车时,我不会在不装上引擎盖的情况下开车,所以我可以看到发动机在运转。我依靠汽车提供的接口,即转速表和车速表来了解发动机是否在工作。我依靠的事实是,当我踩油门踏板时,汽车实际上会在行驶。如果要测试引擎,可以单独进行检查。:D

当然,如果您有旧版应用程序,那么直接测试私有方法可能是最后的选择,但我希望将旧版代码重构以实现更好的测试。迈克尔·费瑟斯(Michael Feathers)就这一主题写了一本很棒的书。http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052


16
此答案中的逻辑完全错误。需要私有方法的典型情况是需要一些代码由公共方法重用。您将此代码放入某些PrivMethod。此方法不应公开,但需要进行测试以确保依赖PrivMethod的公共方法确实可以依赖它。
Dima

在最初的开发过程中有意义,但是您是否想要在标准回归分析中测试私有方法?如果是这样,如果实现发生变化,则可能会破坏测试套件。OTOH,如果您的回归测试仅关注外部可见的公共方法,那么如果私有方法在稍后中断,则回归套件仍应检测到错误。然后,如有必要,可以根据需要清除旧的专用测试。
Alex Blakemore

9
不同意,您应该只测试公共接口,否则为什么需要私有方法。在这种情况下,将它们全部公开并进行测试。如果您正在测试私有方法,则会破坏封装。如果要测试私有方法并在多个公共方法中使用它,则应将其移到其自己的类中并进行隔离测试,然后所有公共方法都应委派给该新类。原始类”接口,您可以验证行为没有改变,并且可以对委托的私有方法进行单独的测试。
Big Kahuna

@Big Kahuna-如果您认为在任何情况下都不需要对私有方法进行单元测试,那么您从来没有使用过足够大/复杂的项目。很多时候,诸如客户特定验证之类的公共功能仅以20行结尾,它们仅调用非常简单的私有方法以使代码更具可读性,但是您仍然必须测试每个单独的私有方法。测试20倍于公共功能将使单元测试失败时很难首次亮相。
Pedro.The.Kid

1
我为FTSE 100公司工作。我想我已经看过几个复杂的项目,谢谢。如果需要测试到该级别,则应单独测试作为单独协作者的每个私有方法,因为这意味着它们具有需要测试的单独行为。然后,对主要中介对象的测试就变成了交互测试。它只是测试是否调用了正确的策略。您的情况听起来像是所讨论的类未遵循SRP。它没有更改的唯一原因,而是20 => SRP违规。阅读GOOS书或Bob叔叔。YMWV
Big Kahuna

17

私有类型,内部成员和私有成员之所以如此,是因为某些原因,并且通常您不想直接与它们混淆。而且,如果这样做,您很有可能会在以后休息,因为无法保证创建这些程序集的人会保留私有/内部实现。

但是,有时候,当进行一些hacked / explored或第三方程序集时,我自己最终想初始化一个私有类或一个带有私有或内部构造函数的类。或者,有时,在处理我无法更改的预编译遗留库时,我最终针对私有方法编写了一些测试。

因此诞生了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();

12

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

  1. 您可以创建PrivateObject类的实例,语法如下

    PrivateObject obj= new PrivateObject(PrivateClass);
    //now with this obj you can call the private method of PrivateCalss.
    obj.PrivateMethod("Parameters");
  2. 您可以使用反射。

    PrivateClass obj = new PrivateClass(); // Class containing private obj
    Type t = typeof(PrivateClass); 
    var x = t.InvokeMember("PrivateFunc", 
        BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public |  
            BindingFlags.Instance, null, obj, new object[] { 5 });

好的答案,但是对于#1来说,您的语法是错误的。您需要声明PrivateClassfirst 的实例并使用它。stackoverflow.com/questions/9122708/…–
SharpC

10

我还使用了InternalsVisibleToAttribute方法。也值得一提的是,如果您不满意将内部以前的私有方法用于实现此目的,那么也许无论如何它们都不应该成为直接单元测试的主题。

毕竟,您正在测试的是类的行为,而不是特定的实现 -您可以更改后者而不更改前者,并且测试仍然可以通过。


我喜欢有关测试行为而非实现的观点。如果将单元测试与实现(私有方法)联系在一起,则测试将变得脆弱,并且当实现发生更改时需要进行更改。
鲍勃·霍恩


8

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

8
生成的文件仅存在于VS2005中。在2008年,它们在幕后产生。他们是一种憎恶。并且关联的Shadow任务在构建服务器上很不稳定。
鲁宾·巴特林克

在VS2012-2013中,访问器也已弃用。
Zephan Schroeder

5

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

您可以在这里找到该文章:

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



4

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


您将在生产版本中包括测试访问器(以测试编译器优化等),但在发行版本中将它们排除在外。但是我要分开头发,无论如何我还是赞成,因为我认为将这些东西放在一个地方是个好主意。谢谢你的主意。
CAD家伙

4

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

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

因此,您应该避免使用InternalsVisibleToAtrribute。

这是Ian Cooper的精彩演讲,涵盖了这个主题:Ian Cooper:TDD,这哪里出错了?


3

有时,测试私有声明可能会很好。从根本上说,编译器只有一个公共方法:Compile(string outputFileName,params string [] sourceSFileNames)。我相信您知道如果不测试每个“隐藏”声明,将很难测试这种方法!

这就是为什么我们创建Visual T#:以便进行更轻松的测试的原因。它是一种免费的.NET编程语言(与C#v2.0兼容)。

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

看看我们的网站:下载的免费


3

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

此外,使用脚本语言(具有OO功能,如Python,Ruby和PHP),您可以在运行文件时对其进行测试。不错的快速方法,可确保您所做的更改不会破坏任何内容。显然,这为测试所有类提供了可扩展的解决方案:只需全部运行它们。(您也可以在其他语言中使用void main来执行此操作,该main也始终运行其测试)。


1
尽管这很实用,但不是很优雅。这可能会使代码库有些混乱,也不允许您将测试与实际代码分开。进行外部测试的能力使脚本能够自动执行测试,而无需手动编写静态方法。
达伦·里德

这并不妨碍您进行外部测试...只需随意调用静态方法即可。代码库也不是混乱的……您可以相应地命名方法。我使用“ runTests”,但是任何类似的作品。
乡巴佬

您的权利,它不排除从外部进行测试,但是它确实生成了更多的代码,即使代码库混乱。每个类可能有很多专用方法来测试在其一个或多个构造函数中初始化变量的方法。要进行测试,您将必须编写至少与要测试的方法一样多的静态方法,并且测试方法可能必须很大才能初始化正确的值。这会使代码的维护更加困难。正如其他人所说,测试类的行为是一种更好的方法,其余的应该足够小以进行调试。
达伦·里德

我使用与其他人相同的行数进行测试(实际上要少一些,以后再读)。您不必测试所有私有方法。只是那些需要测试的方法:)您也不需要使用单独的方法来测试每个方法。我只打一个电话。实际上,这使代码的维护变得很困难,因为我的所有类都具有相同的总体单元测试方法,该方法逐行运行所有私有和受保护的单元测试。然后,整个测试工具会在我所有的类上调用相同的方法,并且所有维护都在我的类中-测试及全部。
rube

3

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

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

  /**
   *
   * @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));

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


3

对于任何想要运行私有方法而又不费吹灰之力的人。这适用于任何单元测试框架,仅使用良好的旧反射功能。

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

2

MbUnit为此提供了一个很好的包装,称为Reflector。

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

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

dogReflector.GetProperty("Age");

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


4
只是为了提供信息,Reflector已被MirrorGallio / MbUnit v3.2中功能更强大的功能所取代。(gallio.org/wiki/doku.php?id=mbunit:mirror
晏Trevin

2

这是一篇关于私有方法的单元测试的好文章。但是我不确定有什么更好的选择,是让您的应用程序专门为测试而设计(就像创建仅用于测试的测试)还是使用反射来进行测试。可以肯定,我们大多数人都会选择第二种方式。


2

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

为了对它进行单元测试,将方法公开,破坏了封装,暴露了实现细节。

好的公共API可以解决客户端代码的近期目标,并可以完全解决该目标。


这应该是IMO的正确答案。如果您有很多私有方法,那可能是因为您有一个隐藏的类,您应该将其分解为自己的公共接口。
删除

2

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

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

2
CC -Dprivate=public

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


2
这是什么?这是否适用于Visual Studio?
YeahStu

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

在Visual Studio中,在构建环境中设置一个定义。
马克·哈里森

1

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

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

1

一种方法是拥有您的方法protected并编写一个测试夹具,该夹具继承要测试的类。这样,您也public无需改变方法,但是可以启用测试。


我不同意这一点,因为您还将允许使用者从基类继承并使用受保护的函数。首先,您想通过将这些功能设为私有或内部功能来防止这种情况。
Nick N.

1

1)如果您有旧版代码,则测试私有方法的唯一方法是通过反射。

2)如果是新代码,则可以选择以下选项:

  • 使用反射(要复杂)
  • 在同一个类中编写单元测试(通过在其中包含测试代码来使生产代码很难看)
  • 在某种util类中重构并公开该方法
  • 使用@VisibleForTesting批注并删除私有

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


1

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

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

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


0

您可以从Visual Studio 2008中为私有方法生成测试方法。为私有方法创建单元测试时,会将Test References文件夹添加到测试项目中,并将访问器添加到该文件夹​​中。访问器在单元测试方法的逻辑中也被称为。该访问器允许您的单元测试在要测试的代码中调用私有方法。详情请看

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


0

还要注意,InternalsVisibleToAtrribute要求您的程序集必须使用强命名,如果您在以前没有该要求的解决方案中工作,则会产生一系列问题。我使用访问器来测试私有方法。请参阅此问题的示例。


2
不,InternalsVisibleToAttribute 要求你的组件被强命名。我目前在不是这种情况的项目上使用它。
科迪·格雷

1
为了澄清这一点:“当前程序集和朋友程序集都必须未签名,或者都必须使用强名称签名。” -来自MSDN
哈里
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.