Python突变测试介绍

通过突变测试来修复未知的 bug。

创新互联建站是一家专注于网站建设、成都网站制作与策划设计,普兰网站建设哪家好?创新互联建站做网站,专注于网站建设十年,网设计领域的专业建站公司;建站业务涵盖:普兰等地区。普兰做网站价格咨询:028-86922220

你一定对所有内容都进行了测试,也许你甚至在项目仓库中有一个徽章,标明有 100% 的测试覆盖率,但是这些测试真的帮到你了吗?你怎么知道的?

开发人员很清楚单元测试的成本。测试必须要编写。有时它们无法按照预期工作:存在假告警或者抖动测试。在不更改任何代码的情况下有时成功,有时失败。通过单元测试发现的小问题很有价值,但是通常它们悄无声息的出现在开发人员的机器上,并且在提交到版本控制之前就已得到修复。但真正令人担忧的问题大多是看不见的。最糟糕的是,丢失的告警是完全不可见的:你看不到没能捕获的错误,直到出现在用户手上 —— 有时甚至连用户都看不到。

有一种测试可以使不可见的错误变为可见:突变测试mutation testing。

变异测试通过算法修改源代码,并检查每次测试是否都有“变异体”存活。任何在单元测试中幸存下来的变异体都是问题:这意味着对代码的修改(可能会引入错误)没有被标准测试套件捕获。

Python 中用于突变测试的一个框架是 mutmut

假设你需要编写代码来计算钟表中时针和分针之间的角度,直到最接近的度数,代码可能会这样写:

 
 
 
  1. def hours_hand(hour, minutes):
  2.     base = (hour % 12 ) * (360 // 12)
  3.     correction = int((minutes / 60) * (360 // 12))
  4.     return base + correction
  5. def minutes_hand(hour, minutes):
  6.     return minutes * (360 // 60)
  7. def between(hour, minutes):
  8.     return abs(hours_hand(hour, minutes) - minutes_hand(hour, minutes))

首先,写一个简单的单元测试:

 
 
 
  1. import angle
  2. def test_twelve():
  3.     assert angle.between(12, 00) == 0

足够了吗?代码没有 if 语句,所以如果你查看覆盖率:

 
 
 
  1. $ coverage run `which pytest`
  2. ============================= test session starts ==============================
  3. platform linux -- Python 3.8.3, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
  4. rootdir: /home/moshez/src/mut-mut-test
  5. collected 1 item                                                              
  6. tests/test_angle.py .                                                    [100%]
  7. ============================== 1 passed in 0.01s ===============================

完美!测试通过,覆盖率为 100%,你真的是一个测试专家。但是,当你使用突变测试时,覆盖率会变成多少?

 
 
 
  1. $ mutmut run --paths-to-mutate angle.py
  2. Legend for output:
  3. Killed mutants. The goal is for everything to end up in this bucket.
  4. ⏰ Timeout. Test suite took 10 times as long as the baseline so were killed.
  5. ???? Suspicious. Tests took a long time, but not long enough to be fatal.
  6. Survived. This means your tests needs to be expanded.
  7. Skipped. Skipped.
  8. ⠋ 21/21 5 ⏰ 0 ???? 0 16 0

天啊,在 21 个突变体中,有 16 个存活。只有 5 个通过了突变测试,但是,这意味着什么呢?

对于每个突变测试,mutmut 会修改部分源代码,以模拟潜在的错误,修改的一个例子是将 > 比较更改为 >=,查看会发生什么。如果没有针对这个边界条件的单元测试,那么这个突变将会“存活”:这是一个没有任何测试用例能够检测到的潜在错误。

是时候编写更好的单元测试了。很容易检查使用 results 所做的更改:

 
 
 
  1. $ mutmut results
  2. Survived (16)
  3. ---- angle.py (16) ----
  4. 4-7, 9-14, 16-21
  5. $ mutmut apply 4
  6. $ git diff
  7. diff --git a/angle.py b/angle.py
  8. index b5dca41..3939353 100644
  9. --- a/angle.py
  10. +++ b/angle.py
  11. @@ -1,6 +1,6 @@
  12. def hours_hand(hour, minutes):
  13. hour = hour % 12
  14. - base = hour * (360 // 12)
  15. + base = hour / (360 // 12)
  16. correction = int((minutes / 60) * (360 // 12))
  17. return base + correction

这是 mutmut 执行突变的一个典型例子,它会分析源代码并将运算符更改为不同的运算符:减法变加法。在本例中由乘法变为除法。一般来说,单元测试应该在操作符更换时捕获错误。否则,它们将无法有效地测试行为。按照这种逻辑,mutmut 会遍历源代码仔细检查你的测试。

你可以使用 mutmut apply 来应用失败的突变体。事实证明你几乎没有检查过 hour 参数是否被正确使用。修复它:

 
 
 
  1. $ git diff
  2. diff --git a/tests/test_angle.py b/tests/test_angle.py
  3. index f51d43a..1a2e4df 100644
  4. --- a/tests/test_angle.py
  5. +++ b/tests/test_angle.py
  6. @@ -2,3 +2,6 @@ import angle
  7. def test_twelve():
  8. assert angle.between(12, 00) == 0
  9. +
  10. +def test_three():
  11. + assert angle.between(3, 00) == 90

以前,你只测试了 12 点钟,现在增加一个 3 点钟的测试就足够了吗?

 
 
 
  1. $ mutmut run --paths-to-mutate angle.py
  2. ⠋ 21/21 7 ⏰ 0 ???? 0 14 0

这项新测试成功杀死了两个突变体,比以前更好,当然还有很长的路要走。我不会一一解决剩下的 14 个测试用例,因为我认为模式已经很明确了。(你能将它们降低到零吗?)

变异测试和覆盖率一样,是一种工具,它允许你查看测试套件的全面程度。使用它使得测试用例需要改进:那些幸存的突变体中的任何一个都是人类在篡改代码时可能犯的错误,以及潜伏在程序中的隐藏错误。继续测试,愉快地搜寻 bug 吧。

文章标题:Python突变测试介绍
网站链接:http://www.shufengxianlan.com/qtweb/news26/8676.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联