测试驱动开发:如果错误出现在界面中怎么办?

发布于 2024-08-03 00:57:35 字数 1555 浏览 3 评论 0原文

我读了最新的编码恐怖帖子,其中一条评论触动了我的神经为我:

这是测试驱动设计/重构应该解决的情况类型。如果(如果)您对接口进行了测试,则重写实现是没有风险的,因为您将知道是否捕获了所有内容。

现在理论上我喜欢测试驱动开发的想法,但每次我试图让它发挥作用时,效果都不是特别好,我改掉了这个习惯,接下来我就知道了我所进行的所有测试。原来写的不仅不通过,而且不再反映系统的设计。

如果你从一开始就得到了一个完美的设计(根据我的经验,这实际上从未发生过),那一切都很好,但是如果在系统生产的过程中你注意到存在一个严重的缺陷,该怎么办?设计?那么它就不再是一个简单的深入研究并修复“bug”的问题了,你还必须重写所有的测试。一个基本假设是错误的,现在你必须改变它。现在测试驱动开发不再是一件方便的事情,但这只是意味着做每件事都需要两倍的工作量。

我之前曾尝试向同行和网上问过这个问题,但我从未听到过非常满意的答案。 ...哦等等...问题是什么?

如何将测试驱动开发与必须更改的设计结合起来以反映对问题空间不断增长的理解?如何让 TDD 实践对你有利而不是不利?

更新: 我仍然认为我没有完全理解这一切,所以我无法真正决定接受哪个答案。我的大部分理解飞跃都发生在评论部分,而不是答案中。这是迄今为止我最喜欢的收藏:

“任何使用“无风险”等术语的人 在软件开发方面确实很全面 狗屎。但不要仅仅放弃 TDD 因为它的一些支持者 非常容易受到炒作的影响。我找到了 帮助我澄清我之前的想法 编写一大块代码可以帮助我 重现错误并修复它们,并使 我对重构更有信心 当事情开始变得丑陋时”

——克里斯托弗·约翰逊

“在这种情况下,您重写测试 仅针对界面的部分 已经改变的,并考虑 你自己很幸运能得到好的测试 其他地方的报道会告诉你 还有哪些对象依赖于它。”

-rcoder

“在 TDD 中,编写测试的原因 就是做设计。制作的理由 测试自动化,这样您就可以 重用它们作为设计和代码 发展。当测试失败时,意味着 你以某种方式违反了之前的规定 设计决策。也许那是一个 你想要改变的决定,但它是 很高兴尽快得到反馈 有可能。”

克里斯托弗·约翰逊

[关于测试接口]“测试将插入一些元素, 检查尺寸是否对应 插入的元素数量,检查 contains() 对它们返回 true 但不是为了那些不存在的事情 插入,检查remove()是否有效, 等等。所有这些测试都是 所有实现都相同,并且 当然你会运行相同的代码 对于每个实现而不是复制 它。所以当界面发生变化时, 你只需要调整测试 编码一次,而不是每个编码一次 实施。”

——迈克尔·博格沃特

I read the latest coding horror post, and one of the comments touched a nerve for me:

This is the type of situation that test driven design/refactoring are supposed to fix. If (big if) you have tests for the interfaces, rewriting the implementation is risk-free, because you will know whether you caught everything.

Now in theory I like the idea of test driven development, but all the times I've tried to make it work, it hasn't gone particularly well, I get out of the habit, and next thing I know all the tests that I had originally written not only don't pass, but they're no longer a reflection of the design of the system.

It's all well and good if you've been handed a perfect design from on high, straight from the start (which in my experience never actually happens), but what if halfway through the production of a system you notice that there's a critical flaw in the design? Then it's no longer a simple matter of diving in and fixing "the bug", but you also have to rewrite all the tests. A fundamental assumption was wrong, and now you have to change it. Now test driven development is no longer a handy thing, but it just means that there's twice as much work to do everything.

