SlideShare a Scribd company logo
1 of 28
Jenkinsと一緒にTracプラグイン開発
                                                  -UT/IT自動化のコツ-




                                                          2011年5月20日
                                                        株式会社NTTデータ
Copyright © 2011 NTT DATA CORPORATION          技術開発本部 ALMソリューションセンタ
アジェンダ

                   1. 自己紹介
                   2. Trac Plugin開発 with Jenkins
                              1. 開発環境説明
                              2. 環境準備(ジョブ作成など)
                              3. コーディング
                              4. 単体テスト
                              5. 結合テスト

                   3. 最後に




Copyright © 2011 NTT DATA CORPORATION              1
自己紹介

    名前:和田 貴久
    Twitter ID:@wadatka
    所属:NTTデータ

    何ができる?:
              入社以来、Python道を攻略中です。
              それ以前は、データマイニングの研究や.netでの開発をやっていました。

    今の仕事:
     社内向けの開発支援ツールの開発
     各プロジェクトへの開発支援ツールの導入・普及活動


Copyright © 2011 NTT DATA CORPORATION               2
社内向けの開発支援ツールの開発

SDワークベンチというツールの開発を行っています。




■ SDワークベンチとは、Trac、Subversion、JenkinsなどOSSのツールを組み合わせオール
  インワンパッケージです。
■ ツール、利用方法、開発方法の3点セットのベストプラクティスを普及活動しています。
■ ツールの開発を行う中でTracプラグインの開発を行ってきました。
   • NTTデータならではの機能拡充
                   •       チケットの情報公開範囲の指定など
       • Trac・プラグインのバグフィックス

                                         • 開発・バグフィックスの中で、Jenkinsを活用してデプロイ・単体テス
                                           ト・結合テストを自動化しました。

                                         オールインワンパッケージのインストーラの生成では、Jenkinsを活用
                                         して日次ビルド・テストを行ってきました。
 Copyright © 2011 NTT DATA CORPORATION                                           3
Trac Plugin開発 with Jenkins

■ 弊社で行ってきたSDワークベンチ(開発支援ツール)の開発で、Jenkinsをどのように活
  用してきたか紹介します。

          ポイント
                                • 品質を担保するため、一定以上のテスト密度が必要。
                                   • バグ修正時の再テストの量が膨大
                                   • リリース毎のテストの量が膨大
                                  ⇒Jenkinsで自動化して効率的に!

          ポイント
                               • ゼロ機能リリースを実践!(ZFR:Zero-Feature Release)
                                 ⇒開発の最初に機能はないがリリースできる状態を用意
                                 ⇒以降の開発の進捗がわかりやすくなる!



Copyright © 2011 NTT DATA CORPORATION                                     4
Trac Plugin開発(開発環境)

Tracプラグインの開発は、下記の開発環境とサーバ構成で行いました。
                        開発者端末
                                                    • 実行結果(メール、IRC、・・・)
                                                    • レポート

                                        • コミット
         • バグチケット                       • チェックアウト/アップデート
                                                                               単体テストジョブ
                                                                      CIサーバ         Seleniumジョブ
                                                      • チェックアウト/
                                                       アップデート
                                                                                    • Java
                                                                                    • Python
                                                       • ポーリング

                                                                          デプロイジョブ
      ポイント
                                                                      デプロイサーバ(Slave)
           • CIサーバやデプロイサーバは、ジョブ
             の実行で負荷がかかるので、                                                          • Java
             Trac・Subversionと分けて構築しました!                                             • Python
                                                                                    • Trac


Copyright © 2011 NTT DATA CORPORATION                                                             5
Trac Plugin開発(開発準備)

  「何を作るか」「どのように作るか」は、決まっているものとします。
  Jenkinsとコラボレーションするための準備をします。
• 準備は、次の2ステップで行います。
   1. Subversionへの雛型フォルダ・ファイルのインポート
                • プラグイン本体コード
                                            コードの中身はなくてもOK!
                • 単体テストコード
                                            まずは、フォルダと空ファイルだけでもインポート
                • Seleniumコード
      2. Jenkinsのジョブの作成
                •           単体テストジョブ
                                            インポートしたコードのチェックアウトと実行を行うジョブを作成
                •           デプロイジョブ
                                            結果は空だが、いつでも実行できるジョブを用意
                •           Seleniumジョブ

              ポイント
                                   • CIやるならコーディング前からジョブを作るべし!
                                   • ZFR を実践!!!

Copyright © 2011 NTT DATA CORPORATION                                        6
Trac Plugin開発(SCMに雛型インポート)

Subversionに雛型フォルダ・ファイルをインポートします。
                                           プラグイン本体コード
         リポジトリ


                                                         単体テストコード




                                           Seleniumコード




           ポイント
                                • プラグインのソースコードやテストコード、Seleniumコードは、
                                  同じフォルダ構成にするべし!
                                • フォルダの構成・ジョブのコピーで再利用が簡単に!!!
 Copyright © 2011 NTT DATA CORPORATION                                7
Trac Plugin開発(単体テストジョブ)

• 流れ:チェックアウト⇒単体テストコード実行⇒結果の変換
• Jenkinsプラグイン:Cobertura Plguin、violations
• その他のツール:ant、python-coverage、pylint、nose

                                                      CIサーバー(Masterマシン)
      リポジトリ
                                                     ワークスペース            単体テストジョブ
                                                    Ant                •チェックアウト
                                         チェックアウト
                                                    HelloWorldPlugin   •Ant実行      Antで定義した処理
                                                                                   •   ワークスペースの初期化
                                                                       •結果保存       •   プラグインのインストール
                                                    Result
                                                                                   •   単体テストコードの実行
                                                                                   •   テスト結果の集計
                                          チェックアウト                                  •   カバレッジの集計
                                                                                   •   Pylintでの解析
                                                                                   •   集計・解析結果の変換


                                              ポイント
                                                   • 出てきた結果は視覚化したい!!!
                                                     ⇒各種プラグインで読み込むために変換スクリプト
                                                     を実装。

 Copyright © 2011 NTT DATA CORPORATION                                                                8
Trac Plugin開発(単体テストジョブでの利用ファイル)

