对不起,这么长回答,但是我认为如果我全部经历了这个过程,我的思维过程会更加连贯。
由于此问题标记为TDD,因此我假定您正在编写方法TDD样式。如果是的话,你可能要开始:
describe "my_square_function" do
it "Squares a positive number" do
my_square_function(1).should == 1
end
end
有一个失败的测试,你可以实现my_square_function
如下:
def my_square_function(number)
1
end
现在,测试合格要重构出复制。在这种情况下,代码和测试之间的重复,即文字1.由于参数带有测试的值,所以我们可以使用参数来删除重复。
def my_square_function(number)
number
end
现在,复制已被删除,测试仍然可以通过,我们可以移动到下一个测试:
describe "my_square_function" do
it "Squares a positive number" do
my_square_function(1).should == 1
end
it "Squares a negative number" do
my_square_function(-1).should == 1
end
end
运行你再次失败的测试迎来了测试,所以我们使它传:
def my_square_function(number)
number.abs # of course I probably wouldn't really do this but
# hey, it's an example. :-)
end
现在这个测试通过,它的时间移动到另一个测试:
describe "my_square_function" do
it "Squares a positive number" do
my_square_function(1).should == 1
end
it "Squares a negative number" do
my_square_function(-1).should == 1
end
it "Squares other positive numbers" do
my_square_function(2).should == 4
end
end
在这一点上,你最新的测试将不再通过,所以现在要让它通过:
def my_square_function(number)
number.abs * number
end
哎呀。这不起作用,它导致我们的负数测试失败。幸运的是,失败表明我们回到了无效的确切测试,但我们知道由于“负面”测试而失败。回到代码:
def my_square_function(number)
number.abs * number.abs
end
这样比较好,我们所有的测试都通过了。现在是时候重新构造了。在这里,我们在abs
的调用中看到一些其他不必要的代码。我们可以摆脱他们:
def my_square_function(number)
number * number
end
测试仍然通过,我们看到一些与讨厌的论点更多的重复。让我们看看我们是否可以摆脱它:
def my_square_function(number)
number ** 2
end
测试通过,我们不再有这种重复。现在,我们有一个干净的实现,让我们处理nil
情况下一:
describe "my_square_function" do
it "Squares a positive number" do
my_square_function(1).should == 1
end
it "Squares a negative number" do
my_square_function(-1).should == 1
end
it "Squares other positive numbers" do
my_square_function(2).should == 4
end
it "Doesn't try to process 'nil' arguments" do
my_square_function(nil).should == nil
end
end
好了,我们又回到了再次失败,我们可以继续前进,实现nil
检查:
def my_square_function(number)
number ** 2 unless number == nil
end
这测试通过,它很干净,所以我们会保持原样。现在我们回到规范,看看我们有什么,并验证我们喜欢我们所看到的:
describe "my_square_function" do
it "Squares a positive number" do
my_square_function(1).should == 1
end
it "Squares a negative number" do
my_square_function(-1).should == 1
end
it "Squares other positive numbers" do
my_square_function(2).should == 4
end
it "Doesn't try to process 'nil' arguments" do
my_square_function(nil).should == nil
end
end
我的第一个倾向是,我们真的描述“平方”的数字,行为不函数本身,所以我们将改变它:
describe "How to square a number" do
it "Squares a positive number" do
my_square_function(1).should == 1
end
it "Squares a negative number" do
my_square_function(-1).should == 1
end
it "Squares other positive numbers" do
my_square_function(2).should == 4
end
it "Doesn't try to process 'nil' arguments" do
my_square_function(nil).should == nil
end
end
现在,这三个示例名称在放入该上下文中时会有点松动。我将从第一个例子开始,对于方块1,这似乎有点俗气。这是我将要减少代码中的示例数量的选择。我真的希望这些例子以某种方式变得有趣,否则我不会测试它们。平方1和2之间的区别是无趣的,所以我将删除第一个例子。它起初是有用的,但不再是。这使得我们有:
describe "How to square a number" do
it "Squares a negative number" do
my_square_function(-1).should == 1
end
it "Squares other positive numbers" do
my_square_function(2).should == 4
end
it "Doesn't try to process 'nil' arguments" do
my_square_function(nil).should == nil
end
end
我要看看接下来的事情就是反面的例子,因为它涉及到在描述块的上下文。我要去给它和实例的新描述的其余部分:
describe "How to square a number" do
it "Squaring a number is simply the number multiplied by itself" do
my_square_function(2).should == 4
end
it "The square of a negative number is positive" do
my_square_function(-1).should == 1
end
it "It is not possible to square a 'nil' value" do
my_square_function(nil).should == nil
end
end
现在,我们已经限制了测试用例的数量最有趣的,我们真的没有太多要处理用。正如我们上面看到的那样,如果发现另一个测试案例,我们不希望发生故障,那么知道故障发生在哪条线路上真是令人高兴。通过构建一个场景列表,我们会失去该功能,从而难以调试失败。现在,我们可以用另一个解决方案中提到的动态生成的it
块代替示例,但是我们开始失去了我们试图描述的行为。因此,总而言之,通过将您的测试场景限制为仅描述系统有趣特性的场景,您将减少太多场景的需求。在一个更复杂的系统中,有许多场景可能会强调对象模型可能需要另一个外观。
希望有帮助!
布兰登
+1为给出有意义的名称来测试输入输出组合,帮助您了解为什么需要组合。容易的事情..但很少见。有没有办法自定义应该失败的消息(无需编写自定义匹配器)? – Gishu
谢谢布兰登!这是严格的TDD的一个很好的解释。你的方法明确了为什么每个数据点都很重要,而不是仅仅在函数中抛出一堆示例。 – brahn
@Gishu我不知道如何在没有自定义匹配器的情况下自定义消息,虽然它们很容易编写,所以我不会害怕这样做。 – bcarlso