mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-01-13 22:30:37 +08:00
Merge pull request #20292 from wxy/20200720-An-introduction-to-mutation-testing-in-Python
PRF&PUB:20200720 An introduction to mutation testing in Python
This commit is contained in:
commit
03e4f8661f
@ -1,28 +1,31 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (MjSeven)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: reviewer: (wxy)
|
||||
[#]: publisher: (wxy)
|
||||
[#]: url: (https://linux.cn/article-12871-1.html)
|
||||
[#]: subject: (An introduction to mutation testing in Python)
|
||||
[#]: via: (https://opensource.com/article/20/7/mutmut-python)
|
||||
[#]: author: (Moshe Zadka https://opensource.com/users/moshez)
|
||||
|
||||
Python 突变测试简介
|
||||
Python 突变测试介绍
|
||||
======
|
||||
通过突变测试来修复未知的 bug。
|
||||
![Searching for code][1]
|
||||
|
||||
> 通过突变测试来修复未知的 bug。
|
||||
|
||||
![](https://img.linux.net.cn/data/attachment/album/202011/29/230106ie9xc89dj3jx1yj9.jpg)
|
||||
|
||||
你一定对所有内容都进行了测试,也许你甚至在项目仓库中有一个徽章,标明有 100% 的测试覆盖率,但是这些测试真的帮到你了吗?你怎么知道的?
|
||||
|
||||
开发人员很清楚单元测试的 _成本_,必须编写测试。有时它们无法按照预期工作:存在假告警或者抖动测试。在不更改任何代码的情况下有时成功,有时失败。通过单元测试发现的小问题很有价值,但是通常它们悄无声息的出现在开发人员的机器上,并且在提交到版本控制之前就已得到修复。但真正令人担忧的问题大多是不可见的。最糟糕的是,_丢失的告警_ 是完全不可见的:你看不到没能捕获的错误,直到出现在用户手上--有时甚至连用户都看不到。
|
||||
开发人员很清楚单元测试的*成本*。测试必须要编写。有时它们无法按照预期工作:存在假告警或者抖动测试。在不更改任何代码的情况下有时成功,有时失败。通过单元测试发现的小问题很有价值,但是通常它们悄无声息的出现在开发人员的机器上,并且在提交到版本控制之前就已得到修复。但真正令人担忧的问题大多是看不见的。最糟糕的是,*丢失的告警*是完全不可见的:你看不到没能捕获的错误,直到出现在用户手上 —— 有时甚至连用户都看不到。
|
||||
|
||||
有一种测试可以使不可见的错误变为可见:[突变测试][2]。
|
||||
有一种测试可以使不可见的错误变为可见:<ruby>[突变测试][2]<rt>mutation testing</rt></ruby>。
|
||||
|
||||
变异测试通过算法修改源代码,并检查每次测试是否都有“变异体”存活。任何在单元测试中幸存下来的变异体都是问题:这意味着对代码的修改(可能会引入错误)没有被标准测试套件捕获。
|
||||
|
||||
[Python][3] 中用于突变测试的一个框架是 `mutmut`。
|
||||
|
||||
假设你需要编写代码来计算钟表中时针和分针之间的角度,直到最接近的度数,代码可能会这样写:
|
||||
|
||||
```
|
||||
def hours_hand(hour, minutes):
|
||||
base = (hour % 12 ) * (360 // 12)
|
||||
@ -37,6 +40,7 @@ def between(hour, minutes):
|
||||
```
|
||||
|
||||
首先,写一个简单的单元测试:
|
||||
|
||||
```
|
||||
import angle
|
||||
|
||||
@ -45,6 +49,7 @@ def test_twelve():
|
||||
```
|
||||
|
||||
足够了吗?代码没有 `if` 语句,所以如果你查看覆盖率:
|
||||
|
||||
```
|
||||
$ coverage run `which pytest`
|
||||
============================= test session starts ==============================
|
||||
@ -58,71 +63,74 @@ tests/test_angle.py .
|
||||
```
|
||||
|
||||
完美!测试通过,覆盖率为 100%,你真的是一个测试专家。但是,当你使用突变测试时,覆盖率会变成多少?
|
||||
|
||||
```
|
||||
$ mutmut run --paths-to-mutate angle.py
|
||||
<snip>
|
||||
<snip>
|
||||
Legend for output:
|
||||
🎉 Killed mutants. The goal is for everything to end up in this bucket.
|
||||
⏰ Timeout. Test suite took 10 times as long as the baseline so were killed.
|
||||
🤔 Suspicious. Tests took a long time, but not long enough to be fatal.
|
||||
🙁 Survived. This means your tests needs to be expanded.
|
||||
🔇 Skipped. Skipped.
|
||||
<snip>
|
||||
⠋ 21/21 🎉 5 ⏰ 0 🤔 0 🙁 16 🔇 0
|
||||
🎉 Killed mutants. The goal is for everything to end up in this bucket.
|
||||
⏰ Timeout. Test suite took 10 times as long as the baseline so were killed.
|
||||
🤔 Suspicious. Tests took a long time, but not long enough to be fatal.
|
||||
🙁 Survived. This means your tests needs to be expanded.
|
||||
🔇 Skipped. Skipped.
|
||||
<snip>
|
||||
⠋ 21/21 🎉 5 ⏰ 0 🤔 0 🙁 16 🔇 0
|
||||
```
|
||||
|
||||
天啊,在 21 个突变体中,只有 16 个存活。只有 5 个通过了突变测试,但是,这意味着什么呢?
|
||||
天啊,在 21 个突变体中,有 16 个存活。只有 5 个通过了突变测试,但是,这意味着什么呢?
|
||||
|
||||
对于每个突变测试,`mutmut` 会修改部分源代码,以模拟潜在的错误,修改的一个例子是将 `>` 比较更改为 `>=`,查看会发生什么。如果没有针对这个边界条件的单元测试,那么这个突变将会“存活”:这是一个没有任何测试用例能够检测到的潜在错误。
|
||||
|
||||
是时候编写更好的单元测试了。很容易检查使用 `results` 所做的更改:
|
||||
|
||||
```
|
||||
$ mutmut results
|
||||
<snip>
|
||||
<snip>
|
||||
Survived 🙁 (16)
|
||||
|
||||
\---- angle.py (16) ----
|
||||
---- angle.py (16) ----
|
||||
|
||||
4-7, 9-14, 16-21
|
||||
$ mutmut apply 4
|
||||
$ git diff
|
||||
diff --git a/angle.py b/angle.py
|
||||
index b5dca41..3939353 100644
|
||||
\--- a/angle.py
|
||||
--- a/angle.py
|
||||
+++ b/angle.py
|
||||
@@ -1,6 +1,6 @@
|
||||
def hours_hand(hour, minutes):
|
||||
hour = hour % 12
|
||||
\- base = hour * (360 // 12)
|
||||
\+ base = hour / (360 // 12)
|
||||
correction = int((minutes / 60) * (360 // 12))
|
||||
return base + correction
|
||||
def hours_hand(hour, minutes):
|
||||
hour = hour % 12
|
||||
- base = hour * (360 // 12)
|
||||
+ base = hour / (360 // 12)
|
||||
correction = int((minutes / 60) * (360 // 12))
|
||||
return base + correction
|
||||
```
|
||||
|
||||
这是 `mutmut` 执行突变的一个典型例子,它会分析源代码并将运算符更改为不同的运算符:减法变加法。在本例中由乘法变为除法。一般来说,单元测试应该在操作符更换时捕获错误。否则,它们将无法有效地测试行为。按照这种逻辑,`mutmut` 会遍历源代码仔细检查你的测试。
|
||||
|
||||
你可以使用 `mutmut apply` 来应用失败的突变体。事实证明你几乎没有检查过 `hour` 参数是否被正确使用。修复它:
|
||||
|
||||
```
|
||||
$ git diff
|
||||
diff --git a/tests/test_angle.py b/tests/test_angle.py
|
||||
index f51d43a..1a2e4df 100644
|
||||
\--- a/tests/test_angle.py
|
||||
--- a/tests/test_angle.py
|
||||
+++ b/tests/test_angle.py
|
||||
@@ -2,3 +2,6 @@ import angle
|
||||
|
||||
def test_twelve():
|
||||
assert angle.between(12, 00) == 0
|
||||
|
||||
def test_twelve():
|
||||
assert angle.between(12, 00) == 0
|
||||
+
|
||||
+def test_three():
|
||||
\+ assert angle.between(3, 00) == 90
|
||||
+ assert angle.between(3, 00) == 90
|
||||
```
|
||||
|
||||
以前,你只测试了 12 点钟,现在增加一个 3 点钟的测试就足够了吗?
|
||||
|
||||
```
|
||||
$ mutmut run --paths-to-mutate angle.py
|
||||
<snip>
|
||||
⠋ 21/21 🎉 7 ⏰ 0 🤔 0 🙁 14 🔇 0
|
||||
<snip>
|
||||
⠋ 21/21 🎉 7 ⏰ 0 🤔 0 🙁 14 🔇 0
|
||||
```
|
||||
|
||||
这项新测试成功杀死了两个突变体,比以前更好,当然还有很长的路要走。我不会一一解决剩下的 14 个测试用例,因为我认为模式已经很明确了。(你能将它们降低到零吗?)
|
||||
@ -136,7 +144,7 @@ via: https://opensource.com/article/20/7/mutmut-python
|
||||
作者:[Moshe Zadka][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[MjSeven](https://github.com/MjSeven)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
Loading…
Reference in New Issue
Block a user