代码之家  ›  专栏  ›  技术社区  ›  OscarRyz

在做TDD的时候,我应该对“做最简单的事情,可以做的工作”有多严格?

  •  17
  • OscarRyz  · 技术社区  · 15 年前

    对于TDD,你必须

    1. 创建失败的测试
    2. 做一些最简单的事情来通过测试
    3. 添加更多测试变体并重复
    4. 模式出现时重构

    用这种方法,你应该涵盖所有的情况(至少我想到了这一点),但我想知道我在这里是否过于严格,是否有可能 “先行思考” 有些场景不是简单的发现它们。

    例如,我正在处理一个文件,如果它不符合某种格式,我将抛出一个 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 回复  |  直到 12 年前
        1
  •  7
  •   Carl Manaster    15 年前

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

    您使用的是非常小的步骤,这是我和其他一些TDR最舒适的方法。如果你对更大的步骤更为满意,那也很好——但是要知道,在那些大的步骤绊倒你的时候,你总是可以退回到更细粒度的过程中去。

        2
  •  3
  •   Rotsor    15 年前

    当然,你对规则的解释过于直白。 听起来应该像“做最简单的 可能有用 东西……”

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

        3
  •  3
  •   SCFrench    15 年前

    我也是一个在这个问题上挣扎的TDD新手。在调查的时候,我发现 this blog post 罗伊·奥舍洛夫(RoyOsherove)是我发现的第一个也是唯一一个具体的、切实可行的“最简单的事情可能奏效”的定义(甚至罗伊也承认这只是一个开始)。

    简言之,罗伊说:

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

    _我能以……__的方式实现相同的解决方案吗?

    1. ……更多硬编码..__
    2. ……接近方法的开头时,我把它写了进去。艾斯
    3. ……缩进更少(尽可能少地使用范围,如ifs、loops、try catch)。
    4. ……更短(字面上写的字符更少)但仍然可读。

    仍然让所有测试通过?艾斯

    如果其中一个的答案是__是__,那么就这样做,并看到所有测试仍然通过。

        4
  •  2
  •   Norman Ramsey    15 年前

    很多评论:

    • 如果验证 "empty.txt" 抛出一个异常,你无法抓住它。

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

    • 我没有看到任何单元测试框架的迹象。也许我错过了他们?只是使用 assert 无法扩展到更大的系统。当您从验证中得到一个结果时,您应该有一种方法向测试框架宣布一个具有给定名称的给定测试成功或失败。

    • 我对检查文件名(而不是 内容 )构成“验证”。在我看来,这有点 简单。

    关于你的基本问题,我认为你会从一个更广泛的概念中受益,那就是最简单的事情是什么。我也不是一个原教旨主义者,我可以允许你“超前思考”一个 小的 比特。这意味着提前考虑今天下午或明天上午,而不是提前考虑下周。

        5
  •  2
  •   Arne    15 年前

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

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

        6
  •  2
  •   Colin K    12 年前

    对未来的TDD学习者来说,有一点值得注意——TDD的咒语实际上并不包括“做最简单的事情,这可能有效”。 TDD Book 只有3个步骤:

    1. Red_ 首先编译。
    2. 绿色“让测试快速进行,承诺 在这个过程中任何必要的罪恶。
    3. 重构消除了仅仅使测试工作而产生的所有重复。

    尽管“做最简单的事…”这句话经常被认为是沃德·坎宁安的话,但他实际上问了一个问题。 "What's the simplest thing that could possibly work?" 但这个问题后来变成了一个命令——沃德认为,这可能会混淆,而不是帮助。

    编辑:我不推荐读贝克的 TDD图书 足够强大——这就像与大师本人进行一次配对会议,向您提供他对测试驱动开发过程的见解和想法。

        7
  •  1
  •   philant    15 年前

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

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

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