如何使用 TDD 处理无法访问的异常
How to handle unreachable Exceptions with TDD
我仍然在思考 TDD 的部分内容。我有一个正在编写的新库,所以这似乎是一个尝试它的好机会。
我在 TDD 上读到的内容宣传 100% 的代码覆盖率,但这似乎有点像象牙塔,所以我将 JaCoco 配置为要求 90% 的代码覆盖率给我一些喘息的空间。
我开始编写加载 KeyStore 的代码。有很多样板代码和很多已检查的异常。所以从这里开始让我的生活更轻松。一切看起来都很好,我的测试也通过了。但代码覆盖率仅为 49%。查看代码,除了我称之为 "Impossible Exceptions" 的代码外,所有内容都已涵盖:
public void saveKey(Key key, String alias) {
KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(key.getMaterial(), "AES"));
try {
keyStore.setEntry(alias, entry, new KeyStore.PasswordProtection(password));
} catch (KeyStoreException e) {
throw new UnexpectedErrorException("Failed to save the key", e);
}
}
在这种特殊情况下,根据文档,如果 keyStore 尚未初始化,则会抛出 KeyStoreException。我正在编写防御性代码,并保证此时将初始化 keyStore。所以不能抛出 KeyStoreException。但这是一个已检查的异常,所以我必须处理它,所以我将它包装在一个自定义的 RuntimeException 中。
问题是我无法在单元测试中触发此错误。事实上,我已尽我所能确保它不会发生。
在这样的情况下,TDD 如何实现神话般的 100% 覆盖率?
我可以模拟 KeyStore,但 Mockito 的建议是 "Don't mock types you don't own." 所以我宁愿不要。此外,KeyStore 依赖于 Mockito 没有帮助的几个静态方法,我不想在简单情况下引入 PowerMock,而且我不相信在这个问题上投入更多的库是一个理想的解决方案。
所以:
- TDD 的 100% 代码覆盖率是神话吗?
- 是否有一种技术可以进行代码分析以识别此代码已被覆盖?
我目前预期的解决方案是将我配置的 90% 代码覆盖率限制降低到 40% 或 50%,直到我有更多 类 来提高我的总体平均覆盖率。但在我这样做之前,我是否遗漏了什么?
Like most aspects of programming, testing requires thoughtfulness. TDD is a very useful, but certainly not sufficient, tool to help you get good tests. If you are testing thoughtfully and well, I would expect a coverage percentage in the upper 80s or 90s. I would be suspicious of anything like 100% - it would smell of someone writing tests to make the coverage numbers happy, but not thinking about what they are doing. -- Martin Fowler, 2012
对于您的程序的核心,100% 的覆盖率可能是一个可以实现的目标;毕竟,你永远不会在没有失败测试的情况下引入代码,并且 "refactoring" 不应该引入未使用的分支。
但是与边界交互的代码... 100% 的覆盖率变得更加昂贵,你最终会达到临界点:
I get paid for code that works, not for tests, so my philosophy is to test as little as possible to reach a given level of confidence... -- Kent Beck, 2008
如果未选中 KeyStoreException,您就不会使用这个特定示例;检查异常的问题在某种程度上是 Java.
所特有的
David Parnas,writing in 1972,给了我们一些关于如何解决这个问题的提示。您可以设计您的解决方案,使其隐藏您使用 java.security.KeyStore
的决定。换句话说,您创建一个接口来描述您 希望 密钥库具有的 API,并将所有代码写入该接口。只有实现需要知道异常管理的具体细节;只有您的实现需要知道您决定 KeyStore 异常是不可恢复的。
同一个想法的另一种思考方式;我们试图将代码分成两堆:核心 包含易于测试的复杂代码; 边界 包含难以测试的简单代码。您的边界代码试金石是 Hoare:"so simple that there are obviously no deficiencies".
使用适用于每种情况的测试覆盖启发法。
My anticipated solution for now is to move my configured 90% code coverage restriction down to 40 or 50 percent until I have more classes to bring my over-all average coverage up.
使用棘轮机制来防止覆盖率统计数据出现倒退是个好主意。
What I've read on TDD advertises 100% code coverage.
这是一个常见的误解。
当我们进行 TDD 时,我们 不是 目标是 100% 代码覆盖率 。我们的目标是 100% 需求覆盖率 。不:100% 的代码覆盖率 不 意味着 100% 的需求覆盖率,反之亦然...
遗憾的是,我们无法衡量需求覆盖率。因此,获得它的唯一方法就是随后进行 TDD。
In this particular case, according to the docs, KeyStoreException is thrown if the keyStore hasn't been initialized. I'm coding defensively and have guaranteed that the keyStore will be initialized at this point. So the KeyStoreException cannot be thrown.
Given cases like this how does TDD achieve a mythical 100% coverage?
I could mock KeyStore, but advice from Mockito is "Don't mock types you don't own."
单元测试验证你的单元行为孤立这意味着任何其他单元你的代码under test collabors with need 需要替换为 test doubles。那里有一个讨论,我们应该在哪个细节级别削减我们的单位,但"types you don't own"绝对不是 您的 代码的一部分。不嘲笑它们意味着您的测试依赖于此外部代码。如果它失败了,你不知道是你的代码有问题还是外部代码有问题。
因此 "Don't mock types you don't own." 是一个相当糟糕的建议。
实际上 TDD 与代码覆盖率无关。
TDD 的规则之一是
You are not allowed to write any production code unless it is to make
a failing unit test pass.
因此,如果您实践测试驱动开发并遵循上述规则,那么您将始终拥有 100% 的代码覆盖率。
代码覆盖率是单元测试的衡量工具,适用于在编写生产代码后编写测试的情况。有了代码覆盖率,你 "check" 你不会忘记为你的逻辑中的某些情况编写测试。
我仍然在思考 TDD 的部分内容。我有一个正在编写的新库,所以这似乎是一个尝试它的好机会。
我在 TDD 上读到的内容宣传 100% 的代码覆盖率,但这似乎有点像象牙塔,所以我将 JaCoco 配置为要求 90% 的代码覆盖率给我一些喘息的空间。
我开始编写加载 KeyStore 的代码。有很多样板代码和很多已检查的异常。所以从这里开始让我的生活更轻松。一切看起来都很好,我的测试也通过了。但代码覆盖率仅为 49%。查看代码,除了我称之为 "Impossible Exceptions" 的代码外,所有内容都已涵盖:
public void saveKey(Key key, String alias) {
KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(key.getMaterial(), "AES"));
try {
keyStore.setEntry(alias, entry, new KeyStore.PasswordProtection(password));
} catch (KeyStoreException e) {
throw new UnexpectedErrorException("Failed to save the key", e);
}
}
在这种特殊情况下,根据文档,如果 keyStore 尚未初始化,则会抛出 KeyStoreException。我正在编写防御性代码,并保证此时将初始化 keyStore。所以不能抛出 KeyStoreException。但这是一个已检查的异常,所以我必须处理它,所以我将它包装在一个自定义的 RuntimeException 中。
问题是我无法在单元测试中触发此错误。事实上,我已尽我所能确保它不会发生。
在这样的情况下,TDD 如何实现神话般的 100% 覆盖率?
我可以模拟 KeyStore,但 Mockito 的建议是 "Don't mock types you don't own." 所以我宁愿不要。此外,KeyStore 依赖于 Mockito 没有帮助的几个静态方法,我不想在简单情况下引入 PowerMock,而且我不相信在这个问题上投入更多的库是一个理想的解决方案。
所以:
- TDD 的 100% 代码覆盖率是神话吗?
- 是否有一种技术可以进行代码分析以识别此代码已被覆盖?
我目前预期的解决方案是将我配置的 90% 代码覆盖率限制降低到 40% 或 50%,直到我有更多 类 来提高我的总体平均覆盖率。但在我这样做之前,我是否遗漏了什么?
Like most aspects of programming, testing requires thoughtfulness. TDD is a very useful, but certainly not sufficient, tool to help you get good tests. If you are testing thoughtfully and well, I would expect a coverage percentage in the upper 80s or 90s. I would be suspicious of anything like 100% - it would smell of someone writing tests to make the coverage numbers happy, but not thinking about what they are doing. -- Martin Fowler, 2012
对于您的程序的核心,100% 的覆盖率可能是一个可以实现的目标;毕竟,你永远不会在没有失败测试的情况下引入代码,并且 "refactoring" 不应该引入未使用的分支。
但是与边界交互的代码... 100% 的覆盖率变得更加昂贵,你最终会达到临界点:
I get paid for code that works, not for tests, so my philosophy is to test as little as possible to reach a given level of confidence... -- Kent Beck, 2008
如果未选中 KeyStoreException,您就不会使用这个特定示例;检查异常的问题在某种程度上是 Java.
所特有的David Parnas,writing in 1972,给了我们一些关于如何解决这个问题的提示。您可以设计您的解决方案,使其隐藏您使用 java.security.KeyStore
的决定。换句话说,您创建一个接口来描述您 希望 密钥库具有的 API,并将所有代码写入该接口。只有实现需要知道异常管理的具体细节;只有您的实现需要知道您决定 KeyStore 异常是不可恢复的。
同一个想法的另一种思考方式;我们试图将代码分成两堆:核心 包含易于测试的复杂代码; 边界 包含难以测试的简单代码。您的边界代码试金石是 Hoare:"so simple that there are obviously no deficiencies".
使用适用于每种情况的测试覆盖启发法。
My anticipated solution for now is to move my configured 90% code coverage restriction down to 40 or 50 percent until I have more classes to bring my over-all average coverage up.
使用棘轮机制来防止覆盖率统计数据出现倒退是个好主意。
What I've read on TDD advertises 100% code coverage.
这是一个常见的误解。
当我们进行 TDD 时,我们 不是 目标是 100% 代码覆盖率 。我们的目标是 100% 需求覆盖率 。不:100% 的代码覆盖率 不 意味着 100% 的需求覆盖率,反之亦然...
遗憾的是,我们无法衡量需求覆盖率。因此,获得它的唯一方法就是随后进行 TDD。
In this particular case, according to the docs, KeyStoreException is thrown if the keyStore hasn't been initialized. I'm coding defensively and have guaranteed that the keyStore will be initialized at this point. So the KeyStoreException cannot be thrown.
Given cases like this how does TDD achieve a mythical 100% coverage?
I could mock KeyStore, but advice from Mockito is "Don't mock types you don't own."
单元测试验证你的单元行为孤立这意味着任何其他单元你的代码under test collabors with need 需要替换为 test doubles。那里有一个讨论,我们应该在哪个细节级别削减我们的单位,但"types you don't own"绝对不是 您的 代码的一部分。不嘲笑它们意味着您的测试依赖于此外部代码。如果它失败了,你不知道是你的代码有问题还是外部代码有问题。
因此 "Don't mock types you don't own." 是一个相当糟糕的建议。
实际上 TDD 与代码覆盖率无关。
TDD 的规则之一是
You are not allowed to write any production code unless it is to make a failing unit test pass.
因此,如果您实践测试驱动开发并遵循上述规则,那么您将始终拥有 100% 的代码覆盖率。
代码覆盖率是单元测试的衡量工具,适用于在编写生产代码后编写测试的情况。有了代码覆盖率,你 "check" 你不会忘记为你的逻辑中的某些情况编写测试。