Bob Jiang | 敏捷教练 | Scrum Master

敏捷培训 | Scrum培训 | 组织创新

什么时候不用测试驱动开发(TDD)

Posted at — Apr 24, 2020 阅读

我什么时候不使用TDD?

Alex Bunardzic一直是有关TDD及其反对者的 Twitter 话题中的一员。他的担忧之一是TDD的支持者同意TDD并不总是合适的,但没有说什么时候不用TDD。让我们探索一下。

亚历克斯的担忧似乎在于他正试图让组织中的人员使用TDD,其中许多人提出了异议。这些异议之一是,甚至专家都说他们并不总是使用TDD,所以我们为什么要在这里使用TDD。

现在,如果您接受销售培训^1^,那么您将要教的一件事是如何处理异议。第一项也是最强大的技术是忽略它们。在销售中,您可能会继续提出反对意见,这就是为什么我们很多人讨厌被出售的原因之一。

我没有卖任何东西,但似乎Alex负责在他的公司中销售TDD,所以我们可能有不同的担忧。我在写和讲授思想上的关注是可以理解的。我很乐意将决定权交给听众。如果我每月都有这么多TDD销售的配额,我可能会有所不同。

尽管如此,我确实知道有些情况下我通常不使用TDD,并且我或多或少乐于谈论它们。或多或少是因为TDD主题似乎不断地深入我的灵魂。无论如何,这里是:

我什么时候不用TDD?

让我数一数:我有时不用TDD的…

  1. 当手头没有合适的TDD工具时;
  2. 当我不知道如何测试某事时;
  3. 当输出本质上是可见时(可视化);
  4. 当程序是一个简单的,会扔掉的。

让我们通过示例进一步探讨其中的每一个情况。

当没有像样的测试工具可以立即使用时,我通常不用TDD。

一个例子是我iPad上的Codea Lua。它没有附带TDD工具,而且很长一段时间没有可用的工具。我很喜欢写一个玩玩,因为在Lua中反思很有趣,但从未真正得出我喜欢的东西。

然后出现了CodeaUnit,它工作得非常好,我最近在示例TDD会话中使用了它。但是请参阅以下部分。

另一个示例是Second Life的脚本语言LSL。该语言非常初级,不包含反射功能,这在您尝试生成一个体面的TDD框架时非常重要。如果没有可用的工具,则我用TDD的频率将比理想情况低。

***但是请注意:***缺少一个不错的工具并不是不使用TDD的很好理由。通常,我在Lua或LSL上的开发所花的时间要比遵循TDD学科所需的时间长。我怎么知道?我对使用TDD和不使用TDD时会发生的事情非常有经验,因此我很容易认识到缺少测试会伤害我的情况。

最好的迹象是调试会话需要更多的时间。这总是表明更多测试时我‘会做得更好。

如果工具不易使用,您是否应该考虑不使用TDD?

我认为这不是很好的理由。几乎可以肯定的是,无论您使用哪种语言,都可以使用一个很好的TDD工具。对我来说,如果是只写一些小小的一次性程序的人,寻找和学习该工具的投资可能不值得。您正在专业地工作。对于您来说,投资可能是值得的。

很可能,这对我来说是值得的。在没有TDD的情况下工作会使我更经常地进入调试模式。即使对于我的小型项目,TDD的价值也可能存在。我将自己不这样做的原因归咎于懒惰,说实话而不是出于智慧。

当输出本质上是可见(可视化)时,我通常不进行测试。

在Codea / Lua中,人们经常编写一些代码在屏幕上绘制运动对象。在《第二人生》中,我经常编写脚本来在虚拟世界中移动事物。尽管这些脚本中的某些部分可能会从TDD中受益,但总会有一些-其主要功能使我不知道如何有效地使用TDD。

让我们举两个例子。

想象一下,我想在iPad屏幕上绘制一个蒸汽引擎。该图片的一部分将是引擎驱动轮,该驱动轮会随着火车的移动而旋转。推杆安装在该轮的半径中间附近。该推杆的轮端绕转^2^左右。该杆的另一端在连接到蒸汽活塞的另一根水平杆的推动下水平地前后移动,该水平杆只是来回运动。

