JavaScriptのTDD。 アプリケーション開発

みなさんこんにちは。 この記事では、JavaScriptで使用するためのテストによる開発(TDD)手法に焦点を当てています。

TDD方法論について簡単に説明しますが、上記のリンクから詳細情報を入手できます。



テストによる開発は、コードを記述する前に次のことを行う必要があることを意味します。





テストによる開発



だから、トピックに近い。



技術的手段



テスト用ライブラリQUnitは、必要なすべての機能を備えた使いやすいライブラリです。



オブジェクトの概念



概念的に、システム内のすべてのオブジェクトを3つのカテゴリに分割します。

  1. 基本的な 。 これらはシステムのコアを形成するクラスであり、ストレージ(GoogleGears、IndexedDbなど)の操作、モジュール間の相互作用、言語パック、ユーザー設定などを担当します。
  2. モジュール これらは、モジュールの基本機能が実装されているクラスです(たとえば、通話履歴を表示するモジュール、チャットモジュールなど)。
  3. 補助オブジェクト 。 小さな機能を持ち、特定の小さなタスクを解決するオブジェクト(たとえば、ツールチップ、フィールドバリデータなどを表示するオブジェクト)


なぜ分割するのですか? 機能のテストカバレッジの完全性は、オブジェクトのタイプによって異なります。 基本オブジェクト-可能な限り、 モジュール -約70%、 補助オブジェクト -オブジェクトの設計(これについては後ほど説明します)および結果の確認専用です。



オブジェクトの命名



この場合、最も単純な名前を使用します。

すべてのアプリケーションオブジェクトは、Applicationという単語で始まります。

  1. ベース -Application_Core_ObjectName
  2. モジュール-Application_Module_ModuleName
  3. ヘルパーオブジェクト -Application_Helper_ModuleName(オブジェクトが特定のモジュールでのみ機能する場合)_ObjectName


さらに多くの興味深い命名方法があります。 グローバルスコープから名前を非表示にしますが、この記事はそれについてではありません。



オブジェクトの実装



なぜなら 「単純なものは複雑なものよりも優れている」という原則(c)が好きです。その場合、オブジェクトの実装は単純であり、TDD方法論と(非常に重要です!)



ベースモジュール

function Application_Core_FuncName() {



this .publicMethod = function () {

}



this ._privateMethod = function () {

}



}




* This source code was highlighted with Source Code Highlighter .









function Application_Core_FuncName() {



this .publicMethod = function () {

}



this ._privateMethod = function () {

}



}




* This source code was highlighted with Source Code Highlighter .









function Application_Core_FuncName() {



this .publicMethod = function () {

}



this ._privateMethod = function () {

}



}




* This source code was highlighted with Source Code Highlighter .














サポートオブジェクト

var Application_Helper_HelperName = {

init: function () {

},



publicMethod: function () {

},



_privateMethod: function () {

}

};




* This source code was highlighted with Source Code Highlighter .









var Application_Helper_HelperName = {

init: function () {

},



publicMethod: function () {

},



_privateMethod: function () {

}

};




* This source code was highlighted with Source Code Highlighter .









var Application_Helper_HelperName = {

init: function () {

},



publicMethod: function () {

},



_privateMethod: function () {

}

};




* This source code was highlighted with Source Code Highlighter .












なぜそうですか? 問題のステートメントから始めましょう。アプリケーションはTDD方法論に従って開発されます。 テストのためにプライベートクラスメソッドへのアクセスを実装する必要があります(プライベートメソッドをテストする必要性についていくつかの意見があります。私はテストしています)。 一般的な標準では、アンダースコアで始まるJavaScriptメソッドはプライベートです(多くのIDEでは使用可能なメソッドのヒントに表示されません)が、このメソッドは実際に使用可能です。



開発



ある種の補助モジュールを開発する必要があるとしましょう。補助モジュールは、ユーザーに関する情報を含む生成されたhtmlを返す必要があります。



テストから始めましょう:



module( "User Info" );



test( "main" , function () {

equals( typeof (Application_Helper_UserInfo), "object" , "Check object" );

})




* This source code was highlighted with Source Code Highlighter .









module( "User Info" );



test( "main" , function () {

equals( typeof (Application_Helper_UserInfo), "object" , "Check object" );

})




* This source code was highlighted with Source Code Highlighter .