単体テストジョブで実行したAntファイル
<?xml version="1.0"?>                                                                              (続き)
<project name="pythonUnitTest" default="report">
  <property name="project.name" value="pythonUnitTest"/>                                              <!-- report -->
                                                                                                      <target name="report" depends="test">                                       レポート変換
  <!-- Properties for all source file folders -->                                                        <!-- report of nose -->
  <property name="src.dir" value="plugins"/>                                                             <exec dir="${basedir}" executable="python.exe">
                                                                                                            <arg line="converter.py test ${src.dir}¥nosetest.xml ${result.dir}¥nosetest.xml"/>
  <property   name="result.dir" value="result"/>                                                         </exec>
  <property   name="result.test" value="${result.dir}/nosetest.xml"/>
  <property   name="result.pylint" value="${result.dir}/pylint.txt.raw"/>                                <!-- report of coverage(xml) -->
  <property   name="result.coverage" value="${result.dir}/coverage.xml"/>                                <exec dir="${src.dir}" executable="coverage.exe">
  <property   name="result.coverage.dir" value="${result.dir}/coverage"/>                                   <arg line="xml --omit=*__init__.py,*setup.py,*¥*¥tests¥*.py"/>
                                                                                                         </exec>
  <!-- init -->
  <target name="init">
                                           初期化                                                           <exec dir="${basedir}" executable="python.exe">
     <delete dir="${result.dir}"/>                                                                         <arg line="converter.py test ${src.dir}¥coverage.xml ${src.dir}¥coverage2.xml"/>
     <mkdir dir="${result.dir}"/>                                                                        </exec>
     <mkdir dir="${result.coverage.dir}"/>
     <exec dir="${src.dir}" executable="coverage.exe">                                                   <!-- report of coverage(html) -->
        <arg line="-e"/>                                                                                 <exec dir="${src.dir}" executable="coverage.exe">
     </exec>                                                                                                <arg line="-b -i -d ..¥${result.coverage.dir} --omit=*__init__.py,*setup.py,*¥*¥tests¥*.py"/>
  </target>                                                                                              </exec>

  <!-- test -->                                                                                          <!-- report of pylint -->
  <target name="test" depends="init">                                             テスト                    <exec dir="${src.dir}" executable="pylint.bat" output="${result.pylint}">
     <exec dir="${src.dir}" executable="nosetests.exe">                                                     <arg line="-f parseable -i y . --output-format=parseable --
        <arg line="-i .*¥.*¥tests¥*.py --with-xunit --xunit-file=nosetest.xml --with-coverage"/>   ignore=decoder.py,report_editor.py,setup.py,tests"/>
     </exec>                                                                                             </exec>
  </target>                                                                                              <exec dir="${basedir}" executable="python.exe">
                                                                                                            <arg line="converter.py pylint ${result.dir}¥pylint.txt.raw ${result.dir}¥pylint.txt"/>
                                                                                                         </exec>
                                                                                                      </target>
                                                                                       (続く・・・)     </project>



   Copyright © 2011 NTT DATA CORPORATION                                                                                                                                                                    9
Trac Plugin開発(単体テストジョブでの利用ファイル)

単体テストジョブで実行したAntファイル
# -*- coding: utf-8 -*-                                                       (続き)
import codecs
import os                                                                     argvs = sys.argv
import re                                                                     argc = len(argvs)
import sys
                                                                              if (argc != 4):
global _workspace_path                                                            print 'Usage: python %s mode input_file output_file' % argvs[0]
_workspace_path = os.path.dirname(os.path.abspath(__file__))
                                                                              if argvs[1] == 'test':
print _workspace_path                                                             convert_character_code(argvs[2], argvs[3])
                                                                              elif argvs[1] == 'pylint':
def convert_character_code(input_file, output_file):                              convert_escape_character(argvs[2], argvs[3])
   input = codecs.open(input_file, 'r', 'shift_jis')
   output = codecs.open(output_file, 'w', 'utf-8')

   for line in input:                                  単体テスト結果、
      output.write(line)
                                                       カバレッジの変換
   output.close()
   input.close()
   print output_file + ' is generated from ' + input_file

def convert_escape_character(input_file, output_file):
   input = open(input_file, 'r')
   output = open(output_file, 'w',)

   for line in input:
      if '__init__.py' in line:                      Pylintの結果の変換
           continue
      else:
           output.write(re.sub('¥¥¥¥', '/', line))

   output.close()
   input.close()
   print output_file + ‘ is generated from ’ + input_file           (続く・・・)


    Copyright © 2011 NTT DATA CORPORATION                                                                                                           10
Trac Plugin開発(単体テストジョブ実行結果実例1)

単体テストジョブ実行結果




Copyright © 2011 NTT DATA CORPORATION                11
Trac Plugin開発(単体テストジョブ実行結果実例2)

コードチェッカー(Pylint)とコード・カバレッジレポート




      ポイント
              • Pythonだけど、Javaと同じように失敗個所、問題個所を簡単に確認できて、
                早めの対応が可能に!

 Copyright © 2011 NTT DATA CORPORATION                    12
Trac Plugin開発(デプロイジョブ)

• 流れ:チェックアウト⇒プラグインインストール⇒Trac再起動
• Jenkinsプラグイン: -
• その他のツール: -

                                                    CIサーバー(Masterマシン)   ポイント
     リポジトリ
                                                                         • デプロイサーバーに直
                                                    デプロイジョブ
                                                  •チェックアウト
                                                                           接チェックアウトしてい
                                                  •Windowsバッチの実行           た。
                                                                           ⇒テストのリビジョンと
                                                                           デプロイのリビジョンが
                                        チェックアウト                            異なることが。。。
                                                    デプロイサーバー
                                                    (Slaveマシン)

                                                                        • デプロイサーバーには、事前に
                                                   ワークスペース
                                                                          Trac+Subversionをインストール
                                                  HelloWorldPlugin        が必要
                                                                        • プラグインのインストールは、
                                                                          「python setup.py install」コマンド
                                                                          で実行

Copyright © 2011 NTT DATA CORPORATION                                                                     13
Trac Plugin開発(Seleniumジョブ)

• 流れ:チェックアウト⇒Seleniumコード実行⇒結果出力
• Jenkinsプラグイン:HTML Publisher Plugin(結果表示に利用)
• その他のツール:firefox、Selenium、HTMLTestRunner.py

                                                    CIサーバー(Masterマシン)
      リポジトリ
                                                    ワークスペース          Seleniumジョブ
                                                   Selenium         •チェックアウト
                                                                    •Selenium実行
                                         チェックアウト                    •結果保存


                                                   Seleniumテスト          ポイント
                                                                        • SeleniumジョブのSeleniumコードの実行は、
                                                    デプロイサーバー              バッチとPythonのコードで実装しました。
                                                    (Slaveマシン)          • 下記のプラグインを使うとよりスマートに
                                                                          実行できます。
                                                                              • Selenium Plugin
                                                                              • Selenium AES Plugin
                                                                              • Seleniumhq Plugin
                                                                              • SeleniumRC Plugin
                                                                              • seleniumhtmlreport Plugin


 Copyright © 2011 NTT DATA CORPORATION                                                                      14
