"不要测试private方法"
这是个著名的原则。我基本上也同意。
但你是不是因为这条规则,放弃了真正需要的测试?
public和private的区别
public方法是稳定的接口——对外公开的概念窗口。针对它的测试自然也是稳定的。
而private方法是内部实现细节。重构时会增减,public方法变化时也会受影响。它本质上是不稳定的。针对它的测试自然也会不稳定。
不稳定测试的代价
针对不稳定方法的测试,维护成本太高。
想重构,但改测试的成本太大,于是放弃了。这种情况需要避免。
所以"不要测试private方法"才会成为原则。
共用逻辑的两难
多个public方法调用的共用逻辑。通过public方法测试的话,可能要在多处重复相同的验证。
直接测试共用逻辑,只需要写一处。
但正如前面所说,不稳定测试的代价很高。如果要直接测试private方法,必须仔细斟酌是否真的有必要。
"提取成单独的类"的疑问
"想测试的话,就提取成另一个类的public方法。"这是常见的建议。
当然,如果从设计角度看分离是合理的,那就应该提取。符合单一职责原则的拆分是好事。
但仅仅为了写测试而拆分类?那是另一回事。高内聚的类被拆散。原本private的逻辑变成了public(虽然是在另一个类里)。两者都应该避免。
为了测试的便利而扭曲生产代码,这应该不是我们的本意。
private的接口
private方法不是接口——这是常识。
但如果你强烈觉得某个private方法应该单独测试,那么它很可能是被多个public方法共享的,通往"内部概念"的窗口。
如果这个内部概念足够重要,就值得像设计public方法一样认真对待。结果会是:这个private方法变成了"稳定的内部接口"。针对它的测试也会变得稳定。
这本质上和"提取成单独的类"是一回事。整理代码,但不拆分、不公开——仅此而已。
原则与例外
遵守原则很重要。但不理解为什么就盲目遵守,是想当然。不应该仅仅因为可见性就否定正确的判断。
不过,适用的场景并不多。大多数private方法只是实现细节,或者是不需要单独测试的内部概念。
只有满足以下所有条件时,才允许例外:
- 你强烈觉得需要单独测试
- 它是不应该成为单独类的内部概念
- 代码已经整理成稳定的接口
缺一不可。否则,不要写那个测试。
而且,这种判断需要经验。如果团队里没有能做出判断的人,如果Code Review没有正常运作——那就全面禁止吧。
不要停在"因为是private"。多想一步。