最近、私は「突然変異テスト」と呼ばれるソフトウェアテスト方法に精通し、テストを書くためのこのアプローチのファンになりました。
理論第一
突然変異テストの目的は、効果のないテストや不完全なテストを識別することです。つまり、本質的にテストをテストすることです。
アイデアは、ソースコードの小さなランダムな断片を修正し、テストの反応を観察することです。 変更を行った後もテストが合格した場合、そのような一連のテストは無効または不完全です。
ソースコードで変換を実行するルール(たとえば、 false
ではなくtrue
を置換するルール)は、 ミューテーター(mutation operator)と呼ばれます。 ミューテーターとして、算術演算とブール演算子の符号の置換、場所での変数のゼロ化と再配置、コード分岐の削除なども使用します。 ソースコードに加えられた変更は、 突然変異と呼ばれます。 突然変異の獲得の結果として、ソースコードは突然変異し、 突然変異体になります。 テスト後、変異体は2つのカテゴリに分類されます。
- killed(caught)-偏差が検出され、少なくとも1つのテストが失敗したもの
- 生存者(エスケープ)-テストに合格できた人
自動ミューテーションテストでは、元のソースコードのミュータントが多数作成され、それぞれに対してテストセットが実行されます。
突然変異テストの有効性の測定基準はMSIインジケーター(突然変異スコアインジケーター)であり、これは殺された突然変異体と生存者の比率を反映しています。 MSIとテストでのコードカバレッジの割合の差が大きいほど、テストの品質を評価するための情報量の少ない基準がカバレッジの割合になります。
ミューテーターの組み合わせが相互に排他的なミューテーションを引き起こすことがあり、結果のミュータントは(元のプログラムと)同等であると言います。 これは、小規模プロジェクトでもMSIを100%で達成することが非常に難しい理由の1つです。
今すぐ練習
Strykerと呼ばれる自動化された突然変異テストフレームワークについて説明します。
プロジェクトを準備するには、stryker-cliパッケージをグローバルにインストールします。
npm i -g stryker-cli
次に、プロジェクトdevsにstrykerおよびstryker-apiパッケージをインストールして保存します
npm i --save-dev stryker stryker-api
Mochaを自動テストフレームワークとして使用し、 Chaiはステートメントライブラリとして私に馴染みがあります。
npm i --save-dev chai mocha@3.5.0
stryker init
実行してみましょう。この初期化ユーティリティはいくつかの質問をします。設定と構成に従ってすべてを選択し、さらにレポートのリストにhtmlアイテムを追加しました。 これは次の行と同等です。
npm i --save-dev stryker-api stryker-mocha-runner stryker-mocha-framework stryker-html-reporter
構成の最後にstryker.conf.js
次の内容stryker.conf.js
ファイルが作成されます。
module.exports = function(config) { config.set({ files: [{ pattern: 'src/**/*.js', mutated: true, included: false }, 'test/**/*.js' ], mutate: [], testRunner: 'mocha', testFramework: 'mocha', mutator: 'es5', transpilers: [], reporter: ['html', 'clear-text', 'progress'], coverageAnalysis: 'perTest' }); };
オプションを見つけて、自分用に設定します。
-
files
テストに必要なファイルを指定するための名前と名前パターンの配列。 使用できる要素として:
-
'src/**/*.js'
などの文字列リテラル。 - InputFileDescriptorオブジェクト:
{ pattern: '', included: true, mutated: false }
、ここで
-
pattern
名前または名前テンプレートを持つ必須フィールド。ただし、!
によるファイルの除外はサポートしていません 文字列リテラルとは異なります。 つまり、ファイルまたはディレクトリが!
始まる場合 プロジェクトで必要な場合は、文字列リテラルの代わりにこのメソッドを使用します。 -
included
は、ファイルをテストランナーにアップロードする(true
)か、単にサンドボックスにコピーする(false
)かを決定するオプションのフィールドです。 実行時に、プロジェクト構造内で.stryker-tmp
ディレクトリがちらつき、その中にミュータントを含むサンドボックスがどのように.stryker-tmp
するかを確認できます。プロジェクトが他のモジュールに依存している場合は、サンドボックスへのコピーにも指定する必要があります。 -
mutated
は、ファイルを変更するかどうかを決定するオプションのフィールドです。
-
-
-
mutate
は、変更するファイルを指定するための名前と名前パターンのオプションの配列です。files
配列内のfiles
選択するときにInputFileDescriptorオブジェクトを使用する場合、この配列なしで実行できfiles
。 -
testRunner
必須フィールド。テストのテストランナーを示します。stryker-karma-runner
などのStryker用の適切なプラグインがインストールされていることを確認してくださいstryker-karma-runner
テストランナーとして使用します。 -
testFramework
テストで使用されるフレームワークを示します。 デフォルト値はtestRunner
からのものtestRunner
-
mutator
-オプションのフィールド。テストで使用されるmutator
プラグインセットを示します。デフォルトではes5
です。 -
transpilers
オプションの配列フィールドは、実行を開始する前にコード変換を実行する必要があるトランスパイラーを示します。 -
reporter
-オプションの配列フィールド。自動ミューテーションテスト後のレポート形式を選択できます。 -
maxConcurrentTestRunners
同時に実行されるテストの数を決定するオプションのフィールド。
実用的な例として、次の構造を持つプロジェクトを作成しました
├── app.js ├── package.json ├── stryker.conf.js └── test └── app.test.js
メインファイルには1つの関数のみが含まれ、エクスポートされます
// app.js module.exports = { userIsOldEnough: (user) => user.age >= 18 };
突然変異テストの概念を実証するために、2回のパスでも、プロジェクトに100%カバレッジの単体テストを提供します。
// test/app.test.js const expect = require('chai').expect, app = require('../app'); describe('Site', () => { it('can be visited by an adult', () => { expect(app.userIsOldEnough({ age: 23 })).to.be.true; }); it('can not be visited by a child', () => { expect(app.userIsOldEnough({ age: 13 })).to.be.false; }); });
Stryker構成ファイルは次のようになります
// stryker.conf.js module.exports = function(config) { config.set({ files: [{ pattern: 'app.js', mutated: true }, 'test/**/*.js' ], testRunner: 'mocha', reporter: ['html', 'clear-text', 'progress'], testFramework: 'mocha' }); };
また、便宜上、 package.json
いくつかのスクリプトを追加しました。
{ "name": "mutations-demo", "version": "1.0.0", "private": true, "scripts": { "test": "istanbul cover _mocha", "posttest": "stryker run" }, "main": "app.js", "devDependencies": { "chai": "^4.1.2", "mocha": "^3.5.0", "istanbul": "^0.4.5", "stryker": "^0.13.0", "stryker-api": "^0.11.0", "stryker-html-reporter": "^0.10.1", "stryker-mocha-framework": "^0.6.1", "stryker-mocha-runner": "^0.9.1" }, "dependencies": { "underscore": "^1.8.3" } }
実行する
npm t
そして今、楽しい部分が始まります。すべてのユニットテストに合格し、コードの100%をカバーしていることを確認できます。
Site ✓ can be visited by an adult ✓ can not be visited by a child 2 passing (15ms) =============================== Coverage summary =============================== Statements : 100% ( 2/2 ) Branches : 100% ( 0/0 ) Functions : 100% ( 0/0 ) Lines : 100% ( 2/2 ) ================================================================================
その後、突然変異テストが自動的に開始されます。ここでは、MSI 50%の形式で悪いニュースを取得します。
Mutant survived! Mutator: BinaryOperator - userIsOldEnough: (user) => user.age >= 18 + userIsOldEnough: (user) => user.age > 18 Tests ran: Site can be visited by an adult Site can not be visited by a child Ran 1.50 tests per mutant on average. ----------|---------|----------|-----------|------------|----------|---------| File | % score | # killed | # timeout | # survived | # no cov | # error | ----------|---------|----------|-----------|------------|----------|---------| All files | 50.00 | 1 | 0 | 1 | 0 | 0 | app.js | 50.00 | 1 | 0 | 1 | 0 | 0 | ----------|---------|----------|-----------|------------|----------|---------|
レポートは、テスト操作が>=
から>
への論理操作の変更の影響を受けなかったため、テストが不完全であると結論付けました。したがって、サイトのユーザーが18歳の場合、テストは機能をチェックしません。 このレポートはコミット間の差分のように見えますが、設定によると、より美しいレポートが同様の htmlドキュメントの形式で生成されます。
このプロジェクトのリポジトリはGithubにあります。 そして、あなたは何も上げることができず、ログを見ることができるように、私はTravisにプロジェクトを追加しました。