Trac Plugin開発(Seleniumジョブ実行結果)

ジョブのトップページからSeleniumコードの実行結果を表示できます。




Copyright © 2011 NTT DATA CORPORATION                15
Trac Plugin開発

ジョブの準備ができたので、実際に開発例を紹介します。
1. コーディング
                プラグイン本体のコードを実装
                テストコードの実装
2. 単体テスト
                コーディング時に実装しなかったテストケースを実装(異常系など)
3. 結合テスト
                Seleniumコードを実装
■ 今回実装するコードは、管理画面に「HelloWorldPlugin」のメニューを表示し、リンクをク
  リックしたときに画面上に「Hello World」と表示します。




Copyright © 2011 NTT DATA CORPORATION                 16
Trac Plugin開発(コーディング 1/3)

Tracの管理画面を表示するメソッドを実装します。
「helloworld.html」ページに変数「helloworld」に値「Hello World」を格納して渡します。

プラグインのコード(web_ui.py)                                         テストコード(test_web_ui.py)
  def render_admin_panel(self, req, cat, page, path_info):   def test_render_admin_panel(self):
     req.perm.require('TRAC_ADMIN')                                pass
     data = {}
     return 'helloworld.html', data


 def render_admin_panel(self, req, cat, page, path_info):    def test_render_admin_panel(self):
     req.perm.require('TRAC_ADMIN')                                cat = 'helloworldplugin'
     data = {'helloworld': "Hello World"}                          page = 'helloworld'
     return 'helloworld.html', data                                path_info = ''
                                                                   self.req.perm = PermissionCache(self.env, 'admin')

                                                                  helloworld_admin = HelloWorldAdminPage(self.env)
                                                                  templdate, data = helloworld_admin.render_admin_panel( self.req, cat, page, path_info )
                                                                  assert templdate == 'helloworld.html'
                                                                  assert data == {'helloworld': ''}
       画面上の変更箇所




 Copyright © 2011 NTT DATA CORPORATION                                                                                                                      17
Trac Plugin開発(コーディング 2/3)

単体テストでエラー発生!!
「assert data == {‘helloworld’: ‘’}」でAssertionErrorが発生。




                                                         エラー!



  Copyright © 2011 NTT DATA CORPORATION                         18
Trac Plugin開発(コーディング 3/3)