module( "User Info" );



test( "main" , function () {

equals( typeof (Application_Helper_UserInfo), "object" , "Check object" );

})




* This source code was highlighted with Source Code Highlighter .












テストを実行します。



明らかに、テストは失敗します そのようなオブジェクトは存在しません。

オブジェクトを作成する



var Application_Helper_UserInfo = {



};




* This source code was highlighted with Source Code Highlighter .









var Application_Helper_UserInfo = {



};




* This source code was highlighted with Source Code Highlighter .









var Application_Helper_UserInfo = {



};




* This source code was highlighted with Source Code Highlighter .












テストに合格しました。次のテストに進みます。

このオブジェクトは、 補助オブジェクトのタイプに属します 。 テストは、結果と設計を検証するためにのみ記述されます。

TDD方法論を使用した設計とは何ですか? これは、クラスメソッドとテストを使用した相互作用の説明です。



module( "User Info" );



test( "main" , function () {

equals( typeof (Application_Helper_UserInfo), "object" , "Check object" );



equals(Application_Helper_UserInfo.hasOwnProperty( "getHTML" ), true , "Check existing method getHTML" );



});




* This source code was highlighted with Source Code Highlighter .









module( "User Info" );



test( "main" , function () {

equals( typeof (Application_Helper_UserInfo), "object" , "Check object" );



equals(Application_Helper_UserInfo.hasOwnProperty( "getHTML" ), true , "Check existing method getHTML" );



});




* This source code was highlighted with Source Code Highlighter .









module( "User Info" );



test( "main" , function () {

equals( typeof (Application_Helper_UserInfo), "object" , "Check object" );



equals(Application_Helper_UserInfo.hasOwnProperty( "getHTML" ), true , "Check existing method getHTML" );



});




* This source code was highlighted with Source Code Highlighter .












そして実装:

var Application_Helper_UserInfo = {



getHTML: function () {

}



};




* This source code was highlighted with Source Code Highlighter .









var Application_Helper_UserInfo = {



getHTML: function () {

}



};




* This source code was highlighted with Source Code Highlighter .









var Application_Helper_UserInfo = {



getHTML: function () {

}



};




* This source code was highlighted with Source Code Highlighter .














さらにドキュメントでは、ユーザー情報が写真、名前、詳細情報で構成されていることがわかります。 したがって、次のように設計します。



module( "User Info" );



test( "main" , function () {

equals( typeof (Application_Helper_UserInfo), "object" , "Check object" );



equals(Application_Helper_UserInfo.hasOwnProperty( "getHTML" ), true , "Check existing method getHTML" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getPhoto" ), true , "Check existing private method _getPhoto" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getUsername" ), true , "Check existing private method _getUsername" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getInfo" ), true , "Check existing method _getInfo" );



});




* This source code was highlighted with Source Code Highlighter .









module( "User Info" );



test( "main" , function () {

equals( typeof (Application_Helper_UserInfo), "object" , "Check object" );



equals(Application_Helper_UserInfo.hasOwnProperty( "getHTML" ), true , "Check existing method getHTML" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getPhoto" ), true , "Check existing private method _getPhoto" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getUsername" ), true , "Check existing private method _getUsername" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getInfo" ), true , "Check existing method _getInfo" );



});




* This source code was highlighted with Source Code Highlighter .









module( "User Info" );



test( "main" , function () {

equals( typeof (Application_Helper_UserInfo), "object" , "Check object" );



equals(Application_Helper_UserInfo.hasOwnProperty( "getHTML" ), true , "Check existing method getHTML" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getPhoto" ), true , "Check existing private method _getPhoto" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getUsername" ), true , "Check existing private method _getUsername" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getInfo" ), true , "Check existing method _getInfo" );



});




* This source code was highlighted with Source Code Highlighter .












開始-新しいテストはパスしませんでした。





実装を作成しています。



var Application_Helper_UserInfo = {



getHTML: function () {

},



_getPhoto: function () {

},



_getUsername: function () {

},



_getInfo: function () {

}

};




* This source code was highlighted with Source Code Highlighter .









var Application_Helper_UserInfo = {



getHTML: function () {

},



_getPhoto: function () {

},



_getUsername: function () {

},



_getInfo: function () {

}

};




* This source code was highlighted with Source Code Highlighter .









