基礎から見直す ASP.NET MVC
   の単体テスト自動化方法
~ Windows Azure 関連もあるかも~
   Microsft MVP for Windows Azure

                          割と普通



                                    1
自己紹介
• 割と普通 ( @normalian )
 – Windows Azure のコミュニティメンバ
   • Japan Windows Azure User Group
     http://r.jazug.jp/
 – わんくま同盟 のコミュニティメンバ
   • http://www.wankuma.com/
 – Microsoft MVP for Windows Azure 2010~




                                           2
本セッションの目的とゴール
• 目的
 – C#/VB.NET で単体テスト自動化するため、どのよ
   うにテストコードを記述すれば良いかを認知
 – 単体テスト自動化を支援するツール、ライブラリ群を
   認知


• ゴール
 – C#/VB.NET で、単体テストの自動化が可能なテ
   ストコードを効率的に記述することができる

                                 3
アジェンダ
 何故 単体テスト自動化 が必要?

  単体テスト自動化 のコツ

  単体テストを効率化するツール群

  まとめ

 参考
何で単体テスト自動化が必要?
• 良く言われるのは以下のメリット
 – 単体テストの工数を削減できる

 – コードの保守・再利用性が向上する

 – コードが綺麗になる




                      5
単体テストの工数を削減できる

仕様変更が増える程、単体
  テスト工数は増大




                  6
コードの保守・再利用性が向上する
• 変更に伴うデグレを瞬時に検知できる
 – リファクタリングや保守が容易
 – 単体テストコードの動作検証がいつでも可能


• 外部仕様が容易に理解できる
 – メソッドの外部仕様がテストコードに記載
 – ドキュメント・コードの二重化を防止



                          7
コードが綺麗になる
• ソースコードのテスタビリティが向上する
 – 単体テストの自動化を意識したインターフェースの定
   義を強制
 – テストコードをマニュアルとして利用可能
 – コーディング力が向上


• 若手、新人の教育向けに適用できる



                              8
アジェンダ
 何故 単体テスト自動化 が必要?

  単体テスト自動化 のコツ

  単体テストを効率化するツール群

  まとめ

 参考
単体テストの対象を明確化する
• データの入出力ポイントに対して単
  体テストを実施する   モデルのテスト

HTML/   Java       画面      コント    モデル
 CSS    Script     モデル     ローラ




JavaScript で記    JavaScript から
                                 コントローラの
述したロジック          C# へのマッピ
                                 テスト
のテスト             ング                     JavaScript
                                        C#
                                                     10
単体テスト自動化が容易なインター
   フェースを定義する 1/3
• 従来の ASP.NET Web Forms では、単体テ
  ストの自動化がきわめて困難
  メインコード




  テストコード
単体テスト自動化が容易なインター
  フェースを定義する 2/3
• ASP.NET MVC で単体テストの自動化が容易に
   メインコード




   テストコード
単体テスト自動化が容易なインター
   フェースを定義する 3/3
• Testing Framework や NUnit 等で自動テ
  スト可能な外部インターフェース設計とする
             ViewReult   コント
             • ViewBag   ローラ
             • Model

             Web API の
               応答


            XXXXResult
             (その他の応答)
                                    13
変数名の命名規則に留意する 1/2

• テストメソッド名、変数名からチェック対象を理解
  できるように命名する

良い例
 [TestMethod]
 public void Indexがリストを返す()
 {
    List<string> expect = new List<string>(){ "a", "b", "c" };
    List<string> actual = null;
    HomeController controller = new HomeController();
    actual = (controller.Index() as ViewResult).Model;
    Assert.Equals( expect, actual );
 }
                                                                 14
変数名の命名規則に留意する 2/2

• 「悪い例」では、テストメソッド名、変数名から
  チェック対象が理解できない

悪い例
 [TestMethod]
 public void Index ()
 {
    HomeController controller = new HomeController();

     var actual = (controller.Index() as ViewResult).Model;

     Assert.Equals(new List<string>(){ "a", "b", "c" }, actual );
 }
                                                                    15
1メソッド・1アサートを心がける 1/2
• テスト対象を明確化するため、1メソッド・1アサートを
  心がける
• if 文, for 文, while 文に Assert はダメ、絶対
良い例
 string expect = XXXXXX;
 string actual = null;

 (中間ロジック)

 Assert.Eqauls( expect, actual );



                                        16
1メソッド・1アサートを心がける 2/2
• 「悪い例」では、チェックする場所が散って何をチェックして
  いるか分からない
悪い例
 string expect = XXXXXX;
 string actual = null;

 (中間ロジック)

 if(flag == true) {
   Assert.Eqauls( expect, actual );
 }else{
     Assert.Fail(“ここは通らないはず”);
 }
                                      17
