如何增加私有方法的测试覆盖率
How to increase test coverage in private method
假设我正在编写一个向量结构
struct Vector3d {
private var v: Double[3]
init(_ x: Double, _ y: Double, _ z: Double) { v = [x, y, z] }
init?(value: Double, at index: Int) {
guard 0...2 ~= index else { return nil }
v = [0, 0, 0]
v[index] = value
}
var x: Double { return v[0] }
var y: Double { return v[1] }
var z: Double { return v[2] }
var ortho: Vector3d { // return an arbitrary unit vector which is orthogonal to self
// Idea: find a vector that is not collinear with self and then cross product of that vector and self will be orthogonal to self
var largestAbsIndex = 0
largestAbsIndex = fabs(x) > fabs(y) ? fabs(x) > fabs(z) ? 0 : 2 : fabs(y) > fabs(z) ? 1 : 2
var indexNextToLargest = largestAbsIndex - 1
if indexNextToLargest < 0 { indexNextToLargest = 2 }
return (self ** Vector3d(value: 1, at: indexNextToLargest)!).normalized
}
static func **(lhs: Vector3d, rhs: Vector3d) -> Vector3d { /* cross product */}
var normalized: Vector3d { /* some code */ }
}
并且在某些时候,我想让组件索引基于 1,即 1...3
而不是 0...2
。我更改了第二个初始化程序和相应的测试,全部为绿色
init?(value: Double, at index: Int) {
guard 1...3 ~= index else { return nil }
v = [0, 0, 0]
v[index - 1] = value
}
实际上,ortho
属性 也取决于初始值设定项,也应进行更改。但是我在我的大部分测试中使用了 Vector3d(1, 2, 3)
,所以当它 运行 进入 ortho
测试时,它仍然有效,因为 largestAbsIndex = 2, indexNextToLargest = 1
-- 真幸运。但是,Vector3d(1, 4, 3).ortho
会崩溃,因为 largestAbsIndex = 1, indexNextToLargest = 0
和 Vector3d(value: 1, at: 0) == nil
我发现了这个错误,很明显如何修复它。但是 TDD 建议我应该先编写一个测试来揭示这个错误,然后再修复生产代码。另一方面,有人说测试用例应该测试行为而不是实现,所以我没有足够的借口添加 Vector3d(1, 4, 3).ortho
的新测试,因为正是具体的实现细节使这两个测试不同。
那么,我应该添加什么测试来揭示这个错误?
出现这个问题是因为 ortho
getter 正在做一些与其预期行为无关的事情,当然不相关的行为没有得到测试。我可能会将这些(即 largestAbsIndex、indexNextToLargest)提取到其他一些方法中,然后分别对其进行测试。但仍然存在问题。这些方法当然应该是私有的,而我不能也不应该测试私有方法。有人说我可以将这些方法提取到另一个 class 并测试 class 如果不可避免地需要测试私有方法。我可能会提取 enum
或类似 Vector3dComponentIndex
的内容。但我仍然无法忍受像 public 这样的东西,因为它只有在 Vector3d
中使用时才有意义
在任何一种方法中(提取到私有方法或提取到应该私有的class/struct/enum),都存在一个问题,即如何增加私有代码中的测试覆盖率?
这里要考虑的重要事情是为什么我们尝试测试行为而不是实施。我们试图避免的是测试与当前实现紧密相关,以至于我们在重构或更改实现时必须一直更改它们。在我看来,这是 TDD 的好处之一;它迫使您首先考虑该单元的界面,以及它如何与周围的事物交互。
在您的情况下,您所做的断言是正确的:生成的向量必须是正交的。这与该方法的任何给定实现都很好地分离; 不管我们怎么写,那一定是真的。
因此,我认为更改当前测试或添加另一个具有不同起始向量的测试是完全合理的,以暴露您已识别的边缘情况。如果你想把它作为一个错误来修复,你就会这样做:编写一个重现它的测试。至关重要的是,如果您稍后更改实施,这个新测试将 仍有望通过 ,因此它没有指南旨在避免的问题。
假设我正在编写一个向量结构
struct Vector3d {
private var v: Double[3]
init(_ x: Double, _ y: Double, _ z: Double) { v = [x, y, z] }
init?(value: Double, at index: Int) {
guard 0...2 ~= index else { return nil }
v = [0, 0, 0]
v[index] = value
}
var x: Double { return v[0] }
var y: Double { return v[1] }
var z: Double { return v[2] }
var ortho: Vector3d { // return an arbitrary unit vector which is orthogonal to self
// Idea: find a vector that is not collinear with self and then cross product of that vector and self will be orthogonal to self
var largestAbsIndex = 0
largestAbsIndex = fabs(x) > fabs(y) ? fabs(x) > fabs(z) ? 0 : 2 : fabs(y) > fabs(z) ? 1 : 2
var indexNextToLargest = largestAbsIndex - 1
if indexNextToLargest < 0 { indexNextToLargest = 2 }
return (self ** Vector3d(value: 1, at: indexNextToLargest)!).normalized
}
static func **(lhs: Vector3d, rhs: Vector3d) -> Vector3d { /* cross product */}
var normalized: Vector3d { /* some code */ }
}
并且在某些时候,我想让组件索引基于 1,即 1...3
而不是 0...2
。我更改了第二个初始化程序和相应的测试,全部为绿色
init?(value: Double, at index: Int) {
guard 1...3 ~= index else { return nil }
v = [0, 0, 0]
v[index - 1] = value
}
实际上,ortho
属性 也取决于初始值设定项,也应进行更改。但是我在我的大部分测试中使用了 Vector3d(1, 2, 3)
,所以当它 运行 进入 ortho
测试时,它仍然有效,因为 largestAbsIndex = 2, indexNextToLargest = 1
-- 真幸运。但是,Vector3d(1, 4, 3).ortho
会崩溃,因为 largestAbsIndex = 1, indexNextToLargest = 0
和 Vector3d(value: 1, at: 0) == nil
我发现了这个错误,很明显如何修复它。但是 TDD 建议我应该先编写一个测试来揭示这个错误,然后再修复生产代码。另一方面,有人说测试用例应该测试行为而不是实现,所以我没有足够的借口添加 Vector3d(1, 4, 3).ortho
的新测试,因为正是具体的实现细节使这两个测试不同。
那么,我应该添加什么测试来揭示这个错误?
出现这个问题是因为 ortho
getter 正在做一些与其预期行为无关的事情,当然不相关的行为没有得到测试。我可能会将这些(即 largestAbsIndex、indexNextToLargest)提取到其他一些方法中,然后分别对其进行测试。但仍然存在问题。这些方法当然应该是私有的,而我不能也不应该测试私有方法。有人说我可以将这些方法提取到另一个 class 并测试 class 如果不可避免地需要测试私有方法。我可能会提取 enum
或类似 Vector3dComponentIndex
的内容。但我仍然无法忍受像 public 这样的东西,因为它只有在 Vector3d
在任何一种方法中(提取到私有方法或提取到应该私有的class/struct/enum),都存在一个问题,即如何增加私有代码中的测试覆盖率?
这里要考虑的重要事情是为什么我们尝试测试行为而不是实施。我们试图避免的是测试与当前实现紧密相关,以至于我们在重构或更改实现时必须一直更改它们。在我看来,这是 TDD 的好处之一;它迫使您首先考虑该单元的界面,以及它如何与周围的事物交互。
在您的情况下,您所做的断言是正确的:生成的向量必须是正交的。这与该方法的任何给定实现都很好地分离; 不管我们怎么写,那一定是真的。
因此,我认为更改当前测试或添加另一个具有不同起始向量的测试是完全合理的,以暴露您已识别的边缘情况。如果你想把它作为一个错误来修复,你就会这样做:编写一个重现它的测试。至关重要的是,如果您稍后更改实施,这个新测试将 仍有望通过 ,因此它没有指南旨在避免的问题。