var Application_Helper_UserInfo = {



getHTML: function () {

},



_getPhoto: function () {

},



_getUsername: function () {

},



_getInfo: function () {

}

};




* This source code was highlighted with Source Code Highlighter .












次に、メソッドの実装をテストする必要があります。 テストのために、モックオブジェクト(テスト用のスタブ)を作成し、そのコンテキストでテストを実施します。



module( "User Info" );



test( "main" , function () {

equals( typeof (Application_Helper_UserInfo), "object" , "Check object" );



equals(Application_Helper_UserInfo.hasOwnProperty( "getHTML" ), true , "Check existing method getHTML" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getPhoto" ), true , "Check existing private method _getPhoto" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getUsername" ), true , "Check existing private method _getUsername" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getInfo" ), true , "Check existing method _getInfo" );



var mockUserInfo = {

username: 'Name' ,

photo: 'photo.png' ,

info: 'Information'

};



var photo = Application_Helper_UserInfo._getPhoto.call(mockUserInfo);



equals(photo, 'photo.png' , 'Checking photo' );



var username = Application_Helper_UserInfo._getUsername.call(mockUserInfo);



equals(username, 'Name' , 'Checking username' );



var info = Application_Helper_UserInfo._getInfo.call(mockUserInfo);



equals(info, 'Information' , 'Checking information' );

});




* This source code was highlighted with Source Code Highlighter .









module( "User Info" );



test( "main" , function () {

equals( typeof (Application_Helper_UserInfo), "object" , "Check object" );



equals(Application_Helper_UserInfo.hasOwnProperty( "getHTML" ), true , "Check existing method getHTML" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getPhoto" ), true , "Check existing private method _getPhoto" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getUsername" ), true , "Check existing private method _getUsername" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getInfo" ), true , "Check existing method _getInfo" );



var mockUserInfo = {

username: 'Name' ,

photo: 'photo.png' ,

info: 'Information'

};



var photo = Application_Helper_UserInfo._getPhoto.call(mockUserInfo);



equals(photo, 'photo.png' , 'Checking photo' );



var username = Application_Helper_UserInfo._getUsername.call(mockUserInfo);



equals(username, 'Name' , 'Checking username' );



var info = Application_Helper_UserInfo._getInfo.call(mockUserInfo);



equals(info, 'Information' , 'Checking information' );

});




* This source code was highlighted with Source Code Highlighter .









module( "User Info" );



test( "main" , function () {

equals( typeof (Application_Helper_UserInfo), "object" , "Check object" );



equals(Application_Helper_UserInfo.hasOwnProperty( "getHTML" ), true , "Check existing method getHTML" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getPhoto" ), true , "Check existing private method _getPhoto" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getUsername" ), true , "Check existing private method _getUsername" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getInfo" ), true , "Check existing method _getInfo" );



var mockUserInfo = {

username: 'Name' ,

photo: 'photo.png' ,

info: 'Information'

};



var photo = Application_Helper_UserInfo._getPhoto.call(mockUserInfo);



equals(photo, 'photo.png' , 'Checking photo' );



var username = Application_Helper_UserInfo._getUsername.call(mockUserInfo);



equals(username, 'Name' , 'Checking username' );



var info = Application_Helper_UserInfo._getInfo.call(mockUserInfo);



equals(info, 'Information' , 'Checking information' );

});




* This source code was highlighted with Source Code Highlighter .














テストが失敗しました。テストに合格するための実装を作成する必要があります。 TDDの基本原則は、テストに合格するために必要なコードのみを記述することです。 これ以上。



var Application_Helper_UserInfo = {



getHTML: function () {

},



_getPhoto: function () {



return this .photo;

},



_getUsername: function () {



return this .username;

},



_getInfo: function () {



return this .info;

}

};




* This source code was highlighted with Source Code Highlighter .









var Application_Helper_UserInfo = {



getHTML: function () {

},



_getPhoto: function () {



return this .photo;

},



_getUsername: function () {



return this .username;

},



_getInfo: function () {



return this .info;

}

};




* This source code was highlighted with Source Code Highlighter .









var Application_Helper_UserInfo = {



getHTML: function () {

},



_getPhoto: function () {



return this .photo;

},



_getUsername: function () {



return this .username;

},



_getInfo: function () {



return this .info;

}

};




