我已经阅读了有关Saff Squeeze方法的肯特·贝克(Kent Beck)的原始博客post。我还阅读了this InfoQ post,它详细说明了该主题,但未提供任何示例。
我知道这实质上是一种不依赖调试器就可以发现错误的方法。但是我发现肯特的例子不清楚。
能否有一个更开明的人通过清晰明确的示例来教育我如何使用这种方法?希望它也可以作为其他学习该方法的人的学习资源。
最佳答案
Saff Squeeze是一种系统技术,可从失败的测试中删除测试代码和非测试代码,直到测试和代码小到可以理解为止。
我同意Kent's original description of the Saff Squeeze有点困难,部分是因为他正在测试的软件JUnit非常抽象,部分是因为他没有给出足够的步骤2的示例,“在测试中将(失败的)断言比现有的早断言。”
在他的第一轮中,他只是在测试中将断言上移,而他对后续步骤的总结可能会使您认为您在步骤2中唯一可以做的就是移动现有断言,但是到了最后一步,他想出了一个新的,更简单的失败断言。第2步中的断言可以只是在测试中上移的现有断言,这很常见,但也可以是随着对代码的理解和错误的发展而提出的新断言。
这是一个例子。需要Saff Squeeze太简单了,但是它说明了该技术。
我刚刚写了这个关键任务类:
class Autopilot
def self.fly_to(city)
runways_in_city = runways_in city
runway = closest_available runways_in_city
flight_plan = flight_plan_to runway
carry_out flight_plan
end
def self.runways_in(city)
Airport.where(city: city).map(&:runways).flatten
end
def self.closest_available(runways)
runways.select { |r| r.available? }
.sort_by { |r| distance_between current_position, r.position }.last
end
def self.flight_plan_to(runway)
FlightPlan.new runway.latitude, runway.longitude
end
# other methods left to the imagination
end
这是我编写的第一个rspec示例进行测试:
describe Autopilot
describe ".fly_to" do
it "flies to the only available runway" do
Autopilot.stub(:current_position) { Position.new 0, 0 }
nearby_runway = create :runway, latitude: 1, longitude: 1
create :runway, city: nearby_runway.city, latitude: 2, longitude: 2
flight_plan = FlightPlan.new nearby_runway.latitude, nearby_runway.longitude
# Think of the following line as being at the end of the example, since that's when it takes effect
Autopilot.should_receive(:carry_out).with flight_plan
Autopilot.fly_to nearby_runway.airport.city
end
end
end
哦,不-最后一行失败,并显示以下消息:“期望失败:预期使用FlightPlan(纬度:1,经度:1)调用Autopilot.carry_out,但是使用FlightPlan(纬度:2,经度:2)调用”。我不知道那是怎么回事。我们最好使用Saff Squeeze。
内联方法(为本地重命名以避免名称冲突):
it "flies to the only available runway" do
Autopilot.stub(:current_position) { Position.new 0, 0 }
nearby_runway = create :runway, latitude: 1, longitude: 1
create :runway, city: nearby_runway.city, latitude: 2, longitude: 2
flight_plan = FlightPlan.new nearby_runway.latitude, nearby_runway.longitude
Autopilot.should_receive(:carry_out).with flight_plan
runways_in_city = runways_in city
runway = closest_available runways_in_city
actual_flight_plan = flight_plan_to runway
Autopilot.carry_out actual_flight_plan
end
我看不出最后一行是否能满足期望,只要它获得了正确的
FlightPlan
。让我们看看是否可以在测试中更高处编写一个失败的断言:it "flies to the only available runway" do
Autopilot.stub(:current_position) { Position.new 0, 0 }
nearby_runway = create :runway, latitude: 1, longitude: 1
create :runway, city: nearby_runway.city, latitude: 2, longitude: 2
flight_plan = FlightPlan.new nearby_runway.latitude, nearby_runway.longitude
Autopilot.should_receive(:carry_out).with flight_plan
runways_in_city = runways_in city
runway = closest_available runways_in_city
actual_flight_plan = flight_plan_to runway
actual_flight_plan.should == flight_plan
Autopilot.carry_out actual_flight_plan
end
啊,新的断言也失败了,“预期的FlightPlan(纬度:1,经度:1),但是得到了FlightPlan(纬度:2,经度:2)”。好,让我们简化测试:
it "flies to the only available runway" do
Autopilot.stub(:current_position) { Position.new 0, 0 }
nearby_runway = create :runway, latitude: 1, longitude: 1
create :runway, city: nearby_runway.city, latitude: 2, longitude: 2
flight_plan = FlightPlan.new nearby_runway.latitude, nearby_runway.longitude
runways_in_city = runways_in city
runway = closest_available runways_in_city
actual_flight_plan = flight_plan_to runway
actual_flight_plan.should == flight_plan
end
我们到了某个地方,但我仍然看不到有什么问题。更好的Saff Squeeze再次嵌入
flight_plan_to
:it "flies to the only available runway" do
Autopilot.stub(:current_position) { Position.new 0, 0 }
nearby_runway = create :runway, latitude: 1, longitude: 1
create :runway, city: nearby_runway.city, latitude: 2, longitude: 2
flight_plan = FlightPlan.new nearby_runway.latitude, nearby_runway.longitude
runways_in_city = runways_in city
runway = closest_available runways_in_city
actual_flight_plan = FlightPlan.new runway.latitude, runway.longitude
actual_flight_plan.should == flight_plan
end
好吧,只要
flight_plan_to
获得正确的跑道,那显然就会过去。断言:it "flies to the only available runway" do
Autopilot.stub(:current_position) { Position.new 0, 0 }
nearby_runway = create :runway, latitude: 1, longitude: 1
create :runway, city: nearby_runway.city, latitude: 2, longitude: 2
flight_plan = FlightPlan.new nearby_runway.latitude, nearby_runway.longitude
runways_in_city = runways_in city
runway = closest_available runways_in_city
runway.should == nearby_runway
actual_flight_plan = FlightPlan.new runway.latitude, runway.longitude
actual_flight_plan.should == flight_plan
end
好的,新的断言失败,并带有“预期的Runway(id:1)但得到了Runway(id:2)”。再次简化测试:
it "flies to the only available runway" do
Autopilot.stub(:current_position) { Position.new 0, 0 }
nearby_runway = create :runway, latitude: 1, longitude: 1
create :runway, city: nearby_runway.city, latitude: 2, longitude: 2
runways_in_city = runways_in city
runway = closest_available runways_in_city
runway.should == nearby_runway
end
我们已经将原始测试和代码修剪到明显的bug在
closest_available
中的位置-应该使用first
而不是last
。但是,如果仍然不明显怎么办?好吧,让我们尝试再次Saff Squeeze,内联
closest_available
:it "flies to the only available runway" do
Autopilot.stub(:current_position) { Position.new 0, 0 }
nearby_runway = create :runway, latitude: 1, longitude: 1
create :runway, city: nearby_runway.city, latitude: 2, longitude: 2
runways_in_city = runways_in city
runway = runways_in_city.select { |r| r.available? }
.sort_by { |r| Autopilot.distance_between Autopilot.current_position, r.position }.last
runway.should == nearby_runway
end
现在,我将在测试中将失败的断言放在哪里更高呢?我不能-该错误位于测试的最后一行。最终,在内联之前,我将不得不意识到它已经在
closest_available
中了。