您提出了几个问题/问题,因此我将分别处理每个问题。
如何测试私有方法?
除了是否应该测试私有方法的辩论/讨论之外,还有一些方法可以做到这一点。
重构
一个广泛的普遍答案是,您可以将行为重构为原始类利用的单独的可测试类。这是有争议的,并不总是适用,具体取决于您的设计或这样做的特权。
InternalsVisibleTo
一个常见的例程是将可测试的逻辑提取到方法中并将其标记为internal
. 在您的程序集属性中,您可以添加一个属性[InternalsVisibleTo("MyUnitTestingProject")]
。这将允许您从单元测试项目中访问该方法,同时仍然隐藏对所有其他程序集的访问。http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.internalsvisibletoattribute.aspx
但是,鉴于您的评论,您无法在工作场所永久更改源代码的结构;您正在更改访问器以进行测试,测试,然后在提交之前将它们更改回来。在这种情况下,有两种选择:
部分测试类
如果您将类标记为partial
. 创建第二个部分类文件,该文件将包含您的测试(或私有成员的公共包装器)。然后当需要合并/提交时,只需从项目中删除您的部分类并partial
从主类中删除关键字。此外,您可以使用if DEBUG
(或其他指令)包装整个测试代码文件,以便它仅在单元测试时可用并且不会影响生产/开发代码。
http://weblogs.asp.net/ralfw/archive/2006/04/14/442836.aspx
public partial class MyClass
{
private string CreateTempString()
{
return "Hello World!";
}
}
#if DEBUG
public partial class MyClass //in file "MyClass_Accessor.cs"
{
public string CreateTempString_Accessor()
{
return CreateTempString();
}
}
#endif
反射
你仍然可以通过反射访问私有成员: public class Test { private string PrivateField = "private"; }
Test t = new Test();
var publicFieldInfo = typeof(Test).GetField("PrivateField", BindingFlags.Instance | BindingFlags.NonPublic);
Console.WriteLine(publicFieldInfo.GetValue(t)); //outputs "private"
您的单元测试可以通过这种方式提取类中的私有/隐藏数据。事实上,微软提供了两个类来为你做这件事:PrivateObject和PrivateType
鉴于您的内部开发流程限制,这可能是您最好的选择,因为您将能够在主项目库之外管理自己的测试,而无需更改核心代码中的任何内容。
请注意,Silverlight(以及可能的其他 Core-CLR 运行时)在反射期间严格执行非公共访问,因此此选项在这些情况下不适用。
所以,有几种方法可以测试私人成员,我敢肯定还有一些更聪明/不那么聪明的方法潜伏在那里。
是否可以通过使用 __ 前缀命名所有私有方法或引入私有但可访问的访问修饰符来实现所有这些好处?
您(引用其他人)引用的好处是:
- 为了使实现更清晰/避免 IDE 自动完成中的长长的垂直方法列表
- 向全世界宣布哪些方法是公共接口,哪些方法可能会改变并且仅用于实现目的
- 可读性
现在您补充说,这些都可以通过 __或通过更改语言规范和/或支持私有但可访问访问修饰符的 IDE 来实现,可能使用一些类似 unsafe 的关键字来阻止这一点。我认为就更改语言和 IDE 的当前功能/行为(可能对 StackOverflow 没有意义)进行辩论是不值得的,因此关注可用的内容:
1) 更简洁的实现和智能感知
Visual Studio IDE(我不能代表 MonoDevelop)确实支持在使用 [EditorBrowsableAttribute] 标记成员时从智能感知中隐藏成员。但这仅在开发人员在其 Visual Studio 选项中启用“隐藏高级成员”选项时才有效。(请注意,当您在同一个程序集中工作时,它不会抑制智能感知中的成员)
http://msdn.microsoft.com/en-us/library/system.componentmodel.editorbrowsableattribute.aspx
因此,将公共成员标记为这样会使其(智能感知)表现为内部(不支持 [InternalsVisibleTo])。因此,如果您在同一个程序集中,或者如果您没有Hide Advanced Members
启用,您仍然会在智能感知中看到一长串 __ 成员。即使您将其从智能感知中隐藏,它仍然可以根据其当前的访问修饰符完全访问。
2) 公共使用接口/约定
这假定 C#、Visual Basic、F#、C++.NET 和任何 .NET 开发世界中的所有开发人员都将采用相同的 __ 命名约定,并在编译和交换程序集时遵守它开发商之间。也许如果您在 IronPython 中编写脚本,您可以摆脱它,或者如果您的公司内部采用这种方法。但一般来说,这不会发生,.NET 开发人员可能会犹豫利用采用这种约定的库,因为这不是一般的 .NET 文化。
3) 可读性
这与#2 一致,因为“可读性”取决于文化和该领域的开发人员期望什么;这当然是有争议的和主观的。我敢打赌,大多数 C# 开发人员发现严格/强制封装可以显着提高代码可读性,我敢肯定他们中的很大一部分会发现 __ 经常使用会减损这一点。(另一方面,我确信开发人员为私有字段采用 _ 或 __ 前缀并仍然保持私有的情况并不少见)
但是,C# 中的可读性和封装不仅仅是公共/私有访问器。在 C# 中,有 private、public、protected internal、protected 和 internal(我错过了一个吗?)每个都有自己的用途,并为开发人员提供不同的信息。现在我不确定您将如何仅通过 __ 与这些访问器进行通信。建议单下划线是受保护的,双下划线是私有的,这肯定会妨碍可读性。
有没有什么是通过访问限制实现的,而在不限制访问的情况下通过命名约定无法实现?
如果你问为什么C# 设计团队会走这条路,那么我想你总有一天要问 Hejlsberg 先生。我知道他们正在创建一种收集 C/C++ 最好部分的语言,并强烈关注面向对象原则的原则。
至于通过访问修饰符强制访问来实现什么:更能保证 API 消费者的正确访问。如果您的类使用MakeTempStringForXMLSerialization
将字符串存储为类属性以进行序列化的方法,但出于性能原因放弃了昂贵的检查(因为您,作为开发人员已完成单元测试以确保所有类的字段都将通过公共 API 有效)然后第三方做了一些可爱的垃圾进垃圾出,他们会责怪你和/或供应商的劣质图书馆。这公平吗?不必要; 他们把垃圾放进去,但现实是很多人仍然会责怪供应商。
对于试图了解 API 工作原理的新开发人员来说,这有助于简化他们的体验。是的,开发人员应该阅读文档,但如果公共 API 是直观的(通常应该如此)并且不会暴露大量不应访问的成员,那么它就不会那么混乱,而且他们不太可能意外地喂垃圾进入系统。它还将降低让开发人员轻松有效地使用和利用您的 API 的开销。当涉及到您发布的 API 的任何更新,您希望更改/重构/改进您的内部实现细节时,情况尤其如此。
因此,从商业角度来看,它可以保护他们免受责任和与客户的不良关系,并且更能吸引开发商购买和投资。
现在这一切都可能是这种情况,正如你所说,如果每个人都遵循__
不应该在课堂之外访问成员的约定,或者unsafe
在你说的地方提供一些标记,“如果你这样做,它会破坏,这不是我的错! " 那么您与 C# .NET 开发不在同一个星球上。C# 提供的访问器提供了该__
约定,但确保所有开发人员都遵守它。
有人可能会争辩说,受限访问是一种错觉,因为消费者可以通过反射来解决它(如上所示),因此访问修饰符和 __ (或其他)符号之间实际上没有编程差异。(尽管在 Silverlight/Core-CLR 上,肯定存在程序上的差异!)但是开发人员访问这些私有字段所要做的工作是你给消费者一扇敞开的门,上面写着“不要进去” “(你希望他们能读懂)和一扇带锁的门,他们必须砸下来。
那么它到底提供了什么? 对成员的标准化、强制访问,其中 as__
提供对成员的非标准化、非强制访问。此外,__
缺乏各种可用访问修饰符提供的描述范围。
更新(2013 年 1 月 2 日)
我知道已经半年了,但我一直在阅读 C# 语言规范,并从第2.4.2 节标识符中发现了这个小宝石,其中指出:
包含两个连续下划线字符 (U+005F) 的标识符保留供实现使用。例如,一个实现可能会提供以两个下划线开头的扩展关键字。
我想不会发生任何不好的事情,如果你这样做,很可能不会发生灾难性的破坏。但这只是规范建议您不要在代码中使用双下划线时应该考虑的另一个方面。