* This source code was highlighted with Source Code Highlighter .












なぜなら オブジェクトは補助オブジェクトのタイプに属するため、プライベートメソッドの機能に関する詳細なテストは記述されません。 オブジェクトの結果をチェックするテスト(この場合はgetHTMLメソッド)を記述するだけです。



module( "User Info" );



test( "main" , function () {

equals( typeof (Application_Helper_UserInfo), "object" , "Check object" );



equals(Application_Helper_UserInfo.hasOwnProperty( "getHTML" ), true , "Check existing method getHTML" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getPhoto" ), true , "Check existing private method _getPhoto" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getUsername" ), true , "Check existing private method _getUsername" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getInfo" ), true , "Check existing method _getInfo" );



var mockUserInfo = {

username: 'Name' ,

photo: 'photo.png' ,

info: 'Information'

};



var photo = Application_Helper_UserInfo._getPhoto.call(mockUserInfo);



equals(photo, 'photo.png' , 'Checking photo' );



var username = Application_Helper_UserInfo._getUsername.call(mockUserInfo);



equals(username, 'Name' , 'Checking username' );



var info = Application_Helper_UserInfo._getInfo.call(mockUserInfo);



equals(info, 'Information' , 'Checking information' );



var html = Application_Helper_UserInfo.getHTML.call(mockUserInfo);



if (html != undefined && html.indexOf( 'id="application_helper_userinfo"' ) != -1 && html.indexOf( 'Name' ) != -1 && html.indexOf( 'photo.png' ) != -1 && html.indexOf( 'Information' ) != -1) {



ok( true , "HTML ok" );



} else {

ok( false , "HTML does not pass" );

}



});




* This source code was highlighted with Source Code Highlighter .









module( "User Info" );



test( "main" , function () {

equals( typeof (Application_Helper_UserInfo), "object" , "Check object" );



equals(Application_Helper_UserInfo.hasOwnProperty( "getHTML" ), true , "Check existing method getHTML" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getPhoto" ), true , "Check existing private method _getPhoto" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getUsername" ), true , "Check existing private method _getUsername" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getInfo" ), true , "Check existing method _getInfo" );



var mockUserInfo = {

username: 'Name' ,

photo: 'photo.png' ,

info: 'Information'

};



var photo = Application_Helper_UserInfo._getPhoto.call(mockUserInfo);



equals(photo, 'photo.png' , 'Checking photo' );



var username = Application_Helper_UserInfo._getUsername.call(mockUserInfo);



equals(username, 'Name' , 'Checking username' );



var info = Application_Helper_UserInfo._getInfo.call(mockUserInfo);



equals(info, 'Information' , 'Checking information' );



var html = Application_Helper_UserInfo.getHTML.call(mockUserInfo);



if (html != undefined && html.indexOf( 'id="application_helper_userinfo"' ) != -1 && html.indexOf( 'Name' ) != -1 && html.indexOf( 'photo.png' ) != -1 && html.indexOf( 'Information' ) != -1) {



ok( true , "HTML ok" );



} else {

ok( false , "HTML does not pass" );

}



});




* This source code was highlighted with Source Code Highlighter .









module( "User Info" );



test( "main" , function () {

equals( typeof (Application_Helper_UserInfo), "object" , "Check object" );



equals(Application_Helper_UserInfo.hasOwnProperty( "getHTML" ), true , "Check existing method getHTML" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getPhoto" ), true , "Check existing private method _getPhoto" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getUsername" ), true , "Check existing private method _getUsername" );



equals(Application_Helper_UserInfo.hasOwnProperty( "_getInfo" ), true , "Check existing method _getInfo" );



var mockUserInfo = {

username: 'Name' ,

photo: 'photo.png' ,

info: 'Information'

};



var photo = Application_Helper_UserInfo._getPhoto.call(mockUserInfo);



equals(photo, 'photo.png' , 'Checking photo' );



var username = Application_Helper_UserInfo._getUsername.call(mockUserInfo);



equals(username, 'Name' , 'Checking username' );



var info = Application_Helper_UserInfo._getInfo.call(mockUserInfo);



equals(info, 'Information' , 'Checking information' );



var html = Application_Helper_UserInfo.getHTML.call(mockUserInfo);



if (html != undefined && html.indexOf( 'id="application_helper_userinfo"' ) != -1 && html.indexOf( 'Name' ) != -1 && html.indexOf( 'photo.png' ) != -1 && html.indexOf( 'Information' ) != -1) {



ok( true , "HTML ok" );



} else {

ok( false , "HTML does not pass" );

}



});




