Mutmut:一个 Python 变异测试系统
Mutmut:一个 Python 变异测试系统
原文:https://medium.com/hackernoon/mutmut-a-python-mutation-testing-system-9b9639356c78
**我已不再使用介质。新地址是 kodare.net
跳到“能有多难?”如果不在乎背景的话。
什么是突变测试?
变异测试是一种合理地确定你的代码实际上测试了你的代码的全部行为的方法。不只是像覆盖报告告诉你的那样接触所有的线,而是实际测试所有的行为,和所有奇怪的边缘情况。它通过一次在一个地方尽可能微妙地更改代码,并运行测试套件来做到这一点。如果测试套件成功了,那么它就被认为是失败了,因为它可能会改变代码,而你的测试却没有意识到任何错误。
突变的例子是改变"
Background
I wanted to try out mutation testing for libraries I build in Python so I looked at what was available. I had some ideas for ways of radically speeding up mutation testing based on what pytest-testmon 的做法,并在上面添加一些我自己的想法。
谷歌向我展示了两种选择:
- Mutpy:作为论文开发的简单系统。不再维护。
- 宇宙线:积极开发。
这两个都是 Python 3 版本,这有点令人难过,因为我仍然在 Python 2 上工作至少一年多。但是我可以接受,因为库是 Python 2 和 3。
宇宙射线似乎更有前途,所以我试着安装它,但在努力安装依赖项后,我决定,如果只是安装它这么难,可能不切实际。我稍微研究了一下代码,看看我是否可以只使用突变部分作为库,但它对我来说就像一个巨大的单片系统,所以我放弃了。
接下来我看了看穆蒂。这段代码从根本上变得更小、更简单,但在努力以某种方式重构它以使它变得更简单之后,我对自己说:
能有多难?
事实证明,没那么糟!大多数构建模块已经可用。
我决定我绝对想要一个 Cosmic Ray 和 mutpy 都没有的特性:能够在源文件上应用一个突变,而不会搞砸整个文件。Cosmic Ray 和 mutpy 使用 AST 库中内置的 Pythons,但是不幸的是,它没有在 AST 中表示格式,这使得它不可能将 AST 转储回来并获得原始文件。所以如果我不能使用 Pythons 自己的 AST,那会怎么样呢?进入 baron ,这是一个独立开发的 Python- > AST 库,专门创建来能够在不改变源文件的情况下往返。不幸的是,Baron 还不支持 Python 3 的所有语法,但看起来人们正在努力。
[编辑:从这篇文章开始,我用 Parso 替换了 Baron,现在我完全支持 Python 3 了!]
我的作战计划是这样的:
- 创建一个 mutate 函数,该函数接收源代码并可以对所有相关的内容进行变异(这样您就可以获得可用变异的计数)或由索引指定的特定变异。
- 创建一个导入钩子,这样你想要修改的文件在从磁盘到内存的途中就会被修改。这将实现并行化。
- Pytest 插件,它设置了导入挂钩,使您能够指定您想要的突变。
- 制作一个小的命令行程序,运行突变并检查测试的输出。它还应该能够在磁盘上应用特定的突变,所以当你发现一个有趣的突变时,你可以很容易地看到突变是什么。
第 1 点相当简单:基本上我需要确保所有 AST 节点类型要么没有变异(因为这没有意义),要么以我能想到的最糟糕的方式变异。在这一步,我浏览了许多大型开源项目的代码(例如 django 和 numpy)。在这一步,我在 baron 中发现了一些解析错误,但是没有影响我想要运行变异测试的代码。我只是报告了错误,然后继续前进。
点 2 急了。事实证明 python 中的导入钩子系统很糟糕。从文件系统加载的默认行为不在钩子列表中,因为它在 C 代码中的某个地方,所以你不能以它作为导入器的基础。如果设计没问题,那就没问题,但不幸的是导入钩子系统是这样工作的:Python 一次要求一个导入钩子来导入模块。这听起来简单,简单往往是好的,但导入实际上包含以下步骤:
- 找到源文件
- 读取源文件
- 将源文件从文本转换为可运行的模块
所有的进口商必须完成所有的步骤。因此,zip 文件导入器必须执行与默认相同的步骤,并且它不能直接调用默认加载器,因为它不作为 python 代码存在。这也意味着,如果我想在第 2 步和第 3 步之间拦截所有的导入程序,我必须重新实现所有的导入程序。
这显然很糟糕(对于有自己的定制导入器的系统来说,甚至是不可能的),但更糟糕的是,要正确实现导入挂钩需要大量的代码,这是一件非常棘手的事情。据说这在 python 3 中用 importlib 会更好一些,所以我找到了一个到 python 2 的 backport,但是它坏了。我设法破解了崩溃,但最终我也没有让我的导入挂钩工作。我也在 reddit 上寻求帮助,但毫无效果。
经过几个小时的战斗,我放弃了(现在),只是用基于磁盘的突变。它并不伟大,因为它不能并行运行,但至少它是可行的,而且超级简单。对于您使用哪个测试运行器也非常灵活,因为您不需要为 pytest、nose、unittest 等一个接一个地制作任何插件。放弃这一点意味着第 3 点变得没有实际意义,所以这很好。
基本上我应该先做这个东西,因为它非常好
第 4 点相当容易。最困难的是发现如何在控制台上很好地输出持续的进度更新
那么我现在站在哪里?
我们已经在工作中对 tri.declarative 和 tri.struct 运行了 mutmut,它发现了几个我们没有像我们想象的那样彻底测试的东西,尽管我们的测试覆盖了 100%。对于 tri.declarative,它还发现了一段死代码和一个在错误消息中正确创建复数的错误。它显然改进了我们的测试套件,尽管它没有发现任何错误。
你也许现在就可以运行 mut mut。这是一段非常简单的代码,要让它与您的测试运行程序一起工作,只需要它有一个表示成功的退出代码 0,其他任何代码都表示失败。很明显,它非常慢,因为它根本没有并行化,而且它必须运行您为每个变异指定的所有测试套件(有很多!).
下一步是什么?
我还有一些工作要做。使用 pytest-testmon 仍然在我的列表中,解决导入挂钩系统以实现并行化也是如此。光是这些东西就应该能让我的测试速度快几个数量级。我的目标是保持我现在拥有的超级简单的系统,这样总有一个简单的调试和适应系统,你可以用它来进行奇怪的场景或调试。
我对 pytest-testmon 也有一些想法,比如能够在开发人员之间共享一个中央数据库,这也可以用于 mutmut,所以如果有人已经尝试为一个文件的特定版本运行特定的变异,你就不必这样做了。
黑客中午是黑客如何开始他们的下午。我们是 @AMI 家庭的一员。我们现在接受投稿,并乐意讨论广告&赞助机会。
要了解更多信息,请阅读我们的“关于”页面、在脸书上点赞/给我们发消息,或者简单地说, tweet/DM @HackerNoon。