エラーの発生しているテストコードを修正
        テストコード(test_web_ui.py)
         def test_render_admin_panel(self):                       def test_render_admin_panel(self):
               cat = 'helloworldplugin'                                 cat = 'helloworldplugin'
               ・・・                                                      ・・・
               assert data == {'helloworld': ''}                        assert data == {'helloworld': ‘Hello World'}

                                              変数「helloworld」の期待値として「Hello World」を設定します。




                                                                                            ALL OK!!



Copyright © 2011 NTT DATA CORPORATION                                                                                  19
Trac Plugin開発(単体テスト 1/2)

異常系操作を行ったときのテストを実装します。
                                    Tracの管理画面は、管理者権限を持つユーザだけアクセス可能。
                                    管理者権限がないユーザでアクセスしたときのテストを実装。

                               テストコード(test_web_ui.py)
                                        def test_render_admin_panel_error(self):
                                           cat = 'helloworldplugin'
                                           page = 'helloworld'
                                           path_info = ''
                                           self.req.perm = PermissionCache(self.env, ‘wada')

                                          try:
                                              helloworld_admin = HelloWorldAdminPage(self.env)
                                              templdate, data = helloworld_admin.render_admin_panel( self.req, cat, page, self.path_info )
                                          except PermissionError:
                                              pass
                                          else:
                                              self.fail()


                                                                   期待した権限を持つユーザでないときに発生するエラー




Copyright © 2011 NTT DATA CORPORATION                                                                                                        20
Trac Plugin開発(単体テスト 2/2)

追加したテストの分だけ件数が増加した。




Copyright © 2011 NTT DATA CORPORATION          21
Trac Plugin開発(結合テスト 1/2)

画面からのオペレーションテストをSeleniumで実装します。
テストは2つ行います。
•           Tracの管理画面にアクセスした際に、メニューに「HelloWorldPlugin」と「HelloWorld」が表示されているか。
                   def test_func_01(self):
                         sel = self.selenium
                         num, cap_num = init(1)

                            sel.open("http://%s:%s@%s/trac/SampleProject/login" % (user, password, server))
                            sel.open("http://%s/trac/SampleProject/admin" % server)                           Trac管理画面へアクセス

                            self.failUnless(sel.is_text_present("HelloWorldPlugin"))                          メニューの確認
                            self.failUnless(sel.is_text_present(“HelloWorld"))
                            cap_num = capture(sel, num, cap_num, test_id, evidence)                           画面キャプチャの取得
    •        リンク「helloworld」をクリックしたとき、画面に「Hello World」と表示されるか。
                   def test_func_02(self):
                         sel = self.selenium
                         num, cap_num = init(2)

                            sel.open("http://%s:%s@%s/trac/SampleProject/login" % (user, password, server))
                            sel.open("http://%s/trac/SampleProject/admin" % server)
                                                                                                              Trac管理画面へアクセス

                            sel.click(u"link=HelloWorld")                                                     実装したページへのリンク
                            self.failUnless(sel.is_text_present("Hello World"))                               「Hello World」の表示確認
                            cap_num = capture(sel, num, cap_num, test_id, evidence)                           画面キャプチャの取得

        Copyright © 2011 NTT DATA CORPORATION                                                                                      22
Trac Plugin開発(結合テスト 2/2)

Seleniumコードの実行結果のサマリページと各テストの実行結果証跡を出力!!!




                                               Seleniumテストの実行証跡
                                               FUNC_01_01_01.png
                                                              FUNC_01_02_01.png




Copyright © 2011 NTT DATA CORPORATION                                             23
最後に

まとめ
  • コーディングをする前やコーディングの早い段階でジョブを作成したので、
    様々なメリットが得られました!!!(ZFRを実践!)
                  • リファクタリング時のデグレーションの早期発見
                  • 実装量、テスト実施量の見える化でモチベーションアップ


      • 単体テスト~結合テストまでの自動化で再テスト時の作業の効率化が
        できた!!!
                  • バグ修正時の再テストの効率化
                  • リリース毎のテスト作業の削減




Copyright © 2011 NTT DATA CORPORATION            24
今後取り組みたいこと

      • 初期の雛型コードと雛型ジョブを一発で作成したい。

      • Jenkinsからバグトラッカーへの連携を設定したい。

      • XFDをつけたい。

      • Selenium関連のプラグインを効果的に使いたい。




Copyright © 2011 NTT DATA CORPORATION   25
おまけ

• 現在、Jenkinsに関する本書いています!!
• 今年度中に出版します。
• ぜひ書店で見かけた際には、買ってください!

• こんな内容が書かれる予定です。
    1.     継続的インテグレーションについて
           継続的インテグレーションについて
    2.     Jenkinsについて
           Jenkinsについて
    3.     Jenkinsインストールと
                  インストールと基本設定
           Jenkinsインストールと基本設定
    4.     Jenkinsの基本的な
           Jenkinsの基本的な使い方
    5.     Jenkinsを ってWeb開発をしてみよう
           Jenkinsを使ってWeb開発をしてみよう
                      Web開発
    6.     高度な
           高度な使い方




Copyright © 2011 NTT DATA CORPORATION   26
Copyright © 2011 NTT DATA CORPORATION

More Related Content

What's hot

OPNFV Handson Tokyo #1
OPNFV Handson Tokyo #1OPNFV Handson Tokyo #1
OPNFV Handson Tokyo #1Mibu Ryota
 
OPNFVのコンポーネントと調べ方
OPNFVのコンポーネントと調べ方OPNFVのコンポーネントと調べ方
OPNFVのコンポーネントと調べ方Mibu Ryota
 
密着! nibohsiデプロイ 13:00-13:05 - railsアプリのデプロイ事例 -
密着! nibohsiデプロイ 13:00-13:05 - railsアプリのデプロイ事例 -密着! nibohsiデプロイ 13:00-13:05 - railsアプリのデプロイ事例 -
密着! nibohsiデプロイ 13:00-13:05 - railsアプリのデプロイ事例 -Yukihiko SAWANOBORI
 
継続的デリバリー読書会 第 7 章 コミットステージ
継続的デリバリー読書会 第 7 章 コミットステージ継続的デリバリー読書会 第 7 章 コミットステージ
継続的デリバリー読書会 第 7 章 コミットステージYasutomo Arai
 
開発環境構築からはじめるPython VisualStudio Codeとpipenvで始めるpython
開発環境構築からはじめるPython VisualStudio Codeとpipenvで始めるpython開発環境構築からはじめるPython VisualStudio Codeとpipenvで始めるpython
開発環境構築からはじめるPython VisualStudio Codeとpipenvで始めるpythontomitomi3 tomitomi3
 
キーワード駆動によるシステムテストの自動化について 2015
キーワード駆動によるシステムテストの自動化について 2015キーワード駆動によるシステムテストの自動化について 2015
キーワード駆動によるシステムテストの自動化について 2015Toru Koido
 
Java 10でぼくたちの生活はどう変わるの?
Java 10でぼくたちの生活はどう変わるの?Java 10でぼくたちの生活はどう変わるの?
Java 10でぼくたちの生活はどう変わるの?Yuji Kubota
 

What's hot (9)

OPNFV Handson Tokyo #1
OPNFV Handson Tokyo #1OPNFV Handson Tokyo #1
OPNFV Handson Tokyo #1
 
OPNFVのコンポーネントと調べ方
OPNFVのコンポーネントと調べ方OPNFVのコンポーネントと調べ方
OPNFVのコンポーネントと調べ方
 
密着! nibohsiデプロイ 13:00-13:05 - railsアプリのデプロイ事例 -
密着! nibohsiデプロイ 13:00-13:05 - railsアプリのデプロイ事例 -密着! nibohsiデプロイ 13:00-13:05 - railsアプリのデプロイ事例 -
密着! nibohsiデプロイ 13:00-13:05 - railsアプリのデプロイ事例 -
 
継続的デリバリー読書会 第 7 章 コミットステージ
継続的デリバリー読書会 第 7 章 コミットステージ継続的デリバリー読書会 第 7 章 コミットステージ
継続的デリバリー読書会 第 7 章 コミットステージ
 
快適・簡単・安心なアプリE2Eテストの実行環境 #stac2017
快適・簡単・安心なアプリE2Eテストの実行環境 #stac2017快適・簡単・安心なアプリE2Eテストの実行環境 #stac2017
快適・簡単・安心なアプリE2Eテストの実行環境 #stac2017
 
開発環境構築からはじめるPython VisualStudio Codeとpipenvで始めるpython
開発環境構築からはじめるPython VisualStudio Codeとpipenvで始めるpython開発環境構築からはじめるPython VisualStudio Codeとpipenvで始めるpython
開発環境構築からはじめるPython VisualStudio Codeとpipenvで始めるpython
 
キーワード駆動によるシステムテストの自動化について 2015
キーワード駆動によるシステムテストの自動化について 2015キーワード駆動によるシステムテストの自動化について 2015
キーワード駆動によるシステムテストの自動化について 2015
 
Java 10でぼくたちの生活はどう変わるの?
Java 10でぼくたちの生活はどう変わるの?Java 10でぼくたちの生活はどう変わるの?
Java 10でぼくたちの生活はどう変わるの?
 
Opnfv handson apex intro
Opnfv handson apex introOpnfv handson apex intro
Opnfv handson apex intro
 

Similar to Trac Plugin Developement with Jenkins

Androidアプリ開発のテスト環境
Androidアプリ開発のテスト環境Androidアプリ開発のテスト環境
Androidアプリ開発のテスト環境Toshiyuki Hirata
 
UIテストの実行時間の短縮の方法
UIテストの実行時間の短縮の方法UIテストの実行時間の短縮の方法
UIテストの実行時間の短縮の方法Toshiyuki Hirata
 
JenkinsとSeleniumの活用事例
JenkinsとSeleniumの活用事例JenkinsとSeleniumの活用事例
JenkinsとSeleniumの活用事例Takeshi Kondo
 
OSSで作るOpenStack監視システム
OSSで作るOpenStack監視システムOSSで作るOpenStack監視システム
OSSで作るOpenStack監視システムsatsuki fukazu
 
バージョンアップ対応を軽減するサービス:マスティフ
バージョンアップ対応を軽減するサービス:マスティフバージョンアップ対応を軽減するサービス:マスティフ
バージョンアップ対応を軽減するサービス:マスティフToshiyuki Hirata
 
Xcodeの管理を楽に - Jenkins編 -
Xcodeの管理を楽に - Jenkins編 -Xcodeの管理を楽に - Jenkins編 -
Xcodeの管理を楽に - Jenkins編 -Toshiyuki Hirata
 
Awsで実現するseleniumテスト高速術
Awsで実現するseleniumテスト高速術Awsで実現するseleniumテスト高速術
Awsで実現するseleniumテスト高速術finoue
 
毎日が憧れの新築、反復可能なデリバリーによる常時新築システム
毎日が憧れの新築、反復可能なデリバリーによる常時新築システム毎日が憧れの新築、反復可能なデリバリーによる常時新築システム
毎日が憧れの新築、反復可能なデリバリーによる常時新築システムTomohiro Ohtake
 
GraalVMのJavaネイティブビルド機能でどの程度起動が速くなるのか?~サーバレス基盤上での評価~ / How fast does GraalVM's...
GraalVMのJavaネイティブビルド機能でどの程度起動が速くなるのか?~サーバレス基盤上での評価~ / How fast does GraalVM's...GraalVMのJavaネイティブビルド機能でどの程度起動が速くなるのか?~サーバレス基盤上での評価~ / How fast does GraalVM's...
GraalVMのJavaネイティブビルド機能でどの程度起動が速くなるのか?~サーバレス基盤上での評価~ / How fast does GraalVM's...Shinji Takao
 
Voicepic@FacebookNight
Voicepic@FacebookNightVoicepic@FacebookNight
Voicepic@FacebookNightManabu Shimobe
 
HeapStats: Introduction and Technical Preview
HeapStats: Introduction and Technical PreviewHeapStats: Introduction and Technical Preview
HeapStats: Introduction and Technical PreviewYuji Kubota
 
Jenkins study jenkins build-cicdi
Jenkins study jenkins build-cicdiJenkins study jenkins build-cicdi
Jenkins study jenkins build-cicdi昌桓 李
 
Code for Japan 勉強会 Vol.1 CKAN入門 プロジェクトのFork、デプロイ、CIまで
Code for Japan 勉強会 Vol.1 CKAN入門 プロジェクトのFork、デプロイ、CIまでCode for Japan 勉強会 Vol.1 CKAN入門 プロジェクトのFork、デプロイ、CIまで
Code for Japan 勉強会 Vol.1 CKAN入門 プロジェクトのFork、デプロイ、CIまでNaoyuki Yamada
 
Voicepic@FukuiMASeminar
Voicepic@FukuiMASeminarVoicepic@FukuiMASeminar
Voicepic@FukuiMASeminarManabu Shimobe
 
テスト勉強会よしおか100311 1
テスト勉強会よしおか100311 1テスト勉強会よしおか100311 1
テスト勉強会よしおか100311 1Hiro Yoshioka
 
fastlane x iOSアプリのCI
fastlane x iOSアプリのCIfastlane x iOSアプリのCI
fastlane x iOSアプリのCIToshiyuki Hirata
 
はてなにおける継続的デプロイメントの現状と Docker の導入
はてなにおける継続的デプロイメントの現状と Docker の導入はてなにおける継続的デプロイメントの現状と Docker の導入
はてなにおける継続的デプロイメントの現状と Docker の導入Yu Nobuoka
 

Similar to Trac Plugin Developement with Jenkins (20)

Androidアプリ開発のテスト環境
Androidアプリ開発のテスト環境Androidアプリ開発のテスト環境
Androidアプリ開発のテスト環境
 
UIテストの実行時間の短縮の方法
UIテストの実行時間の短縮の方法UIテストの実行時間の短縮の方法
UIテストの実行時間の短縮の方法
 
JenkinsとSeleniumの活用事例
JenkinsとSeleniumの活用事例JenkinsとSeleniumの活用事例
JenkinsとSeleniumの活用事例
 
OSSで作るOpenStack監視システム
OSSで作るOpenStack監視システムOSSで作るOpenStack監視システム
OSSで作るOpenStack監視システム
 
バージョンアップ対応を軽減するサービス:マスティフ
バージョンアップ対応を軽減するサービス:マスティフバージョンアップ対応を軽減するサービス:マスティフ
バージョンアップ対応を軽減するサービス:マスティフ
 
Xcodeの管理を楽に - Jenkins編 -
Xcodeの管理を楽に - Jenkins編 -Xcodeの管理を楽に - Jenkins編 -
Xcodeの管理を楽に - Jenkins編 -
 
Awsで実現するseleniumテスト高速術
Awsで実現するseleniumテスト高速術Awsで実現するseleniumテスト高速術
Awsで実現するseleniumテスト高速術
 
毎日が憧れの新築、反復可能なデリバリーによる常時新築システム
毎日が憧れの新築、反復可能なデリバリーによる常時新築システム毎日が憧れの新築、反復可能なデリバリーによる常時新築システム
毎日が憧れの新築、反復可能なデリバリーによる常時新築システム
 
GraalVMのJavaネイティブビルド機能でどの程度起動が速くなるのか?~サーバレス基盤上での評価~ / How fast does GraalVM's...
GraalVMのJavaネイティブビルド機能でどの程度起動が速くなるのか?~サーバレス基盤上での評価~ / How fast does GraalVM's...GraalVMのJavaネイティブビルド機能でどの程度起動が速くなるのか?~サーバレス基盤上での評価~ / How fast does GraalVM's...
GraalVMのJavaネイティブビルド機能でどの程度起動が速くなるのか?~サーバレス基盤上での評価~ / How fast does GraalVM's...
 
Voicepic@FacebookNight
Voicepic@FacebookNightVoicepic@FacebookNight
Voicepic@FacebookNight
 
HeapStats: Introduction and Technical Preview
HeapStats: Introduction and Technical PreviewHeapStats: Introduction and Technical Preview
HeapStats: Introduction and Technical Preview
 
Play jjug2012spring
Play jjug2012springPlay jjug2012spring
Play jjug2012spring
 
Jenkins study jenkins build-cicdi
Jenkins study jenkins build-cicdiJenkins study jenkins build-cicdi
Jenkins study jenkins build-cicdi
 
Code for Japan 勉強会 Vol.1 CKAN入門 プロジェクトのFork、デプロイ、CIまで
Code for Japan 勉強会 Vol.1 CKAN入門 プロジェクトのFork、デプロイ、CIまでCode for Japan 勉強会 Vol.1 CKAN入門 プロジェクトのFork、デプロイ、CIまで
Code for Japan 勉強会 Vol.1 CKAN入門 プロジェクトのFork、デプロイ、CIまで
 
Redmine Applied for Large Scale
Redmine Applied  for Large ScaleRedmine Applied  for Large Scale
Redmine Applied for Large Scale
 
Voicepic@FukuiMASeminar
Voicepic@FukuiMASeminarVoicepic@FukuiMASeminar
Voicepic@FukuiMASeminar
 
テスト勉強会よしおか100311 1
テスト勉強会よしおか100311 1テスト勉強会よしおか100311 1
テスト勉強会よしおか100311 1
 
fastlane x iOSアプリのCI
fastlane x iOSアプリのCIfastlane x iOSアプリのCI
fastlane x iOSアプリのCI
 
ドリコムJenkins勉強会資料
ドリコムJenkins勉強会資料ドリコムJenkins勉強会資料
ドリコムJenkins勉強会資料
 
はてなにおける継続的デプロイメントの現状と Docker の導入
はてなにおける継続的デプロイメントの現状と Docker の導入はてなにおける継続的デプロイメントの現状と Docker の導入
はてなにおける継続的デプロイメントの現状と Docker の導入
 

Trac Plugin Developement with Jenkins

  • 1. Jenkinsと一緒にTracプラグイン開発 -UT/IT自動化のコツ- 2011年5月20日 株式会社NTTデータ Copyright © 2011 NTT DATA CORPORATION 技術開発本部 ALMソリューションセンタ
  • 2. アジェンダ 1. 自己紹介 2. Trac Plugin開発 with Jenkins 1. 開発環境説明 2. 環境準備(ジョブ作成など) 3. コーディング 4. 単体テスト 5. 結合テスト 3. 最後に Copyright © 2011 NTT DATA CORPORATION 1
  • 3. 自己紹介 名前:和田 貴久 Twitter ID:@wadatka 所属:NTTデータ 何ができる?: 入社以来、Python道を攻略中です。 それ以前は、データマイニングの研究や.netでの開発をやっていました。 今の仕事: 社内向けの開発支援ツールの開発 各プロジェクトへの開発支援ツールの導入・普及活動 Copyright © 2011 NTT DATA CORPORATION 2
  • 4. 社内向けの開発支援ツールの開発 SDワークベンチというツールの開発を行っています。 ■ SDワークベンチとは、Trac、Subversion、JenkinsなどOSSのツールを組み合わせオール インワンパッケージです。 ■ ツール、利用方法、開発方法の3点セットのベストプラクティスを普及活動しています。 ■ ツールの開発を行う中でTracプラグインの開発を行ってきました。 • NTTデータならではの機能拡充 • チケットの情報公開範囲の指定など • Trac・プラグインのバグフィックス • 開発・バグフィックスの中で、Jenkinsを活用してデプロイ・単体テス ト・結合テストを自動化しました。 オールインワンパッケージのインストーラの生成では、Jenkinsを活用 して日次ビルド・テストを行ってきました。 Copyright © 2011 NTT DATA CORPORATION 3
  • 5. Trac Plugin開発 with Jenkins ■ 弊社で行ってきたSDワークベンチ(開発支援ツール)の開発で、Jenkinsをどのように活 用してきたか紹介します。 ポイント • 品質を担保するため、一定以上のテスト密度が必要。 • バグ修正時の再テストの量が膨大 • リリース毎のテストの量が膨大 ⇒Jenkinsで自動化して効率的に! ポイント • ゼロ機能リリースを実践!(ZFR:Zero-Feature Release) ⇒開発の最初に機能はないがリリースできる状態を用意 ⇒以降の開発の進捗がわかりやすくなる! Copyright © 2011 NTT DATA CORPORATION 4
  • 6. Trac Plugin開発(開発環境) Tracプラグインの開発は、下記の開発環境とサーバ構成で行いました。 開発者端末 • 実行結果(メール、IRC、・・・) • レポート • コミット • バグチケット • チェックアウト/アップデート 単体テストジョブ CIサーバ Seleniumジョブ • チェックアウト/ アップデート • Java • Python • ポーリング デプロイジョブ ポイント デプロイサーバ(Slave) • CIサーバやデプロイサーバは、ジョブ の実行で負荷がかかるので、 • Java Trac・Subversionと分けて構築しました! • Python • Trac Copyright © 2011 NTT DATA CORPORATION 5
  • 7. Trac Plugin開発(開発準備) 「何を作るか」「どのように作るか」は、決まっているものとします。 Jenkinsとコラボレーションするための準備をします。 • 準備は、次の2ステップで行います。 1. Subversionへの雛型フォルダ・ファイルのインポート • プラグイン本体コード コードの中身はなくてもOK! • 単体テストコード まずは、フォルダと空ファイルだけでもインポート • Seleniumコード 2. Jenkinsのジョブの作成 • 単体テストジョブ インポートしたコードのチェックアウトと実行を行うジョブを作成 • デプロイジョブ 結果は空だが、いつでも実行できるジョブを用意 • Seleniumジョブ ポイント • CIやるならコーディング前からジョブを作るべし! • ZFR を実践!!! Copyright © 2011 NTT DATA CORPORATION 6
  • 8. Trac Plugin開発(SCMに雛型インポート) Subversionに雛型フォルダ・ファイルをインポートします。 プラグイン本体コード リポジトリ 単体テストコード Seleniumコード ポイント • プラグインのソースコードやテストコード、Seleniumコードは、 同じフォルダ構成にするべし! • フォルダの構成・ジョブのコピーで再利用が簡単に!!! Copyright © 2011 NTT DATA CORPORATION 7
  • 9. Trac Plugin開発(単体テストジョブ) • 流れ:チェックアウト⇒単体テストコード実行⇒結果の変換 • Jenkinsプラグイン:Cobertura Plguin、violations • その他のツール:ant、python-coverage、pylint、nose CIサーバー(Masterマシン) リポジトリ ワークスペース 単体テストジョブ Ant •チェックアウト チェックアウト HelloWorldPlugin •Ant実行 Antで定義した処理 • ワークスペースの初期化 •結果保存 • プラグインのインストール Result • 単体テストコードの実行 • テスト結果の集計 チェックアウト • カバレッジの集計 • Pylintでの解析 • 集計・解析結果の変換 ポイント • 出てきた結果は視覚化したい!!! ⇒各種プラグインで読み込むために変換スクリプト を実装。 Copyright © 2011 NTT DATA CORPORATION 8
  • 10. Trac Plugin開発(単体テストジョブでの利用ファイル) 単体テストジョブで実行したAntファイル <?xml version="1.0"?> (続き) <project name="pythonUnitTest" default="report"> <property name="project.name" value="pythonUnitTest"/> <!-- report --> <target name="report" depends="test"> レポート変換 <!-- Properties for all source file folders --> <!-- report of nose --> <property name="src.dir" value="plugins"/> <exec dir="${basedir}" executable="python.exe"> <arg line="converter.py test ${src.dir}¥nosetest.xml ${result.dir}¥nosetest.xml"/> <property name="result.dir" value="result"/> </exec> <property name="result.test" value="${result.dir}/nosetest.xml"/> <property name="result.pylint" value="${result.dir}/pylint.txt.raw"/> <!-- report of coverage(xml) --> <property name="result.coverage" value="${result.dir}/coverage.xml"/> <exec dir="${src.dir}" executable="coverage.exe"> <property name="result.coverage.dir" value="${result.dir}/coverage"/> <arg line="xml --omit=*__init__.py,*setup.py,*¥*¥tests¥*.py"/> </exec> <!-- init --> <target name="init"> 初期化 <exec dir="${basedir}" executable="python.exe"> <delete dir="${result.dir}"/> <arg line="converter.py test ${src.dir}¥coverage.xml ${src.dir}¥coverage2.xml"/> <mkdir dir="${result.dir}"/> </exec> <mkdir dir="${result.coverage.dir}"/> <exec dir="${src.dir}" executable="coverage.exe"> <!-- report of coverage(html) --> <arg line="-e"/> <exec dir="${src.dir}" executable="coverage.exe"> </exec> <arg line="-b -i -d ..¥${result.coverage.dir} --omit=*__init__.py,*setup.py,*¥*¥tests¥*.py"/> </target> </exec> <!-- test --> <!-- report of pylint --> <target name="test" depends="init"> テスト <exec dir="${src.dir}" executable="pylint.bat" output="${result.pylint}"> <exec dir="${src.dir}" executable="nosetests.exe"> <arg line="-f parseable -i y . --output-format=parseable -- <arg line="-i .*¥.*¥tests¥*.py --with-xunit --xunit-file=nosetest.xml --with-coverage"/> ignore=decoder.py,report_editor.py,setup.py,tests"/> </exec> </exec> </target> <exec dir="${basedir}" executable="python.exe"> <arg line="converter.py pylint ${result.dir}¥pylint.txt.raw ${result.dir}¥pylint.txt"/> </exec> </target> (続く・・・) </project> Copyright © 2011 NTT DATA CORPORATION 9
  • 11. Trac Plugin開発(単体テストジョブでの利用ファイル) 単体テストジョブで実行したAntファイル # -*- coding: utf-8 -*- (続き) import codecs import os argvs = sys.argv import re argc = len(argvs) import sys if (argc != 4): global _workspace_path print 'Usage: python %s mode input_file output_file' % argvs[0] _workspace_path = os.path.dirname(os.path.abspath(__file__)) if argvs[1] == 'test': print _workspace_path convert_character_code(argvs[2], argvs[3]) elif argvs[1] == 'pylint': def convert_character_code(input_file, output_file): convert_escape_character(argvs[2], argvs[3]) input = codecs.open(input_file, 'r', 'shift_jis') output = codecs.open(output_file, 'w', 'utf-8') for line in input: 単体テスト結果、 output.write(line) カバレッジの変換 output.close() input.close() print output_file + ' is generated from ' + input_file def convert_escape_character(input_file, output_file): input = open(input_file, 'r') output = open(output_file, 'w',) for line in input: if '__init__.py' in line: Pylintの結果の変換 continue else: output.write(re.sub('¥¥¥¥', '/', line)) output.close() input.close() print output_file + ‘ is generated from ’ + input_file (続く・・・) Copyright © 2011 NTT DATA CORPORATION 10
  • 13. Trac Plugin開発(単体テストジョブ実行結果実例2) コードチェッカー(Pylint)とコード・カバレッジレポート ポイント • Pythonだけど、Javaと同じように失敗個所、問題個所を簡単に確認できて、 早めの対応が可能に! Copyright © 2011 NTT DATA CORPORATION 12
  • 14. Trac Plugin開発(デプロイジョブ) • 流れ:チェックアウト⇒プラグインインストール⇒Trac再起動 • Jenkinsプラグイン: - • その他のツール: - CIサーバー(Masterマシン) ポイント リポジトリ • デプロイサーバーに直 デプロイジョブ •チェックアウト 接チェックアウトしてい •Windowsバッチの実行 た。 ⇒テストのリビジョンと デプロイのリビジョンが チェックアウト 異なることが。。。 デプロイサーバー (Slaveマシン) • デプロイサーバーには、事前に ワークスペース Trac+Subversionをインストール HelloWorldPlugin が必要 • プラグインのインストールは、 「python setup.py install」コマンド で実行 Copyright © 2011 NTT DATA CORPORATION 13
  • 15. Trac Plugin開発(Seleniumジョブ) • 流れ:チェックアウト⇒Seleniumコード実行⇒結果出力 • Jenkinsプラグイン:HTML Publisher Plugin(結果表示に利用) • その他のツール:firefox、Selenium、HTMLTestRunner.py CIサーバー(Masterマシン) リポジトリ ワークスペース Seleniumジョブ Selenium •チェックアウト •Selenium実行 チェックアウト •結果保存 Seleniumテスト ポイント • SeleniumジョブのSeleniumコードの実行は、 デプロイサーバー バッチとPythonのコードで実装しました。 (Slaveマシン) • 下記のプラグインを使うとよりスマートに 実行できます。 • Selenium Plugin • Selenium AES Plugin • Seleniumhq Plugin • SeleniumRC Plugin • seleniumhtmlreport Plugin Copyright © 2011 NTT DATA CORPORATION 14
  • 17. Trac Plugin開発 ジョブの準備ができたので、実際に開発例を紹介します。 1. コーディング プラグイン本体のコードを実装 テストコードの実装 2. 単体テスト コーディング時に実装しなかったテストケースを実装(異常系など) 3. 結合テスト Seleniumコードを実装 ■ 今回実装するコードは、管理画面に「HelloWorldPlugin」のメニューを表示し、リンクをク リックしたときに画面上に「Hello World」と表示します。 Copyright © 2011 NTT DATA CORPORATION 16
  • 18. Trac Plugin開発(コーディング 1/3) Tracの管理画面を表示するメソッドを実装します。 「helloworld.html」ページに変数「helloworld」に値「Hello World」を格納して渡します。 プラグインのコード(web_ui.py) テストコード(test_web_ui.py) def render_admin_panel(self, req, cat, page, path_info): def test_render_admin_panel(self): req.perm.require('TRAC_ADMIN') pass data = {} return 'helloworld.html', data def render_admin_panel(self, req, cat, page, path_info): def test_render_admin_panel(self): req.perm.require('TRAC_ADMIN') cat = 'helloworldplugin' data = {'helloworld': "Hello World"} page = 'helloworld' return 'helloworld.html', data path_info = '' self.req.perm = PermissionCache(self.env, 'admin') helloworld_admin = HelloWorldAdminPage(self.env) templdate, data = helloworld_admin.render_admin_panel( self.req, cat, page, path_info ) assert templdate == 'helloworld.html' assert data == {'helloworld': ''} 画面上の変更箇所 Copyright © 2011 NTT DATA CORPORATION 17
  • 19. Trac Plugin開発(コーディング 2/3) 単体テストでエラー発生!! 「assert data == {‘helloworld’: ‘’}」でAssertionErrorが発生。 エラー! Copyright © 2011 NTT DATA CORPORATION 18
  • 20. Trac Plugin開発(コーディング 3/3) エラーの発生しているテストコードを修正 テストコード(test_web_ui.py) def test_render_admin_panel(self): def test_render_admin_panel(self): cat = 'helloworldplugin' cat = 'helloworldplugin' ・・・ ・・・ assert data == {'helloworld': ''} assert data == {'helloworld': ‘Hello World'} 変数「helloworld」の期待値として「Hello World」を設定します。 ALL OK!! Copyright © 2011 NTT DATA CORPORATION 19
  • 21. Trac Plugin開発(単体テスト 1/2) 異常系操作を行ったときのテストを実装します。 Tracの管理画面は、管理者権限を持つユーザだけアクセス可能。 管理者権限がないユーザでアクセスしたときのテストを実装。 テストコード(test_web_ui.py) def test_render_admin_panel_error(self): cat = 'helloworldplugin' page = 'helloworld' path_info = '' self.req.perm = PermissionCache(self.env, ‘wada') try: helloworld_admin = HelloWorldAdminPage(self.env) templdate, data = helloworld_admin.render_admin_panel( self.req, cat, page, self.path_info ) except PermissionError: pass else: self.fail() 期待した権限を持つユーザでないときに発生するエラー Copyright © 2011 NTT DATA CORPORATION 20
  • 23. Trac Plugin開発(結合テスト 1/2) 画面からのオペレーションテストをSeleniumで実装します。 テストは2つ行います。 • Tracの管理画面にアクセスした際に、メニューに「HelloWorldPlugin」と「HelloWorld」が表示されているか。 def test_func_01(self): sel = self.selenium num, cap_num = init(1) sel.open("http://%s:%s@%s/trac/SampleProject/login" % (user, password, server)) sel.open("http://%s/trac/SampleProject/admin" % server) Trac管理画面へアクセス self.failUnless(sel.is_text_present("HelloWorldPlugin")) メニューの確認 self.failUnless(sel.is_text_present(“HelloWorld")) cap_num = capture(sel, num, cap_num, test_id, evidence) 画面キャプチャの取得 • リンク「helloworld」をクリックしたとき、画面に「Hello World」と表示されるか。 def test_func_02(self): sel = self.selenium num, cap_num = init(2) sel.open("http://%s:%s@%s/trac/SampleProject/login" % (user, password, server)) sel.open("http://%s/trac/SampleProject/admin" % server) Trac管理画面へアクセス sel.click(u"link=HelloWorld") 実装したページへのリンク self.failUnless(sel.is_text_present("Hello World")) 「Hello World」の表示確認 cap_num = capture(sel, num, cap_num, test_id, evidence) 画面キャプチャの取得 Copyright © 2011 NTT DATA CORPORATION 22
  • 24. Trac Plugin開発(結合テスト 2/2) Seleniumコードの実行結果のサマリページと各テストの実行結果証跡を出力!!! Seleniumテストの実行証跡 FUNC_01_01_01.png FUNC_01_02_01.png Copyright © 2011 NTT DATA CORPORATION 23
  • 25. 最後に まとめ • コーディングをする前やコーディングの早い段階でジョブを作成したので、 様々なメリットが得られました!!!(ZFRを実践!) • リファクタリング時のデグレーションの早期発見 • 実装量、テスト実施量の見える化でモチベーションアップ • 単体テスト~結合テストまでの自動化で再テスト時の作業の効率化が できた!!! • バグ修正時の再テストの効率化 • リリース毎のテスト作業の削減 Copyright © 2011 NTT DATA CORPORATION 24
  • 26. 今後取り組みたいこと • 初期の雛型コードと雛型ジョブを一発で作成したい。 • Jenkinsからバグトラッカーへの連携を設定したい。 • XFDをつけたい。 • Selenium関連のプラグインを効果的に使いたい。 Copyright © 2011 NTT DATA CORPORATION 25
  • 27. おまけ • 現在、Jenkinsに関する本書いています!! • 今年度中に出版します。 • ぜひ書店で見かけた際には、買ってください! • こんな内容が書かれる予定です。 1. 継続的インテグレーションについて 継続的インテグレーションについて 2. Jenkinsについて Jenkinsについて 3. Jenkinsインストールと インストールと基本設定 Jenkinsインストールと基本設定 4. Jenkinsの基本的な Jenkinsの基本的な使い方 5. Jenkinsを ってWeb開発をしてみよう Jenkinsを使ってWeb開発をしてみよう Web開発 6. 高度な 高度な使い方 Copyright © 2011 NTT DATA CORPORATION 26
  • 28. Copyright © 2011 NTT DATA CORPORATION