* This source code was highlighted with Source Code Highlighter .












以上です。 存在しないパラメータなどの詳細なチェックは実行されません。 オブジェクトは補助的なものであり、基本クラスまたはモジュールの場合、ステータスをチェックする追加のテスト、繰り返しの呼び出し、DOMツリーの操作などを記述する必要があります。



書き込まれたテストに合格しなかったため、オブジェクトの最終実装を記述する必要があります。



var Application_Helper_UserInfo = {



getHTML: function () {

var html = '<div id="application_helper_userinfo">' ;

html += '<div>' + Application_Helper_UserInfo._getPhoto.call( this ) + '</div>' ;

html += '<div>' + Application_Helper_UserInfo._getUsername.call( this ) + '</div>' ;

html += '<div>' + Application_Helper_UserInfo._getInfo.call( this ) + '</div>' ;

html += '</div>' ;

return html;

},



_getPhoto: function () {



return this .photo;

},



_getUsername: function () {



return this .username;

},



_getInfo: function () {



return this .info;

}

};




* This source code was highlighted with Source Code Highlighter .









var Application_Helper_UserInfo = {



getHTML: function () {

var html = '<div id="application_helper_userinfo">' ;

html += '<div>' + Application_Helper_UserInfo._getPhoto.call( this ) + '</div>' ;

html += '<div>' + Application_Helper_UserInfo._getUsername.call( this ) + '</div>' ;

html += '<div>' + Application_Helper_UserInfo._getInfo.call( this ) + '</div>' ;

html += '</div>' ;

return html;

},



_getPhoto: function () {



return this .photo;

},



_getUsername: function () {



return this .username;

},



_getInfo: function () {



return this .info;

}

};




* This source code was highlighted with Source Code Highlighter .









var Application_Helper_UserInfo = {



getHTML: function () {

var html = '<div id="application_helper_userinfo">' ;

html += '<div>' + Application_Helper_UserInfo._getPhoto.call( this ) + '</div>' ;

html += '<div>' + Application_Helper_UserInfo._getUsername.call( this ) + '</div>' ;

html += '<div>' + Application_Helper_UserInfo._getInfo.call( this ) + '</div>' ;

html += '</div>' ;

return html;

},



_getPhoto: function () {



return this .photo;

},



_getUsername: function () {



return this .username;

},



_getInfo: function () {



return this .info;

}

};




* This source code was highlighted with Source Code Highlighter .


















テストに合格すると、最終的なリファクタリングを実行できます。追加のチェックを追加し、より美しいデザインのためのcssなどを追加します。 しかし、最も重要なこと-今、私たちは何も壊れないことを確信しています。 ほぼ確実。 もちろん、TDDはすべての病気の治療法ではありませんが、絶えず進化しているプロジェクトにとっては、単にかけがえのないものです。



TDD方法論を使用すると、開発時間が約40%増加しますが、バグ修正の時間は短縮されます。 プロジェクトの保守と開発がはるかに簡単になり、安定性と一貫性が向上し、QAはインターフェイスを完全にテストできるだけです。 ちょうど今日、私のプロジェクトの2つのモジュールがテストされました-1つはTDDを使用し、もう1つは使用しません。 結果はTDDを支持します-最初のQAモジュールではIE9のグラフィックにわずかな問題しか見つかりませんでしたが、2番目ではテストによる開発中に100%検出された不快なバグです。



また、開発者にとって緊急の問題は、予備設計の欠如です。 タスクが単純に思える場合、多くの人がすぐに実装に関与し、設計が時間の浪費であることを考慮します。 設計は素晴らしいです。 最終コードを表示するのが恥ずかしい多くの状況は、事前の設計によって回避できました。



これで、おそらく、私は終了します。 この記事は予想以上のものでしたが、JavaScriptのTDDのほんの一部しか取り上げていません。

必要に応じて、TDDとAjax、複雑なモジュールのテスト、非同期状態のテスト、このための自動生成されたモックオブジェクトとフレームワークについて続けることができます。



ご清聴ありがとうございました。 またね



All Articles