tdd简单示例
本系列的第三篇文章演示了如何使用故障和单元测试来开发更好的代码。
虽然它似乎是旅程结束了与事(物联网)应用程序来控制猫门的一个成功样本的互联网,有经验的程序员都知道的解决方案需要的突变 。
什么是突变测试?
变异测试是以下过程:遍历已实施代码的每一行,变异该行,然后运行单元测试,并检查变异是否超出预期。 如果还没有,则说明您创建了一个尚存的突变体。
如果您让幸存的突变者四处奔波,繁衍,长寿和繁荣,那么您将制造出可怕的技术债务。 另一方面,如果任何单元测试抱怨该临时突变的代码行产生的输出与预期输出不同,则该突变体已被杀死。
安装史赛克
尝试突变测试的最快方法是利用专用框架。 本示例使用Stryker 。
要安装Stryker,请转到命令行并运行:
$ dotnet tool install -g dotnet-stryker
要运行Stryker,请导航至unittest文件夹并键入:
$ dotnet-stryker
以下是史赛克关于我们解决方案质量的报告:
14 mutants have been created. Each mutant will now be tested, this could take a while.
Tests progress | 14/14 | 100% | ~0m 00s |
Killed : 13
Survived : 1
Timeout : 0
All mutants have been tested, and your mutation score has been calculated
- \app [13/14 (92.86%)]
[...]
该报告说:
- 斯特赖克创造了14个突变体
- Stryker看到13个突变体被单元测试杀死
- 斯特赖克(Stryker)看到一个突变体幸免于单元测试的冲击
- Stryker计算得出,现有代码库包含满足期望的代码的92.86%
- Stryker计算得出,7.14%的代码库包含的代码不符合预期
总体而言,Stryker声称本系列前三篇文章中组装的应用程序无法产生可靠的解决方案。
如何杀死一个突变体
当软件开发人员遇到幸存的突变体时,他们通常会伸手寻找实现的代码并寻找修改它的方法。 例如,对于用于猫门自动化的示例应用程序,请更改以下行:
string trapDoorStatus = "Undetermined" ;
至:
string trapDoorStatus = "" ;
然后再次运行Stryker。 突变体幸存下来:
All mutants have been tested, and your mutation score has been calculated
- \app [13/14 (92.86%)]
[...]
[Survived] String mutation on line 4: '""' ==> '"Stryker was here!"'
[...]
这次,您可以看到Stryker改变了这一行:
string trapDoorStatus = "" ;
变成:
string trapDoorStatus = "" Stryker was here ! ";
这是Stryker工作原理的一个很好的例子:它以一种聪明的方式改变了我们代码的每一行,以查看是否还有我们尚未考虑的测试用例。 这迫使我们更深入地考虑我们的期望。
在Stryker的击败下,您可以尝试通过向其添加更多逻辑来改进已实现的代码:
public
string Control
(
string dayOrNight
)
{
string trapDoorStatus
=
"Undetermined"
;
if
( dayOrNight
==
"Nighttime"
)
{
trapDoorStatus
=
"Cat trap door disabled"
;
}
else
if
( dayOrNight
==
"Daylight"
)
{
trapDoorStatus
=
"Cat trap door enabled"
;
}
else
{
trapDoorStatus
=
"Undetermined"
;
}
return trapDoorStatus
;
}
但是在再次运行Stryker之后,您会看到此尝试创建了一个新的变体:
ll mutants have been tested, and your mutation score has been calculated
- \app [13/15 (86.67%)]
[...]
[Survived] String mutation on line 4: '"Undetermined"' ==> '""'
[...]
[Survived] String mutation on line 10: '"Undetermined"' ==> '""'
[...]
您不能通过修改实现的代码来摆脱困境。 事实证明,杀死存活的突变体的唯一方法是描述其他期望 。 您如何描述期望? 通过编写单元测试。
成功进行单元测试
是时候添加一个新的单元测试了。 由于尚存的突变体位于第4行,因此您意识到您没有为输出指定期望值“ Undetermined”。
让我们添加一个新的单元测试:
[ Fact
]
public
void GivenIncorrectTimeOfDayReturnUndetermined
(
)
{
var expected
=
"Undetermined"
;
var actual
= catTrapDoor
.
Control
(
"Incorrect input"
)
;
Assert
.
Equal
( expected, actual
)
;
}
解决方法成功了! 现在所有的突变体都被杀死了:
All mutants have been tested, and your mutation score has been calculated
- \app [14/14 (100%)]
[Killed] [...]
您最终将获得一个完整的解决方案,包括对如果系统接收到错误输入值的预期输出的描述。
变异测试以营救
假设您决定过度设计解决方案,并将此方法添加到FakeCatTrapDoor :
private
string getTrapDoorStatus
(
string dayOrNight
)
{
string status
=
"Everything okay"
;
if
( dayOrNight
!=
"Nighttime"
|| dayOrNight
!=
"Daylight"
)
{
status
=
"Undetermined"
;
}
return status
;
}
然后替换第4行的语句:
string trapDoorStatus = "Undetermined" ;
与:
string trapDoorStatus = getTrapDoorStatus ( dayOrNight ) ;
运行单元测试时,一切都会通过:
Starting test execution, please wait...
Total tests: 5. Passed: 5. Failed: 0. Skipped: 0.
Test Run Successful.
Test execution time: 2.7191 Seconds
测试已顺利通过。 TDD已奏效。 但是将Stryker带到现场,突然的画面看起来有些冷酷:
All mutants have been tested, and your mutation score has been calculated
- \app [14/20 (70%)]
[...]
斯特赖克创造了20个突变体; 14个突变体被杀死,六个突变体幸存。 这会将成功分数降低到70%。 这意味着只有70%的代码可以满足所描述的期望。 其余30%的代码没有明确的原因,这使我们有滥用该代码的风险。
在这种情况下,Stryker有助于对抗膨胀。 它不鼓励使用不必要的和复杂的逻辑,因为它位于这样的不必要的复杂逻辑的缝隙中,在这些逻辑中错误和缺陷会滋生。
结论
如您所见,变异测试确保没有不确定的事实得到检验。
您可以将Stryker与国际象棋大师进行比较,后者可以考虑赢得一场比赛的所有可能动作。 当Stryker不确定时,它告诉您获胜还不是保证。 我们记录为事实的单元测试越多,我们在比赛中的距离就越远,Stryker预测获胜的可能性就越大。 无论如何,即使表面上看起来一切都很好,Stryker仍可以帮助您发现失败的情况。
适当地设计代码始终是一个好主意。 您已经了解了TDD在这方面如何提供帮助。 当要保持代码的高度模块化时,TDD尤其有用。 但是,单靠TDD不足以提供完全符合预期的精益代码。 开发人员可以在不首先描述期望的情况下将代码添加到已经实现的代码库中。 这使整个代码库面临风险。 突变测试在捕获常规测试驱动开发(TDD)节奏中的违规行为时特别有用。 您需要更改已实现代码的每一行,以确保没有特定原因的情况下没有代码行。
既然您了解了变异测试的工作原理,那么您应该研究如何利用它。 下次,我将向您展示在应对更复杂的场景时如何充分利用突变测试。 我还将介绍更多敏捷概念,以了解DevOps文化如何从成熟的技术中受益。
翻译自: https://opensource.com/article/19/9/mutation-testing-example-definition
tdd简单示例