I've tried to ask this question before, both of peers, and online, but I've never heard a very satisfactory answer. ... Oh wait.. what was the question?

How do you combine test driven development with a design that has to change to reflect a growing understanding of the problem space? How do you make the TDD practice work for you instead of against you?

Update:
I still don't think I fully understand it all, so I can't really make a decision about which answer to accept. Most of my leaps in understanding have happened in the comments sections, not in the answers. Here' s a collection of my favorites so far:

"Anyone who uses terms like "risk-free"
in software development is indeed full
of shit. But don't write off TDD just
because some of its proponents are
hyper-susceptible to hype. I find it
helps me clarify my thinking before
writing a chunk of code, helps me to
reproduce bugs and fix them, and makes
me more confident about refactoring
things when they start to look ugly"

-Kristopher Johnson

"In that case, you rewrite the tests
for just the portions of the interface
that have changed, and consider
yourself lucky to have good test
coverage elsewhere that will tell you
what other objects depend on it."

-rcoder

"In TDD, the reason to write the tests
is to do design. The reason to make
the tests automated is so that you can
reuse them as the design and code
evolve. When a test breaks, it means
you've somehow violated an earlier
design decision. Maybe that's a
decision you want to change, but it's
good to get that feedback as soon as
possible."

-Kristopher Johnson

[about testing interfaces] "A test would insert some elements,
check that the size corresponds to the
number of elements inserted, check
that contains() returns true for them
but not for things that weren't
inserted, checks that remove() works,
etc. All of these tests would be
identical for all implementations, and
of course you would run the same code
for each implementation and not copy
it. So when the interface changes,
you'd only have to adjust the test
code once, not once for each
implementation."

–Michael Borgwardt

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

一生独一 2024-08-10 00:57:36

这已经不是一个简单的问题了
潜入并修复“错误”,但是
你还必须重写所有
测试。

TDD 的基本信条是避免生产代码和测试代码中的重复。如果单个设计更改意味着您必须重写所有内容,那么您就没有执行 TDD(或者根本没有正确执行)。

理想情况下,在一个设计良好且适当分离关注点的系统中,设计更改是局部的,就像实现更改一样。虽然现实世界很少是理想的,但您通常仍然会得到介于两者之间的东西:您必须更改一些生产代码和一些测试,但不是全部,并且更改大多很简单,并且可能会发生变化。甚至可以通过重构工具自动完成。

it's no longer a simple matter of
diving in and fixing "the bug", but
you also have to rewrite all the
tests.

A fundamental creed of TDD is to avoid duplication both in the production code AND in the test code. If a single design change means you have to rewrite everything, you weren't doing TDD (or not doing it correctly at all).

Ideally, in a well-designed system with proper separation of concerns, design changes are local, just like implementation changes. While the real world is rarely ideal, you still usually get something in between: you have to change some of the production code and some of the tests, but not everything, and the changes are mostly simple and may even be done automatically by refactoring tools.

渡你暖光 2024-08-10 00:57:36

在不知道什么在 UI 中最有效的情况下编写代码,同时编写单元测试。这是非常耗时的。最好开始制作一些 GUI 原型以获得正确的交互..然后用单元测试重写它(如果您的雇主允许)。

Coding something without knowing what will work best in the UI, while at the same time writing unittests. That is very time consuming. It's better to start out making some prototypes of the GUI to get the interaction right.. and then rewrite it with unittests (if you employer allows you).

离去的眼神 2024-08-10 00:57:36

持续集成(CI)是关键之一。如果您的测试在每次签入源代码管理时自动运行(如果失败,其他人都会看到它),那么更容易避免“陈旧”测试并保持正常状态。

正如迪亚斯先生提到的,婴儿学步很重要。您进行小型重构,然后运行测试。如果测试失败,您可以立即确定这是预期的(设计更改)还是失败的重构。当测试真正独立时(伴随练习),这很少会很困难。慢慢地改进你的设计。

