在做TDD时,我应该对“做最简单可能的工作”有多严格

时间:2010-06-09 23:27:45

标签: language-agnostic tdd

对于TDD,您必须

  1. 创建失败的测试
  2. 做最简单的事情可以通过测试
  3. 添加测试的更多变体并重复
  4. 模式出现时重构
  5. 通过这种方法,你想要覆盖所有的情况(至少在我脑海中浮现),但我想知道我是否在这里过于严格,是否有可能“提前思考”< / em>某些场景而非简单发现它们。

    例如,我正在处理一个文件,如果它不符合某种格式,我会抛出InvalidFormatException

    所以我的第一次测试是:

    @Test 
    void testFormat(){
        // empty doesn't do anything nor throw anything
        processor.validate("empty.txt"); 
        try {
            processor.validate("invalid.txt");
            assert false: "Should have thrown InvalidFormatException";
        } catch( InvalidFormatException ife ) {
            assert "Invalid format".equals( ife.getMessage() );
        }
     }
    

    我运行它并且失败,因为它不会抛出异常。

    所以我想到的下一件事是:“尽可能做最简单的事情”,所以我:

    public void validate( String fileName ) throws InvalidFormatException {
        if(fileName.equals("invalid.txt") {
            throw new InvalidFormatException("Invalid format");
        }
    }
    

    卫生署!! (虽然真正的代码有点复杂,我发现自己做了几次这样的事情

    我知道我必须最终添加另一个文件名和其他测试,这会使这种方法变得不切实际,这将迫使我重构一些有意义的东西(如果我正确理解是TDD的重点,发现用法揭示的模式)但是:

    问:我是否太过字面了“做最简单的事情......”的东西?

7 个答案:

答案 0 :(得分:7)

我认为你的方法很好,如果你对它感到满意的话。你没有浪费时间编写一个愚蠢的案例并以愚蠢的方式解决它 - 你写了一个真正理想的功能的严肃测试,并让它传递 - 正如你所说 - 最简单的方式可能工作。现在 - 以及将来,随着您添加越来越多的实际功能 - 您将确保您的代码具有在一个特定格式错误的文件上抛出正确异常的所需行为。接下来要做的就是让这种行为成为现实 - 你可以通过编写更多测试来推动这种行为。如果编写正确的代码变得比再次伪造它更简单,那就是当你编写正确的代码时。这种评估在程序员中有所不同 - 当然有些人会认为时间是第一次失败测试的时间。

你正在使用非常小的步骤,这对我和其他一些TDD来说是最舒服的方法。如果你对更大的步骤感觉更舒服,那也没关系 - 但是知道你可以在那些重大步骤绊倒你的那些场合总是回到更细粒度的过程。

答案 1 :(得分:3)

当然,你对规则的解释太字面了。 它应该听起来像“做最简单的潜在有用的事情......”

另外,我认为在编写实现时你应该忘记你试图满足的测试体。您应该只记住测试的名称(它应该告诉您测试的内容)。通过这种方式,您将被迫编写通用的代码以使其有用。

答案 2 :(得分:3)

我也是TDD新手,正在努力解决这个问题。在进行研究时,我发现Roy Osherove this blog post是我发现的第一个也是唯一具体而有形的“可能最有效的东西”的定义(甚至罗伊承认这只是一个开始)。 / p>

简而言之,罗伊说:

查看您刚刚在生产代码中编写的代码,并问自己以下内容:

“我能以......的方式实现相同的解决方案吗?”

  1. “..更加硬编码..”
  2. “..接近我在...中编写的方法的开头。”
  3. “..较少缩进(尽可能少的”范围“,如ifs,loops,try-catch)..”
  4. “..更短(字面意思更少写字符)但仍然可读..”
  5. “......仍然让所有测试通过?”

    如果对其中一个的回答是“是”,那么这样做,并看到所有测试仍在通过。

答案 3 :(得分:2)

很多评论:

  • 如果"empty.txt"的验证引发异常,则无法捕获它。

  • 不要重复自己。您应该有一个测试函数来决定验证是否抛出异常。然后调用该函数两次,得到两个不同的预期结果。

  • 我没有看到任何单元测试框架的迹象。也许我想念他们?但仅使用assert将无法扩展到更大的系统。当您从验证中获得结果时,您应该有办法向测试框架宣布给定测试,具有给定名称,成功或失败。

  • 我对于检查文件名(而不是内容)构成“验证”的想法感到震惊。在我看来,这有点简单。

关于你的基本问题,我认为你会从对最简单的事情的更广泛的想法中受益。我也不是一个原教旨主义的TDDer,而且我可以让你“提前”思考位。这意味着要提前考虑到今天下午或明天早上,不要再考虑下周。

答案 4 :(得分:2)

您错过了列表中的第0点:知道该怎么做。您说您正在处理文件以进行验证。一旦你指定了“验证”的含义(提示:在编写任何代码之前执行此操作),你可能更好地了解如何a)编写测试,以及测试规范是否已实现,以及b)编写最简单的东西

例如,如果验证是“必须是XML”,那么您的测试用例只是一些非xml一致的字符串,并且您的实现正在使用XML库并且(如果需要)将其异常转换为为您指定的异常。验证“功能。

答案 5 :(得分:2)

未来的TDD学习者需要注意的一点 - TDD口头禅实际上并不包括“做最简单的事情可能有效”。 Kent Beck的TDD Book只有3个步骤:

  1. 红色 - 写一个不起作用的小测试,也许甚至不行 首先编译。
  2. 绿色 - 让测试快速完成,提交 在这个过程中必要的任何罪行。
  3. 重构 - 消除仅仅让测试工作所产生的所有重复。
  4. 尽管短语“做最简单的事情......”通常归功于沃德坎宁安,但实际上他问了一个问题"What's the simplest thing that could possibly work?",但这个问题后来变成了一个命令 - 沃德认为这可能会让人感到困惑。

    编辑:我不建议强烈阅读Beck的TDD Book - 就像与主人本人进行配对会议一样,给你他对测试驱动开发过程的见解和想法。

答案 6 :(得分:1)

就像一个方法应该只做一件事,一个测试应该只测试一件事(行为)。为了解决给出的示例,我将编写两个测试,例如test_no_exception_for_empty_filetest_exception_for_invalid_file。第二个确实可能是几个测试 - 一种无效。

TDD过程的第三步应解释为“添加测试的新变体”,而不是“为测试添加新变体”。实际上,单元测试应该是原子的(仅测试一件事)并且通常遵循三A模式:排列 - 行为 - 断言。并且首先验证测试是否失败非常重要,以确保它真正测试某些东西。

我还要分开阅读文件和验证其内容的责任。这样,测试可以将缓冲区传递给validate()函数,测试不必读取文件。通常,单元测试不会访问文件系统,因此会降低它们的速度。