应该测试哪一层抽象
What layer of abstraction should be tested
假设我们要编写一些功能。它应该使用一些公司数据验证 csv 文件——每行一个公司。我想为此功能编写测试。今天我和我的同事进行了一些交谈,我们无法决定哪种方法更好,或者我们都错了:
在作为验证入口点的某些服务级别编写测试。它有一种方法:
ProcessingResult process(CsvFile csvFile)
IBAN 号码测试如下所示:
// given
valid csv row with invalid value in IBAN column
// when
service called
// then
result is validation failure with INVALID_IBAN error reason
为特定验证器编写测试。这种方法假设很少有验证器负责某些列组,例如。 CompanyNameValidator
、AccountNumberValidator
等。
第一种方法很好,因为它让我们在不接触测试的情况下重构整个验证过程。我尝试了它并在每一步后完全改变了实现 运行 测试,我非常有信心我没有破坏任何东西。这种方法也有缺点。假设我要测试公司名称和 IBAN。考虑在 IBAN 之前实施检查公司名称。假设此名称检查已损坏。现在对 IBAN 的测试也被破坏了(公司名称破坏了它)。好不好?
第二种方法不会有上述问题,因为公司名称是由不同于 IBAN 的验证器验证的。然而,这使得更大的重构变得不可能。如果我们想更改实现并退出多个验证器方法,我们将不得不修改测试,并且我们正在测试它们而不是整个功能。
哪种方法更好,或者是否有另一种方法可以测试我不知道的这种代码?
如您所见,第一种方法会带来更多价值,因为您可以自由地尝试实施。
Let's say I have a test for company name and for IBAN. Consider
implementation checking company name before IBAN. Let's say this name
check is broken. Now test for IBAN is broken as well (the company name
broke it). Is it ok or not?
分别为每个验证测试用例编写测试。然后你将拥有干净的 "test report",其中测试名称将告诉你你遇到的确切问题,而没有 reading/opening 失败的测试消息。
但是因为你是读文件,所以建议抽象文件读,这样你的测试会变得更快一点,而且不影响验证逻辑的维护。
我喜欢教我的团队的主要科目:
尽量避免将测试与实现细节耦合,因为单元测试,尤其是在 TDD 上下文中,是关于重构,而不是测试。
测试特定的验证器将导致脆弱的测试,因为重构的能力将被严重改变。
确实,如果明天您或您的队友有更好的方法来设计涉及不同性质验证的代码,那会怎样?
如果明天让第三方图书馆做验证工作呢?
如果明天你想要 combine/merge 一些验证器怎么办?
我的观点是:始终在 API 级别进行单元测试,就像您提到的第一种方法。
这样,您将能够在不破坏任何测试的情况下重构您的代码。
你的测试不应该知道你用来完成工作的策略。
它应该只关注入口点:API(通常是 Java/C# 等中的 public
方法)。
人们抱怨单元测试浪费他们的时间;症状是独一无二的:
以他们的方式,测试与实现细节相结合;所以在重构步骤中太容易崩溃了。
不要为每个验证单独编写测试文件,除非您在出现问题时聆听测试变得太难弄清楚。
尽可能避免这样做,否则你会陷入不想重构的陷阱,因为你不会 "allow" 你现有的测试中断(一种可悲的人类行为)。
Let's say I have a test for company name and for IBAN. Consider
implementation checking company name before IBAN. Let's say this name
check is broken. Now test for IBAN is broken as well (the company name
broke it).
这一点显然不依赖于测试策略。
您的算法应根据相关用户故事进行调整:
是否验证"fail-fast"(在那种情况下,检查公司名称失败中断了整个过程)?
只需编写另一个测试,其中上下文是成功的公司名称检查和失败的 IBAN 验证;但继续瞄准 API。
如果您想了解更多信息,建议您观看that video;我在这个主题上看过的最好的。
假设我们要编写一些功能。它应该使用一些公司数据验证 csv 文件——每行一个公司。我想为此功能编写测试。今天我和我的同事进行了一些交谈,我们无法决定哪种方法更好,或者我们都错了:
在作为验证入口点的某些服务级别编写测试。它有一种方法:
ProcessingResult process(CsvFile csvFile)
IBAN 号码测试如下所示:
// given valid csv row with invalid value in IBAN column // when service called // then result is validation failure with INVALID_IBAN error reason
为特定验证器编写测试。这种方法假设很少有验证器负责某些列组,例如。
CompanyNameValidator
、AccountNumberValidator
等。
第一种方法很好,因为它让我们在不接触测试的情况下重构整个验证过程。我尝试了它并在每一步后完全改变了实现 运行 测试,我非常有信心我没有破坏任何东西。这种方法也有缺点。假设我要测试公司名称和 IBAN。考虑在 IBAN 之前实施检查公司名称。假设此名称检查已损坏。现在对 IBAN 的测试也被破坏了(公司名称破坏了它)。好不好?
第二种方法不会有上述问题,因为公司名称是由不同于 IBAN 的验证器验证的。然而,这使得更大的重构变得不可能。如果我们想更改实现并退出多个验证器方法,我们将不得不修改测试,并且我们正在测试它们而不是整个功能。
哪种方法更好,或者是否有另一种方法可以测试我不知道的这种代码?
如您所见,第一种方法会带来更多价值,因为您可以自由地尝试实施。
Let's say I have a test for company name and for IBAN. Consider implementation checking company name before IBAN. Let's say this name check is broken. Now test for IBAN is broken as well (the company name broke it). Is it ok or not?
分别为每个验证测试用例编写测试。然后你将拥有干净的 "test report",其中测试名称将告诉你你遇到的确切问题,而没有 reading/opening 失败的测试消息。
但是因为你是读文件,所以建议抽象文件读,这样你的测试会变得更快一点,而且不影响验证逻辑的维护。
我喜欢教我的团队的主要科目:
尽量避免将测试与实现细节耦合,因为单元测试,尤其是在 TDD 上下文中,是关于重构,而不是测试。
测试特定的验证器将导致脆弱的测试,因为重构的能力将被严重改变。
确实,如果明天您或您的队友有更好的方法来设计涉及不同性质验证的代码,那会怎样?
如果明天让第三方图书馆做验证工作呢?
如果明天你想要 combine/merge 一些验证器怎么办?
我的观点是:始终在 API 级别进行单元测试,就像您提到的第一种方法。
这样,您将能够在不破坏任何测试的情况下重构您的代码。
你的测试不应该知道你用来完成工作的策略。
它应该只关注入口点:API(通常是 Java/C# 等中的 public
方法)。
人们抱怨单元测试浪费他们的时间;症状是独一无二的:
以他们的方式,测试与实现细节相结合;所以在重构步骤中太容易崩溃了。
不要为每个验证单独编写测试文件,除非您在出现问题时聆听测试变得太难弄清楚。
尽可能避免这样做,否则你会陷入不想重构的陷阱,因为你不会 "allow" 你现有的测试中断(一种可悲的人类行为)。
Let's say I have a test for company name and for IBAN. Consider implementation checking company name before IBAN. Let's say this name check is broken. Now test for IBAN is broken as well (the company name broke it).
这一点显然不依赖于测试策略。
您的算法应根据相关用户故事进行调整:
是否验证"fail-fast"(在那种情况下,检查公司名称失败中断了整个过程)?
只需编写另一个测试,其中上下文是成功的公司名称检查和失败的 IBAN 验证;但继续瞄准 API。
如果您想了解更多信息,建议您观看that video;我在这个主题上看过的最好的。