我们的任务是在轮子旋转时针对每个角度计算推杆的位置。为了弄清楚该如何做,我绘制了一些草图并做了一些几何处理(通常使用直角三角形),直到我弄清楚了在每个方向盘上的远端在哪个位置。

显然,当车轮为零时,推杆完全远离车轮,其端点为l + r,其中r为推杆圆的半径,l为杆的长度,假设车轮位于零位置。

当车轮处于180度时,终点将在l-r处。当车轮位于90或270时,终点将为…sqrt(l ^ 2-r ^ 2)。同时,如果角度为θ,则起点为<1,0> rotBy(θ)。

大概。多一点的几何使我相信终点是sqrt(l*l - y*y) - x,其中x和y是起点的相对坐标。

也许,有一种更好的方法来计算终点。它一定是某种 sin或cos (正弦或余弦)功能或类似的东西。但是这种方法很好用,因为我们有能力在两点之间画一条线。

所以我只是编写了代码,看起来像这样,没有进行大量清理:

-- Loco

function setup()
    theta = 0 -- degrees
    deltaTheta = 1
    origin = vec2(600,600)
    radius = 200
    rodRadius = radius/2
    rodLength = 500
end

function draw()
    background(40, 40, 50)
    strokeWidth(5)
    drawWheel()
    drawRod()
    theta = theta + deltaTheta

end

function radians(angleInDegrees)
    return angleInDegrees*math.pi/180
end

function drawRod()
    pushMatrix()
    local rodOrigin = vec2(0,rodRadius):rotate(radians(theta))
    local x = rodOrigin.x
    local y = rodOrigin.y
    translate(origin.x, origin.y)
    ellipse(rodOrigin.x, rodOrigin.y, 15)
    local rodEnd = math.sqrt(rodLength*rodLength - y*y) + x
    line(x,y, rodEnd,0)
    -- local dist = vec2(x,y):dist(vec2(rodEnd,0))
    -- print(dist)
    popMatrix()
end

function drawWheel()
    pushMatrix()
    noFill()
    translate(origin.x, origin.y)
    ellipse(0,0,2*radius)
    rotate(theta)
    line(-radius,0,radius,0)
    line(0,radius, 0, -radius)
    popMatrix()
end

这是运行时的视频,请点击这里。

您可能会注意到中的两行已注释掉drawRod。那些打印推杆线的长度。它始终是500。这是我可以想到的唯一测试,以确保我得到了正确的答案。在代码和屏幕上,相对于车轮为零是显而易见的。

在这个小程序中,我看不到任何需要TDD风格的测试,也看不到我知道如何进行测试。

但是有一些缺陷。首先,有几次我不喜欢绘制项目的位置或大小。我只是看了看图片并改变了数字。其次,在通过创建局部x和y进行优化时,我以某种方式弄乱了对translate()的调用,该调用使推杆自身被原点拉低。所以我解决了。

该程序很好地实现了,老实说,我不知道TDD对我有什么帮助。但是,在某些时候我没有信心,至少有一点在代码中有实际错误。因此,如果我在TDD方面比较擅长,那么使用它可能会做得更好。

***在第二人生中也会发生同样的事情。***在第二人生中,我的脚本通常会四处移动,这基本上是通过设置x,y,z坐标来完成的。为了使运动看起来平滑,您需要经常调整一下坐标。计算与Lua示例中的计算非常相似,有些正弦,余弦,旋转和开根。我通过运行程序并观察其外观来"测试”。

第二人生中有一件特别有趣的事情,增加了乐趣。如果给对象一个奇怪的不正确的坐标集,则它很容易飞到那里,离开您的视线,不知道它的去向或原因。

通常的事情是在移动到<0,0,0>之前,或者当移动的时间超过某个合理的距离,或者在某个边界框之外时,停止程序。我发现丢失一两个对象之后,我总是进行这些检查。

TDD可以帮助我避免这种麻烦吗?我完全不确定。与Lua那个案例一样,我从数字上也不知道想要什么:我必须看一下才能决定我是否喜欢这种效果。