另请参阅http://thought-tracker。 blogspot.com/2005/11/notes-on-pragmatic-unit-testing.html - 一定要买这本书!

编辑:也许我以错误的方式看待这个问题。假设您有一个想要重新设计的遗留代码库。我要做的第一件事是为当前行为添加测试。没有测试的重构是有风险的——你可能会改变行为。之后,我将开始逐步清理设计,并在每个步骤后运行单元测试。这会让我相信我的更改不会破坏任何东西。

在某些时候,API 可能会发生变化。这将是一个重大变化——客户端必须更新。测试会告诉我这一点 - 这很好,因为我必须更新任何现有的客户端(包括测试)。

现在这不是 TDD。但想法是相同的 - 测试是行为规范(是的,我正在融入 BDD),它们让我有信心重构实现,同时确保我保留行为(以及让我知道什么时候我更改接口)。

在实践中,我发现 TDD 可以为我提供有关不良界面设计的即时反馈。我是我的第一个客户 - 我知道我的 API 何时难以使用。

Continuous Integration (CI) is one key. If your tests run automatically every time you check in to source control (and everyone else sees it if they fail), it's easier to avoid "stale" tests and stay in the green.

As Mr. Dias mentioned, Baby Steps are important. You make a small refactoring, you run your tests. If tests break, you immediately determine if this is expected (design change) or a failed refactoring. When tests are truly independent (comes with practice), this is seldom very difficult. Evolve your design slowly.

See also http://thought-tracker.blogspot.com/2005/11/notes-on-pragmatic-unit-testing.html - and definitely buy the book!

EDIT: Perhaps I'm looking at this the wrong way. Say you had a legacy codebase that you wanted to redesign. The first thing I would try to do is add tests for the current behavior. Refactoring without tests is risky - you might change behavior. After that, I would start to clean up the design, in small steps, running my unit tests after each step. That would give me confidence that my changes weren't breaking anything.

At some point the API might change. This would be a breaking change - clients would have to be updated. The tests would tell me this - which is good, because I'd have to update any existing clients (including the tests).

Now that's not TDD. But the idea is the same - the tests are specifications of behavior (yes, I'm shading into BDD), and they give me the confidence to refactor the implementation while insuring that I preserve the behavior (as well as letting me know when I change the interface).

In practice, I've found TDD gives me immediate feedback on poor interface design. I'm my first client - I know when my API is hard to use.

绮筵 2024-08-10 00:57:36

我们倾向于在 TDD 中进行更少的预先设计,因为我们知道它会发生变化。我经历过巨大的旋转项目(它是一个网络应用程序,不,它是一个 RESTful 服务器,不,它是一个机器人)。这些测试使我能够比未经测试的代码更轻松地重构和改进代码。虽然这看起来很矛盾,但这是事实——即使您有更多代码,您也能够进行重大更改,并且有信心现有功能没有任何问题。

我理解您担心基本假设的改变会让您放弃测试。这看起来很直观,但我个人还没有见过。有些测试通过了,但大多数仍然有效——重大变化通常并不像乍看起来那么重要。另外,随着您编写测试的能力越来越强,您往往会编写不那么脆弱的测试,这会有所帮助。

