debugging 究竟什么是'沙夫挤压'的方法来寻找一个错误?

fquxozlt  于 2023-03-03  发布在  其他
关注(0)|答案(3)|浏览(247)

我读过肯特Beck关于Saff Squeeze方法的原始博客post,也读过this InfoQ post,它详细阐述了这个主题,但没有提供任何示例。
我知道这本质上是一种不依赖调试器就能找到bug的方法,但是我发现肯特的例子并不那么清楚。
有没有更开明的人可以用一个清晰、具体的例子来教我如何使用这种方法?它也有望成为其他研究这种方法的人的学习资源。

bvhaajcl

bvhaajcl1#

Saff Squeeze是一种系统技术,用于从失败的测试中删除测试代码和非测试代码,直到测试和代码小到足以理解。
我同意Kent's original description of the Saff Squeeze有点困难,部分原因是他正在测试的软件JUnit是高度抽象的,部分原因是他没有给出足够的步骤2的示例,"在测试中比现有Assert更早地放置(失败的)Assert。"
在他的第一轮测试中,他只是将Assert移到了测试中的更高位置,他对后续步骤的总结可能会让您认为在第2步中唯一可以做的事情就是移动现有的Assert,但是到了最后一步,他已经提出了一个新的、更简单的失败Assert,第2步中的Assert可以只是一个在测试中移到更高位置的现有Assert,这是很常见的。但它也可以是随着您对代码和bug的理解的发展而产生的新问题。
这里有一个例子。它太简单了,无法need 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调用Autopilot. carry_out(纬度: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
  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,让我们看看是否可以在测试中更高的位置写一个失败的Assert:

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

啊,新的Assert也失败了,"预期的飞行计划(纬度: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

我们正在取得一些进展,但我仍然不知道哪里出了问题。再次使用Better 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得到正确的Runway,这种情况就会过去。

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

很好,新Assert失败,"预期跑道(id:1)却得到了天桥骄子(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

我们已经将原来的测试和代码精简到了closest_available中存在bug的程度--应该使用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

现在,我应该把一个失败的Assert放在测试中更高的位置吗?我不能--bug在测试的最后一行。最终我将被迫意识到,在我内联它之前,它在closest_available中。

t5fffqht

t5fffqht2#

样本显示他正在复制(inlining)将被测代码内嵌到单元测试中。然后从开始到结束分别测试代码的各个部分。这使他能够隔离测试每个路径,并在最小可能的单元上进行生产单元测试。其中一个测试将演示缺陷,您将能够修复缺陷。他所展示的示例取决于Eclipse内联方法的能力,如果您不具备这种能力,则需要手动执行(将调用的代码复制到单元测试中)。

s1ag04yj

s1ag04yj3#

这实质上是一个代码树的深度优先搜索,通过遍历单元测试中的代码树来找到bug,然后修剪掉任何不包含bug的分支。
我怀疑,如果代码包含导致观察到的(错误)行为的多个问题,那么这可能会有问题。
如果你在代码树中得到一个有大量子分支的a节点(一个很长的方法),我更喜欢使用启发式二分搜索来识别bug所在的位置:)

相关问题