我经常编写有缺陷的代码,并经常希望我有更好的测试,或者更好地约束代码。但是我很少使用TDD,这是因为没有合适的工具,也因为我不确定如果拥有工具,我将如何使用它。

这是否意味着您不应该使用TDD,因为您的输出是可视的?

我不确定。仅仅因为我有时不知道如何做某事并不意味着该事没有用。这甚至不意味着在我不知道该怎么做的情况下它就不会有用。在这些非TDD情况下,我确实确实感觉到我有更多的错误,并且花了更多的时间进行调试。

这是我的中心案例:

当我无法思考如何测试某些东西时,我通常不使用TDD。

如果没有一个测试框架,我不能轻易考虑如何测试的东西,尤其是东西没有明确的数字或其他明确的答案。我确实看到了如何在《第二人生》中测试几个FIFO列表功能,不久前,我为一个朋友编写了这些功能,并包括一个测试这些功能的"主程序”。

我很高兴,因为第二人生中的列表操作有点笨拙,实际上我确实犯了一些错误,这些错误是我的测试正确发现的。

但是在很多时候,我看不到编写显式测试的方法,而我又依靠打印和类似的调试技巧。

因此,我可以提供建议:如果看不到如何编写测试,请不要使用TDD?我不这么认为。我敢肯定,如果您看不到如何使用TDD,那么TDD的主要目的就是更好地使用它。因此,我尝试看到使用它的新方法,并且我希望听众和读者也这样做。

我"应该"看到更多的测试方法,今年我看到的比去年更多。如果我看不到如何测试某些东西,我会原谅自己,但这与建议即使现在还看不到怎么做也不建议进行一些测试不一样。

这对那些没有像我这样二十年TDD经验的人意味着什么?

好吧,我还在学习。如果我到目前为止还没有很好地掌握如何TDD的方法,那么很容易退回到我的许多其他有用技术上,例如调试或代码祈祷。但是多年来,我一直在尝试学习事物,并继续尝试使我更接近于TDDing一切的事物。

我很高兴自己走过了那段时间。

我经常不会对一个简单的一次性程序用TDD。

有时,您只想对某些文件用正则表达,或者从一堆网页或其他一些简单的脚本中提取一些记录。通常,当出现这样的情况时,我会继续写它,因为一旦获取了想要的信息,我将把它扔掉。

如果我只是把它扔掉,那么用TDD似乎并不值得。

这是否意味着我认为您应该针对要运行一次的程序跳过TDD,然后将其丢弃?

好吧,我比这里的其他情况更接近确定,但是我经常花足够的时间进行编辑和重新运行,以使我认为该程序的核心部分可能会从某些TDD中受益。

通常,该位是正则表达式或位于其中心的任何抓取/转换部分。通常,需要多次尝试才能实现该目标。例如,跨越多行的正则表达式总是在过程中的某个地方咬我。(比较难)

因此,即使在这里,我也建议您,懒惰并不是您跳过TDD的好借口。如果您想找借口,那可能是个不错的借口。

总结

经过二十多年的TDD学习,实践和试验,毫无疑问,在某些情况下我不使用TDD。有趣的是,即使在我可以合理化不使用TDD的情况下,如果我一直在使用TDDing,我也会经常遇到很可能被TDD发现的问题。

这有点像您不戴护目镜,将肥皂溅到眼睛上,或者不戴手套迅速对东西进行脱脂,手变干,干裂,闻起来很难闻,或者先做辣椒然后擦眼。

您就像” Dayum,我应该做……"。

很多时候,当我不使用TDD时,我会有所感觉。

我最好的建议是学习TDD并继续学习,使用TDD并继续努力……在那些您根本不能做的事情或只是不想做的情况下……更好地注意到TDD会有所帮助的地方。

然后下定决心。不要来找我做TDD的许可,当然也不要不要我做。

自己动手犯错,这是我的建议。


  1. 我确实接受了销售培训。我不是一个体面的推销员。培训要求采取令我反感的行为。

  2. 火车上的车轮转着转…… 

作者: Ron Jeffries
译者: Bob Jiang
原文链接