テストにも同じルールが適用されます(私にとっては、コード自体よりも1桁単純でなければなりません)。 さらに、テストには黄金のルール- テストごとに1つの期待があります。 1回のテストでたくさんのexpect / assert / shouldを呼び出す必要はありません。 また、テストもコードであり、 コピー&ペーストは悪い習慣であることを忘れないでください。
悪いテストとは何ですか?
Knockout.jsの3.0バージョンを理解した私は、バインダー内の新しいafterプロパティの少なくともいくつかの言及を見つけることを期待してテストを検討することにしました。 正直なところ、筆記されたテストの複雑さに激怒しました。
悪いテスト
describe('Binding: Checked', function() { beforeEach(jasmine.prepareTestNode); it('Triggering a click should toggle a checkbox\'s checked state before the event handler fires', function() { testNode.innerHTML = "<input type='checkbox' />"; var clickHandlerFireCount = 0, expectedCheckedStateInHandler; ko.utils.registerEventHandler(testNode.childNodes[0], "click", function() { clickHandlerFireCount++; expect(testNode.childNodes[0].checked).toEqual(expectedCheckedStateInHandler); }) expect(testNode.childNodes[0].checked).toEqual(false); expectedCheckedStateInHandler = true; ko.utils.triggerEvent(testNode.childNodes[0], "click"); expect(testNode.childNodes[0].checked).toEqual(true); expect(clickHandlerFireCount).toEqual(1); expectedCheckedStateInHandler = false; ko.utils.triggerEvent(testNode.childNodes[0], "click"); expect(testNode.childNodes[0].checked).toEqual(false); expect(clickHandlerFireCount).toEqual(2); }); });
すべてのディレクティブ( describeおよびit )が仕様の一部であることを考慮しない場合、ヘッダーからテストの意味を理解することはできません( クリックをトリガーする必要があります... )。 タイトルとテスト自体の両方で、ナンセンスが判明しました。
以下は、わかりやすく簡単な仕様を作成するのに役立つ質問のリストです。
- テストデータとは何ですか?
- テストコンテキストとは何ですか?
- どのような場合に対応する必要がありますか?
- これらのケースをグループ化するにはどうすればよいですか?
上記の例の場合:
- チェックボックス入力フィールド
- ユーザーがチェックボックスをクリックする
- 事例:
- クリックハンドラーが呼び出される前に状態が変化します。
- チェックボックスがチェックされていない場合、ステータスはチェック済みに変わります
- チェックボックスがオンになっている場合、ステータスはオフになります
現在、すべては英語のみで同じです。
読みやすいテスト
describe('Binding: Checked', function() { beforeEach(jasmine.prepareTestNode); describe("when user clicks on checkbox", function () { beforeEach(function () { testNode.innerHTML = "<input type='checkbox' />"; this.checkbox = testNode.childNodes[0]; this.stateHandler = jasmine.createSpy("checked handler"); this.checkbox.checked = false; ko.utils.registerEventHandler(this.checkbox, "click", function() { this.stateHandler(this.checkbox.checked); }.bind(this)); ko.utils.triggerEvent(this.checkbox, "click"); }) it ("changes state before event handler is triggered", function () { expect(this.stateHandler).toHaveBeenCalledWith(true); }) it ("marks checkbox if it's not marked", function () { expect(this.checkbox.checked).toBe(true) }) it ("unmarks checkbox if it's marked", function () { this.checkbox.checked = true; ko.utils.triggerEvent(this.checkbox, "click"); expect(this.checkbox.checked).toBe(false); }) }) })
セットアップは複雑で、テストは簡単です。 理想的なオプションは、 expect関数を1回呼び出すテストです。
より少ないコード、より多くのテスト
ジャスミンに初めて会ったとき、それが完璧ではないことに気付きましたが、グループスペックを作成する可能性を見つけられなかったため、パニックに陥ってGoogleに助けを求めました。 私の大きな失望に、彼は私に合った答えも知りませんでした。 私は自分でジャスミンの暗い腸を掘り下げて解決策を見つけなければなりませんでした。
共通インターフェース( sizeおよびcontains )を持つ2つのクラスArrayおよびSetがあるJavaScript ++があると想像してみましょう。 ここで、コードを複製せずにテストでそれらをカバーする必要があります! コレクションの一般的なテストを定義します。
sharedExamplesFor("collection", function () { beforeEach(function () { this.sourceItems = [1,2,3]; this.collection = new this.describedClass(this.sourceItems); }) it ("returns proper size", function () { expect(this.collection.size()).toBe(this.sourceItems.length); }) // another specs it ("returns true if contains item", function () { expect(this.collection.contains(this.sourceItems[0])).toBe(true); }) })
Rspecとの類推により、次のいずれかの方法を使用して仕様を接続できるようにしたいと思います。
- itBehavesLike-ネストされたコンテキストでテストを実行する
- itShouldBehaveLike-ネストされたコンテキストでテストを実行する
- includeExamples-現在のコンテキストでテストを実行します
- includeExamplesFor-現在のコンテキストでテストを実行します
注 :itShouldBehaveLikeおよびincludeExamplesFor-テストの可読性を向上させるためにのみ存在します
// array_spec.js describe("Array", function () { beforeEach(function () { this.describedClass = Array; }) itBehavesLike("collection"); //another specs }) // set_spec.js describe("Set", function () { beforeEach(function () { this.describedClass = Set; }) itBehavesLike("collection"); //another specs });
通常、 コンテキスト関数( describeのエイリアス)も作成して、仕様を読みやすくします。
共有仕様の実装のソースコード
// spec_helper.js var sharedExamples = {}; window.sharedExamplesFor = function (name, executor) { sharedExamples[name] = executor; }; window.itBehavesLike = function (sharedExampleName) { jasmine.getEnv().describe("behaves like " + sharedExampleName, sharedExamples[sharedExampleName]); }; window.includeExamplesFor = function (sharedExampleName) { var suite = jasmine.getEnv().currentSuite; sharedExamples[sharedExampleName].call(suite); }; window.context = window.describe; window.includeExamples = window.includeExamplesFor; window.itShouldBehaveLike = window.itBehavesLike;