We tend to do much less design up front with TDD, knowing it can change. I have taken projects through huge gyrations (it's a web app, no it's a RESTful server, no it's a bot). The tests provide me with the ability to refactor and restructure and evolve your code much more easily than untested code. Although it seems contradictory, it is true-- even though you have more code, you are able to make major changes and have confidence that nothing has broken in the existing functionality.

I understand your concern that fundamental assumptions changing make you throw out tests. This seems intuitive, but I personally haven't seen it. Some tests go, but most are still valid-- often a major change isn't as major as it seems at first. Plus, as you get better at writing tests, you tend to write less brittle ones, which helps.

回忆躺在深渊里 2024-08-10 00:57:35

TDD 的实践之一是使用婴儿步骤(一开始可能会很无聊),即使用非常小的步骤来帮助您了解您的问题空间并为您的问题制定良好且令人满意的解决方案。

如果您已经了解应用程序的设计,那么您根本就没有在进行 TDD。我们应该在进行测试时设计它。

因此,我给出的建议是让您专注于婴儿步骤,以获得适当的可测试设计

One of the practices of TDD is the use of Baby Steps (which could be very boring in the beggining) which is the use of really small steps in order for you to understand your problem space and make a good and satisfactory solution for your problem.

If you already know the design of your application you aren't doing TDD at all. We should design it while doing your tests.

So the suggestion I would give is for you to concentrate on the baby steps in order to get a proper testable design

回心转意 2024-08-10 00:57:35

我不认为 TDD 的任何真正实践者会声称它完全消除了错误或回归的可能性。

请记住,TDD 从根本上讲是关于设计,而不是测试或质量控制。说“我所有的测试都通过了”并不意味着“我完成了”。

如果您的需求或高层设计发生了巨大变化,那么您可能需要放弃所有测试以及所有代码。有时候事情就是这样。这并不意味着 TDD 没有帮助您。

I don't think any real practitioner of TDD will claim that it completely eliminates the possibility of error or regression.

Remember that TDD is fundamentally about design, not about testing or quality control. Saying "all my tests pass" does not mean "I'm finished."

If your requirements or high-level design change drastically, then you may need to throw away all your tests along with all the code. That's just how things are sometimes. It doesn't mean that TDD isn't helping you.

粉红×色少女 2024-08-10 00:57:35

如果应用得当,TDD 实际上应该让您在面对不断变化的需求时变得更加轻松。

根据我的经验,易于测试的代码是与其他子系统正交的代码,并且具有明确定义的接口。有了这样的起点,重写应用程序的重要部分就容易得多,因为您可以放心地工作,因为您知道 a) 您的更改将被隔离到几个子系统,b) 任何损坏都会很快显示为失败的测试。

另一方面,如果您只是在设计代码后对其进行单元测试,那么当需求发生变化时,您很可能会遇到问题。当子系统发生变化时(因为它们有效地标记了回归)而快速失败的测试与那些脆弱的测试之间存在差异,因为它们依赖于太多不相关的系统状态部分。前者应该可以通过几行代码来修复,而后者可能会让你摸不着头脑几个小时试图解开它们。

Properly applied, TDD should actually make your life a lot easier in the face of changing requirements.

In my experience, code that is easy to test is code that is orthogonal from other subsystems, and which has clearly defined interfaces. Given such a starting point, it is much easier to rewrite significant portions of your application, since you can work with confidence knowing that a) your changes will be isolated to a few subsystems, and b) any breakage will quickly show up as failing tests.