モックを効率的に作成する 1/2
• Moq.dll 等を利用して HttpContextBase,
  IPrincipal, IIdentity 等のモック作成が難しい
  クラスを作成する
 [TestMethod()]
 public void IndexTest01()
 {
                                                                                            • Moq.dll を利用してもス
   string expect = typeof(RedirectToRouteResult).FullName;
   string actual;
   BuyHistoryController target = new BuyHistoryController(new TestOrderRepository());
                                                                                              テップ数が多い
     //モックの作成
     var mockHttpContextBase = new Mock<HttpContextBase>();
                                                                                            • Moq.dll を利用しないと
     var mockIdentity = new Mock<IIdentity>();
     var mockPrincipal = new Mock<IPrincipal>();                                              数倍のコード行数になる
     //ユーザ情報の設定&httpContextオブジェクトの作成
     mockIdentity.Setup(identity => identity.IsAuthenticated).Returns(true);
     mockIdentity.Setup(identity => identity.Name).Returns("someUser");
     mockPrincipal.Setup(principal => principal.Identity).Returns(mockIdentity.Object);
     mockHttpContextBase.Setup(httpContextBase => httpContextBase.User)
        .Returns(mockPrincipal.Object);

     ControllerContext context = new ControllerContext(mockHttpContextBase.Object, new RouteData(), target);
     target.ControllerContext = context;
     actual = target.Index().GetType().FullName;
     Assert.AreEqual(expect, actual);
 }




                                                                                                                18
モックを効率的に作成する 2/2
• モック作成は行数が伸びるので共通化する
[TestMethod()]
public void IndexTest01(){
   string expected= typeof(RedirectToRouteResult).FullName;
   string actual;
   BuyHistoryController target =           モック作成を共通化
        new BuyHistoryController(new TestOrderRepository());
   ControllerContext context = new ControllerContext(
      Utils.CreateControllerContext(true, "someuser")
      , new RouteData(), target);
   target.ControllerContext = context;
   actual = target.Index().GetType().FullName;
  Assert.AreEqual(expect, actual);
}

                                                               19
名前空間、クラス名の命名規則に留意する

• テスト対象のプロジェクト、クラスが分かりやすいよう
  に命名規則を規定する
 – プロジェクト名:MyMVC → MyMVC.Test
 – クラス名: MyClass → MyClassTest
 – メソッド名: MyMethod → MyMethodTest


• 命名規約に従うことで、 TestDriven.NET を利
  用した、メインコード/テストコードの切り替えが可能
 – http://www.testdriven.net/quickstart.aspx

                                               20
アジェンダ
 何故 単体テスト自動化 が必要?

  単体テスト自動化 のコツ

  単体テストを効率化するツール群

  まとめ

 参考
ツールを利用したテスト効率化したい
• 紹介したツール・ライブラリを利用
  してテストを自動化する
HTML/   Java     画面    コント   モデル
 CSS    Script   モデル   ローラ




                                   JavaScript
                                   C#
                                                22
ツールを利用したテスト効率化(β)
• 紹介したツール・ライブラリを利用
  してテストを自動化する
HTML/    View          View    コント   モデル
 CSS     Model         Model   ローラ

                 knocko                Entity
    knock                  AutoM
                 ut.map                Frame
    out.js                 apper
                 ping.js                work



                                           JavaScript
                                           C#
                                                        23
ツールを利用したテスト効率化(β)
• 紹介したツール・ライブラリを利用
  してテストを自動化する
HTML/  View    View      コント モデル
 QUnit-tap
 CSS   Model   Model   Testing
                        ローラ
で単体テス knocko Framework Entity
 ト自動化 ut.map 単体テスト自動化
  knock          AutoM
                            Frame
  out.js         apper
node.exe ping.js MSTest.exe work

                                   JavaScript
                                   C#
                                                24
紹介するツール・ライブラリ群
• 今回は以下のライブラリ・ツールを紹介
 –   knockout.js
 –   knockout.mapping.js
 –   AutoMapper
 –   Qunit-tap
knockout.js
• DOM 要素と JSON オブジェクトのマッピング機能
 var viewModel = {
    left: ko.observable( 30 ),
    right: ko.observable( 40 )    onblur 等のイベントが発生した
 };                               タイミングで、ViewModel と
  viewModel.answer =              DOM要素で値を同期
     ko.dependentObservable(function () {
         return parseInt(this.left()) + parseInt(this.right());
     }, viewModel);
 ko.applyBindings(viewModel);

 <input type=“text” data-bind=“value: left” /> +
 <input type=“text” data-bind=“value: right” /> = <span data-
 bind="text: answer"></span>
                                                                26
