假设我正在编写一个矢量结构
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建议我首先编写一个测试来揭示这个bug然后修复生产代码。另一方面,有人说测试用例应该测试行为而不是实现,所以我没有足够的理由来添加Vector3d(1, 4, 3).ortho
的新测试,因为这是使这两个测试不同的具体实现细节
那么,我应该添加什么测试来揭示这个错误?
出现这个问题是因为ortho
吸气剂正在做一些与它预期行为无关的事情,当然那些不相关的行为没有经过测试。我可以将那些(即largestAbsIndex,indexNextToLargest)提取到其他一些方法,然后分别测试它们。但仍有问题。这些方法当然应该是私有的,而我不能也不应该测试私有方法。有人说我可以将这些方法提取到另一个类,如果不可避免地需要测试私有方法,则测试该类。我可能会提取enum
或Vector3dComponentIndex
之类的内容。但是,我仍然无法忍受像公开的那样,因为只有在Vector3d
在任何一种方法中(提取到私有方法或提取到应该是私有的类/结构/枚举),存在如何增加私有代码中的测试覆盖率的问题?
答案 0 :(得分:0)
在这里要考虑的重要事项是为什么我们试图测试行为而不是实现。我们试图避免的是与当前实现密切相关的测试,我们必须在重构或更改实现时始终不断更改它们。这是我所看到的TDD的好处之一;它迫使你首先考虑该单元的界面,它将如何与周围的事物相互作用。
在您的情况下,您所做的断言是正确的:得到的向量必须是正交的。这与该方法的任何给定实现很好地分离; 然而我们写它,必须是真的。
因此,我认为更改当前测试或添加另一个测试,使用不同的起始矢量来揭示您已识别的边缘情况是完全合理的。如果您尝试将其修复为错误,那就是您要做的事情:编写一个可以重现错误的测试。至关重要的是,如果您以后更改了实施方案,这个新测试仍然会通过,因此它没有指导该指南旨在避免的问题。