If, on the other hand, you're just slapping unit tests on your code after it has been designed, then you may well have problems when requirements change. There's a difference between tests that fail quickly when subsystems change (because they're effectively flagging regressions) and those that are brittle, because they depend on too many unrelated pieces of system state. The former should be fixable by a few lines of code, while the latter may leave you scratching your head for hours trying to unravel them.

猫弦 2024-08-10 00:57:35

唯一正确的答案是这取决于

  • 有一些方法会导致 TDD 错误,比如
    它不适合你
    环境和吃力气
    最小的好处。
  • 有一些方法可以正确地进行 TDD,例如
    它既可以降低成本又可以增加
    质量。
  • 做某事有办法
    与 TDD 相似但不同,
    可能会也可能不会被称为 TDD,并且
    可能更合适,也可能不更合适
    你的具体情况。

对于软件工具和专家来说,市场上有一个奇怪的怪癖,为了最大限度地提高推动它们的人的收入,它们总是被编写得好像它们以某种方式适用于“所有软件”。

事实是,“软件”与“硬件”一样多样化,没有人会考虑购买一本有关桥梁制作的书来设计电子产品或建造花园小屋。

The only true answer is it depends.

  • There are ways to do TDD wrong, such
    that it doesn't fit in with your
    environment and eats effort with
    minimal benefit.
  • There are ways to do TDD right, such
    that it both cuts costs and increases
    quality.
  • There are ways to something
    similar-but-different to TDD, which
    may or may not get called TDD, and
    may or may not be more appropriate in
    your particular situation.

It's a strange quirk of the market for software tools and experts that, to maximise the revenue for those pushing them, they are always written as if they somehow apply to 'all software'.

Truth is, 'software' is every bit as diverse as 'hardware', and nobody would think of buying a book on bridge-making to design an electronic gadget or build a garden shed.

故事与诗 2024-08-10 00:57:35

我认为您对 TDD 有一些误解。有关它是什么以及如何使用它的良好解释和示例,我建议阅读 Kent Beck 的 测试驱动开发:举例

这里有一些进一步的评论,可以帮助您理解什么是 TDD 以及为什么有些人对它深信不疑:

“如何将测试驱动开发与必须改变的设计结合起来,以反映对问题空间不断增长的理解?”

  • TDD是一种探索问题空间并创建和改进满足您需求的设计的技术。 TDD 并不是你除了设计之外还要做的事情;它是你在做设计时所做的事情。它正在做设计。

“如何让 TDD 实践对你有利而不是不利?”

  • TDD 并不是不做 TDD 的“两倍的工作量”。是的,您将编写大量测试,但这实际上并不会花费太多时间,而且精力也没有浪费。你必须以某种方式测试你的代码,对吧?每当您更改某些内容时,运行自动化测试都比手动测试快得多。

  • 许多 TDD 教程对每个类的每个方法都提供了非常详细的测试。在现实生活中,人们不会这样做。为每个 setter、每个 getter 等等编写测试是愚蠢的。 Beck 的书很好地展示了如何使用 TDD 快速设计和实现某些东西,只有当事情变得棘手时才放慢到“婴儿步骤”。有关这一点的更多信息,请参阅你的单元测试有多深

  • TDD 与回归测试无关。 TDD 是指在编写代码之前进行思考。但进行回归测试是一个很好的附带好处。它们不能保证代码永远不会损坏,但它们有很大帮助。

  • 当您所做的更改导致测试失败时,这并不是一件坏事;这是很有价值的反馈。设计确实会改变,并且您的测试也不是一成不变的。如果您的设计发生了很大的变化,以至于某些测试不再有效,那么就扔掉它们吧。编写新测试时,您需要对新设计充满信心。

I think you have some misconceptions about TDD. For a good explanation and example of what it is and how to use it, I recommend reading Kent Beck's Test-Driven Development: By Example.

Here are a few further comments that may help you understand what TDD is and why some people swear by it:

"How do you combine test driven development with a design that has to change to reflect a growing understanding of the problem space?"

  • TDD is a technique for exploring a problem space and creating and evolving a design that meets your needs. TDD is not something you do in addition to doing design; it is doing design.

"How do you make the TDD practice work for you instead of against you?"

  • TDD is not "twice as much work" as not doing TDD. Yes, you'll write a lot of tests, but that doesn't really take much time, and the effort isn't wasted. You have to test your code somehow, right? Running automated tests are a lot quicker than manually testing whenever you change something.

  • A lot of TDD tutorials present highly detailed tests of every method of every class. In real life, people don't do this. It is silly to write a test for every setter, every getter, and so on. The Beck book does a good job of showing how to use TDD to quickly design and implement something, slowing down to "baby steps" only when things get tricky. See How Deep Are Your Unit Tests for more on this point.

  • TDD is not about regression testing. TDD is about thinking before you write code. But having regression tests is a nice side benefit. They don't guarantee that code will never break, but they help a lot.

  • When you make changes that cause tests to break, that's not a bad thing; it's valuable feedback. Designs do change, and your tests aren't written in stone. If your design has changed so much that some tests are no longer valid, then just throw them away. Write the new tests you need to be confident about the new design.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文