Knockout.mapping.js 1/2
• C#/VB.NET の ViewModel と JSON オブジェクト
  をマッピング             { name: “若人”, age: 20}
                              等のJSONに置換される処理
    <script type="text/javascript">
      //JSONオブジェクトから、ViewModel を作成
      var initialData =
          @Html.Raw(Json.Encode(Model));
      var viewModel = ko.mapping.fromJS(initialData);

      //DOM要素を読み込み後、ViewModel を Binding
      $( function(){
           ko.applyBindings(viewModel);
       });
    </script>              DOM 要素 と viewModel イ
                            ンスタンスとを双方向バインド
                                                        27
Knockout.mapping.js 2/2
• C#/VB.NET の ViewModel と JSON オブジェクト
  をマッピング
   $.ajax({
     type:“POST”, dataType: "json",
     contentType: "application/json",
     data : ko.mapping.toJSON(viewModel),
     url: "@Url.Content("~/api/Values/")",
     success :
        function(res){ alert(JSON.stringify(res)); }
   });

  public class ValuesController : ApiController{
       public string Post(ViewModel viewModel){
             return "server recieved answer = " +
    viewModel.answer; }
                                                       28
AutoMapper
• Model – ViewModel といったモデル間のマッピ
  ングを実施する
• 複数モデルからの集約化等、細かな制御が可能

    Model                ViewModel




     項目1

     項目2
AutoMapper
• 「XXX.ID = YYY.ID」の羅列を防止できる
• マッピングが存在しない場合は一括でチェック可能
 Product product = ProductRepository.GetById(id);

 Mapping.AutoMapperBootstrapper.Configure();

 Mapper.CreateMap<Product, ProductViewModel>();
 Mapper.Map<Product, ProductViewModel>(product);

 var productViewModel = Mapper.Map<Product,
 ProductViewModel>(product);
QUnit-tap の利用
• node.exe から実行可能な QUnit
 – node.exe → Node.js の Windows 実装
 – QUnit → ブラウザ上で JavaScript を単体テスト
• knockout.js の ViewModel が対象?
  require('../test_helper.js');
  QUnit.test('my calc test', function() {
     var expect = 3;
     var actual = calc( 1, 2 );
     assert.equal(expect, actual);
  });
  QUnit.start();
アジェンダ
 何故 単体テスト自動化 が必要?

  単体テスト自動化 のコツ

  単体テストを効率化するツール群

  まとめ

 参考
まとめ
• ASP.NET MVC は単体テストの自動化に向
  いている

• 単体テストの自動化を実施するためにはコツ
  がある

• 単体テストの自動化を支援するツールは多々
  存在する
View と コントローラの構成 (β)
HTML/CSS                        ViewM
                View knockout.map       Contr   Model
    knockout.js Model   ping.js  odel   oller        Entity
                                                  Framework
HTML/CSS                       ViewM
                View knockout.map
    knockout.js
                Model ping.js odel              Model
HTML/CSS        View           ViewM                 Entity
                     knockout.map
    knockout.js
                Model ping.js odel                Framework

                                   1対N      N対M
           1対1 対応
                                   対応       対応

                                                対応関係
                                                C#
                                                JavaScript
アジェンダ
 何故 単体テスト自動化 が必要?

  単体テスト自動化 のコツ

  単体テストを効率化するツール群

  まとめ

 参考
参考 1/2
• InfoQ – ASP.NET MVC のテスト方法
   – http://www.infoq.com/jp/news/2012/03/aspnet-
     unit-test
• MSDN Library – ASP.NET MVC アプリケーションの単体テスト
   – http://msdn.microsoft.com/ja-
     jp/library/ff936235.aspx
• ASP.NET MVC3 における単体テストの基礎
   – http://codezine.jp/article/detail/6493
• wa りと na はてな日記 - Moq.dll on ASP.NET MVC その2
   – http://d.hatena.ne.jp/waritohutsu/20090909/
参考 2/2
• knockout.js Documentation > mapping
   – http://knockoutjs.com/documentation/plugins-
     mapping.html
• knockout.js Documentation > The “template”
  binding
   – http://knockoutjs.com/documentation/template-
     binding.html
• knockout.js の注意すべき点
   – http://d.hatena.ne.jp/shiba-yan/20120130/
• miso_soup3 - AutoMapper+ViewModel In MVC その2
   – http://d.hatena.ne.jp/miso_soup3/20120408/
余談!
というか



続編!
クラウド上で単体テスト自動化
• Trac Lightning on Windows Azure
  – https://github.com/normalian/WATracLightning
テスト自動化もクラウドへ…
単体テスト自動化

(手動キック)

 単体テストの自動化

 (自動キック)


   全てクラウドへ・・・
自はこ
    動ての
    テし
    スな
    トく
    坂遠
    をい
    よ
    ・
    ・
    ・




未
          ばのよオ
          かぼうレ
          りりやは
          だはく
          かじ




完
          らめ
          なた

基礎から見直す ASP.NET MVC の単体テスト自動化方法 ~ Windows Azure 関連もあるかも~