vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

Azure DevOps 関西 2018 に参加してきました

はじめに

1/12 に 日本マイクロソフト株式会社 関西支店 で行われた、 Azure DevOps 関西 2018 に参加してきました。

jazug.connpass.com

詳細なレポートが書ければよかったのですが、ど素人ゆえ印象に残った部分を中心にメモ程度に書き残しておきます。

セッション1

@kosmosebi さんによる Azure DevOps の概要紹介です。

www.slideshare.net

私も年末年始で Azure Pipelines を使ってあれこれしていましたが、単体で使うのではなく、Azure Board など他のサービスと組み合わせることでもっと便利に使えそうだな、というのが印象に残りました。

ただ、正直なところ次のセッションで使うデモ用プロジェクトの作り直しなどで必死だったため、公開された資料を改めて読んで色々試してみたいと思います。

セッション 2

私による Azure Pipelines 使ってみた話です。

冒頭からサービス名(×Azure Pipeline -> 〇Azure Pipelines )を間違えるなど、拙い発表ではあったと思いますが、皆様に温かく見守っていただいたおかげでなんとか無事終えることができました。

speakerdeck.com

yaml で何してるの?などの話が(避けてくれてたのかもしれませんが)被らなくて助かった、とは思いましたw

以下反省。

デモ用のプロジェクト

プロジェクトのリポジトリをメインのプロジェクトフォルダで作ってしまい、テストプロジェクトが Git 管理できず困るというトラブルがありました。

PipelineSample <- ここに作る
    LPipelineSample
    LPipelineSample.Test

×

PipelineSample
    LPipelineSample <- ここに作ってしまった
    LPipelineSample.Test

普段 Rider でプロジェクトを作るときに自動で Git init してもらっていたので、まぁ気を付けましょうという話でした。

ディスプレイ設定について

PowerPoint だけ使って話をする場合、ディスプレイを拡張設定にすると手元で時間や次のスライドがサムネで見られたりして便利です。

が、今回のように Rider など別のソフトウェアを合わせて使う場合、カーソルが見つからず困る、ということがありました。

また今どれくらい話をしているか、という時間を PowerPoint で表示されるものを当てにしていたのですが、スライドショーを停止 -> 別のソフトウェアを使って説明 -> スライドショー再開 などしていたため、時間がわからなくなる、といったことがありました。

時間は別で見ることにして、ミラーリングにしておいた方が良かったかな。。。

60分の発表について

これは反省、というか感想ですが、今回初となった 60 分のセッションについて。

いつも LT でお話させてもらうことが多く、前提となる話をどの程度含めるか?といったところに悩むのですが、これだけ時間があると十分時間が取れる(むしろ早く終わりすぎてしまった)ので、助かるなぁと思いました。

あと完全にテンパった状態で始めて、我に返ってもまだ時間があるという苦笑

話の組み立て方など、他の方のお話も参考にブラッシュアップしていきたいところですね(今後チャンスがあるとして)。

セッション 3

Azure DevOps におけるテストと、 Azure Pipelines と他の CI ツールとの比較、といった内容を中心に @Posaune さんがお話されていました。

テスト

Azure DevOps には Azure Pipelines での自動テストと、 Test Plans での手動テスト支援があります。

手動テストの支援ツールというのは、(たくさんあるのかもしれませんが)初めて見たため、面白そうだなぁと思いながら聴いていました。

CI ツールなど自動化のためのソフトウェアをあまり使い慣れていないチームの場合、こういうところから入っていくのがやりやすそう?な気はします。

有料ということなのですが、30 日のお試しとかあるのかしら。

他の CI ツールとの比較

Jenkins や CircleCI などとの比較について。

yaml ではなく visual designer(GUI) で処理が書ける、というのは他のサービスだと少ない( yaml でバリバリ書く)、というのが印象的でした。

yaml での書き方に慣れてくると問題ないのかもしれませんが、特に初めて CI ツールに触れる私のような人には躓きが少なくて良いですね(私は yaml で書こうとしていましたが)。

あとは Microsoft 社が実行環境として VM を提供する辺りも違うところ。

実際には Unity などのビルドは EULA 的な問題がありそう、といった悩ましい問題もあるのですが、 .NET Core や Xcode のビルドなどであれば十分できてしまう、というのはすごいですよね。

ただ、これは次のセッションだったかもしれませんが、モバイルアプリのビルドに特化して考える場合、 App Center を使うことを考えた方が良いようです。

セッション 4

@kkamegawa さんによる Azure DevOps のセキュリティ設定周りのお話。

www.slideshare.net

これまで私がやってきたように、一人でモクモク開発するためだけに使うのであれば良いのですが、会社で導入する、といった規模が大きくなってくるときには必ず考えなければいけない話でもあります。

話の中心となるのは Azure Active Directory であったわけなのですが、全く知識がないためついていけなかった、というのが正直なところ orz

今後 Azure Active Directory について学んでみて、改めて上記資料を読み返してみたいと思います(その頃には新しい資料を出されているかもしれませんが)。

ただ印象に残っているのは、例えばあるプロダクトを作るためのプロジェクトに対する権限を付与する際に、どういう単位で行うか、というお話。

部、課などの単位にしてしまうと、異動したりなくなってしまうかもしれず、その際プロジェクトの権限割り振りはどうするの?となってしまいます。

プロダクトを制作するチームに付与すると、もし開発が中止になるなど、プロダクトが無くなったら権限の付与を考える必要はなくなる。

といった、できるだけ無くなった(変更があった)ときに困らない単位で権限を付与すると良い、という話がありました。

これは Azure Active Directory 以外でも使える話だな、と思いながら聴いていました。

あとリージョンについて、日本では東アジア(香港)辺りのものを使うことになる。

中国にもあるが、これは法律的な必要性から用意されているので中国法人じゃないとアクセスできない(少なくともログインは不可)、という話も(直接今の仕事で必要になるかはわかりませんが)印象的でした。

LT

飛び入りで @shinsukeoda さんによる SQL Server LocalDB のお話。

https://www.slideshare.net/odashinsuke/pipelines-sql-server-localdb

内容はセッションの途中で出た、 CI でテストを行うときの DB を使ったテストをどうするか?という質問に対するものです。

内容もそうなのですが、こういう飛び入りでお話ができてしまう、というのがまず凄いです。

で、その内容ですが、

  • テストにあたって毎回 DB にテーブルを作り、データを入れるというのは非常に手間
  • BACPAC という形式のファイルで元の DB からデータをエクスポートし、それをテスト開始前に読み込むことで簡素化できる
  • インポートには .NET Framework のライブラリが必要 -> PowerShell

また Microsoft-hosted agents で提供されている VM( Windows Server) 上の SQL Server は 2016 であり、SQL Server LocalDB( 開発者を対象とした SQL Server Expressの機能少ない版 )です。

そのため状況によっては使うことができない恐れはありますが、もし使えるのであればこれが安価で簡単なので、まず試してみては?とのこと。

Azure Pipelines で調べていた時も DB を使ったプロジェクトの開発、というところまで到達できなかったため、上記のような情報も参考にしたいと思いました。

おわりに

今回はいつも以上にスポンサーも豪華で、 米Microsoft社、日本マイクロソフト社、RevDebug に加え、SUBWAY 社による軽食までいただけてしまいました。

ありがたい限りです。

あと、セッション中の質問を口頭だけでなく、 Web 上でも受け付けていたのが印象深かったです。

app2.sli.do

会場の盛り上がりとしては口頭が良いのでしょうが、注目をあびる中聞きづらかったり、別のセッション中に質問が思い浮かんだりしたときに聞きづらい、という良さがあったと思います。

最後に、拙い発表であったとは思いますが、お声がけいただいた @kkamegawa さんをはじめ、主催者の方々、スポンサーの皆様、温かく見守ってくださった参加者の皆様、ありがとうございました(..)_

スライドの最後でもお話しましたが、 Azure Pipelines だけでなく、 Jenkins など他の CI ツールも使ってみて、より便利に活用していく方法を学んでみたいと思います。

また、セッション 1 などで紹介されていた Azure DevOps の他のツールとの組み合わせも試してみたいところ。

…当面無料で使えるもの限定となりそうですが(;´Д`)

【ASP.NET Core】Azure Pipelines でコンティニュアスにインテグレーションしてみる その2

はじめに

※ 2019/01/12 更新

  • Azure Pipeline → Azure Pipelines に修正しました。
  • Agent は VM など実行環境上にインストールされているもの、という内容に沿うよう修正しました。

さぁ年も明けましたのでね。張り切っていつも通りぼちぼちとやっていきたいと思います。

前回触れたジョブを実行する Agent から、気になったものをあれこれ調べてみることにします。

Agent について

まずは設定したジョブを実行する環境と Azure Pipelines を連携する Agent から。

Agent は実行環境であるマシン上にインストールされているものです。

以前 Unity プロジェクトをビルドしようとしたときに登録した、自前のマシンを使う Self-hosted agents と、 Microsoft が提供する VM 上で動く Microsoft-hosted agents があります。

ちなみにこれ、下記でいう The Microsoft Azure Virtual Machine Agent が指しているものと同じでしょうか。
(なんか微妙に違う気はします)

なおプロジェクトで使用できる Agent は下記で見ることができます。

https://dev.azure.com/ユーザー名/_settings/agentpools

Microsoft-hosted agents に何がインストールされているかは、上記サイトで Agent を選択 -> Details から見ることができます。

f:id:mslGt:20190105101809j:plain

動きとしては、 Pipeline 実行のたびに新規で VM (Agent がインストールされている) が用意され、 Pipeline の処理が終了するとともに破棄されるとのこと。

前回実行した内容を使って。。。といったことはできないので、設定などの準備を毎回実行するか、 Self-hosted agents として自前で用意するか、という判断となります。

なお上記ページでは Self-hosted agents と比べて Microsoft-hosted agents を強力にプッシュしています。

これは、特に初心者だと自前のホストでやろうとすると色々失敗するでしょ?まずは用意されたものを使ってみ?という意味なのか、 Self-hosted agents の方が Microsoft-hosted agents より Azure ?にかかる負荷が大きい、といった意味なのでしょうか。

といった、どっちでも良いことを考えたり考えなかったりしました。

OAuth token

これも Microsoft Docs で読んだ内容ですが、 Azure Pipelines と Agent との連携はこんな感じだそうです。

  1. Azure Pipelines から Agent を Agent pool に追加
  2. 1.の時 Agent では listener OAuth token をインストールし、ジョブの Queue を待ち受ける
  3. Pipeline 実行時、 Agent は Queue からジョブのリクエストを受ける
  4. 3 の時、 job-specific OAuth token をダウンロードする
  5. ジョブを実行する
  6. ジョブが完了したら、 job-specific OAuth token を破棄し、 Agent は 2 の状態に戻る

2.の時?に実行環境である VM を用意するステップがあるはずなのですが、調べただけだとよくわかりませんでした orz

ビルドの流れ

Azure Pipelines と Agent の流れはわかったようなわからないような状態となりましたが、そもそも GitHub に対して Pull Request や Push を送ったあとビルドが完了するまで、というのはどのような流れとなっているのでしょうか。

  1. Azure Pipelines から GitHub を監視?
  2. ユーザーが GitHub に Pull Request を送る
  3. Azure Pipelines が 2 を受け取り、ジョブの Queue を実行
  4. VM を用意する
  5. Queue はジョブのリクエストを発行する
  6. Agent がリクエストを受け取り、 job-specific OAuth token をインストール
  7. Agent でジョブを実行
  8. ジョブが完了したら Agent から Azure Pipelines にログとともに通知
  9. ジョブ完了後の処理として job-specific OAuth token の破棄などを行う
  10. Agent は次のジョブリクエストを待ち受ける
  11. 全ジョブが完了したら Agent を破棄する? または作り直す?

ところどころよく分かっていませんが、こういうことのようです。

結局 Azure Pipelines は何をしているのか

GitHub に Pull Request や Push が飛んでくるのを監視するのと(これは本当に Azure Pipelines がやっているのかわかりませんが)、ジョブの Queue を実行する、 Agent を管理する、といったことをしています。

つまり、ビルドやテストといった、実際に実行したい内容は(今回の場合) dotnet など他のソフトウェアが実行する、ということです。

ということは、あくまで Azure Pipelines として考えるべきことは、(ビルドやテストなどの)やりたいことをどう呼び出すか、ということとなります。

これは Azure Pipelines に限らず CI/CD ツールを使う上で大切。。。だったりしないのかなぁ。

topic ブランチのビルド設定を追加する

さて、そろそろ実際に動かしてみることにします。

master ブランチのみで開発し続けるのであれば、ローカルでの変更を Push していくだけでビルドされていくのですが、通常ブランチを切って( Microsoft Docs にならって topic ブランチと呼ぶことにします) 開発 -> Pull request -> merge という流れになるかと思います。

が、デフォルトの設定だとビルドの対象は master のみになっており、 topic ブランチの変更を Push してもビルドされません。

また、テストを追加したとしても自動で実行はされません。

ということでこれを変更してみます。
(テストの中身はこちらを参照)

azure-pipeline.yml

~省略~

trigger:
- master
- features/*

pool:
  vmImage: 'Ubuntu-16.04'

variables:
  buildConfiguration: 'Release'

steps:
- script: dotnet build --configuration $(buildConfiguration)
  displayName: 'dotnet build $(buildConfiguration)'
- script: dotnet test
    displayName: 'dotnet test'

pr:
- master
- features/*

最後の pr (Pull Request ビルド対象のブランチ指定)は何となく入れましたが、今回の場合特に意味はないです。

Shell script を実行する

Pull request を送ったあと、 master 側でテストが通ったら自動でマージするようにできないのかな?と思いました。

ということで調べたところ、 Azure Pipelines にはそのような機能がないようです。

これを見るとちょうどマージについて書かれているため、これを試してみることにします。

とりあえず Pull request がきたら、などの条件は置いておくことにします。

上記ページでは bat を使っていますが、実行環境を Ubuntu にしているので Shell script で試してみることにしました。

azure-pipeline.yml

~省略~
steps:
  - script: dotnet build --configuration $(buildConfiguration)
    displayName: 'dotnet build $(buildConfiguration)'
  - script: dotnet test
    displayName: 'dotnet test $(buildConfiguration)'
  - task: ShellScript@2
    inputs:
      scriptPath: git_merge.sh
    displayName: 'git merge'

~省略~
  • task につけられる名前は決まっているようで、今回の場合「ShellScript@2」以外の名前を付けるとエラーとなってしまいました。

ログを見ると、この名前によって Shell script であること、またそのバージョンを指定しているようでした ( Shell scirpt 2.1.3 )。

これで Shell script 自体は実行できます。

で、先ほどのページの Git コマンドをちょっと変更してこのようにしてみました。

git_merge.sh (ちゃんと動きません)

#!/bin/bash

echo SOURCE BRANCH IS %BUILD_SOURCEBRANCH%
if [%BUILD_SOURCEBRANCH% == refs/heads/master]; then 
   echo Building master branch so no merge is needed.
   exit
else
   echo merge
fi

set sourceBranch=origin/%BUILD_SOURCEBRANCH:refs/heads/=%
echo GIT CHECKOUT MASTER
git checkout master
echo GIT STATUS
git status
echo GIT MERGE
git merge %sourceBranch% -m "Merge to master"
echo GIT STATUS
git status
echo GIT PUSH
git push origin
echo GIT STATUS
git status

これを動かすと、下記のようなログが出て、マージはできませんでした。

2019-01-05T00:28:26.6264228Z ##[section]Starting: git merge
2019-01-05T00:28:26.6267544Z ==============================================================================
2019-01-05T00:28:26.6268233Z Task         : Shell Script
2019-01-05T00:28:26.6268617Z Description  : Run a shell script using bash
2019-01-05T00:28:26.6268730Z Version      : 2.1.3
2019-01-05T00:28:26.6268870Z Author       : Microsoft Corporation
2019-01-05T00:28:26.6269023Z Help         : [More Information](https://go.microsoft.com/fwlink/?LinkID=613738)
2019-01-05T00:28:26.6269214Z ==============================================================================
2019-01-05T00:28:26.7689506Z [command]/bin/bash /home/vsts/work/1/s/git_merge.sh
2019-01-05T00:28:26.7756106Z SOURCE BRANCH IS %BUILD_SOURCEBRANCH%
2019-01-05T00:28:26.7756449Z merge
2019-01-05T00:28:26.7756637Z GIT CHECKOUT MASTER
2019-01-05T00:28:26.7761605Z /home/vsts/work/1/s/git_merge.sh: line 4: [%BUILD_SOURCEBRANCH%: command not found
2019-01-05T00:28:26.7761996Z Previous HEAD position was 6105b3a Merge 55ac9c46fb679fef7f3f320765ded4d28e0b04c4 into feb1bcd92a0559c5242b4717b9c11a9a25246217
2019-01-05T00:28:26.7772812Z Switched to a new branch 'master'
2019-01-05T00:28:26.7773575Z Branch 'master' set up to track remote branch 'master' from 'origin'.
2019-01-05T00:28:26.7777038Z GIT STATUS
2019-01-05T00:28:26.7797446Z On branch master
2019-01-05T00:28:26.7798997Z Your branch is up to date with 'origin/master'.
2019-01-05T00:28:26.7799536Z 
2019-01-05T00:28:26.7799811Z nothing to commit, working tree clean
2019-01-05T00:28:26.7801985Z GIT MERGE
2019-01-05T00:28:26.8195510Z merge: %sourceBranch% - not something we can merge
2019-01-05T00:28:26.8198696Z GIT STATUS
2019-01-05T00:28:26.8218335Z On branch master
2019-01-05T00:28:26.8219258Z Your branch is up to date with 'origin/master'.
2019-01-05T00:28:26.8219608Z 
2019-01-05T00:28:26.8219959Z nothing to commit, working tree clean
2019-01-05T00:28:26.8221844Z GIT PUSH
2019-01-05T00:28:28.0116218Z fatal: could not read Username for 'https://github.com': No such device or address
2019-01-05T00:28:28.0137902Z GIT STATUS
2019-01-05T00:28:28.0165334Z On branch master
2019-01-05T00:28:28.0165914Z Your branch is up to date with 'origin/master'.
2019-01-05T00:28:28.0166050Z 
2019-01-05T00:28:28.0166150Z nothing to commit, working tree clean
2019-01-05T00:28:28.0312914Z ##[section]Finishing: git merge

やっぱりまるっとコピーするだけでは無理か。。。

すみませんでした、ということで、VMImage を vs2017-win2016 に変更して、 bat ファイルを実行してみることにしました。

azure-pipeline.yml

~省略~
pool:
  vmImage: 'vs2017-win2016'

steps:
  - script: dotnet build --configuration $(buildConfiguration)
    displayName: 'dotnet build $(buildConfiguration)'
  - script: dotnet test
    displayName: 'dotnet test $(buildConfiguration)'
  - task: ShellScript@2
    inputs:
      scriptPath: git_merge.sh
    displayName: 'git merge'

~省略~

が、今度はバージョンの違いからか dotnet build で失敗する。。。(´・ω・`)

ということで、これはいったん保留とします。

Shell script の方で、要はアカウントの指定ができていないということだと思うので、ここをもう少し調べてみることにします。

まぁそもそも、自動でマージする機能がどこまで必要か?というところは考える必要があるとは思いますが。。。

参照

2018 年の振り返りとか 2019 年とか

はじめに

流行ってる?っぽいので私も何となく。

【2018】家族について

今年のニュースといえば何といっても娘が生まれたことですね。

ただ、10カ月後の今となってみると、あれ?まだ一年たってないんだっけ?という気持ちになっていますw
すくすく健康に育ってくれている、ということでありがたい限りですね。

子供が二人になる、というのは思っていた以上に勝手が違ったりして、まだまだ慣れないところが多く、特に妻への負担が大きくなってしまっているのが気になっています。

来年は娘が大きくなって手がかからないようになるのをちょっと期待しつつ、やり方は考えていかないとな~、と思っています。

【2018】プログラミングについて

今年は特に C# .NET に寄った内容になっています。

mslgt.hatenablog.com

2017年末 - 2018年頭くらいまで Xamarin 、それから C# 、夏ごろから ASP.NET Core 、という感じでやっていたみたいです。

他の言語も当然興味はあるところなのですが、まずは一つの言語をしっかり学びたいな~、と思った結果なのですが、まったく理解できた気がしてないのはなぜでしょうか(白目)。

来年も C# .NET 中心に、もう少し中の処理などに踏み込んで学んでみたいと思っています。

あと、勉強も兼ねて英語版のブログも書いてみたいですね。 ASP.NET Core とか使って自分で作って~とかやってるといつになるかわからないので、まずはどこかのサービスを使おうかな、とは思っています。

【2018】お買い物について

Programming ASP.NET Core

まだ半分くらいしか読めていないのですが。。。

https://www.amazon.co.jp/Programming-ASP-NET-Developer-Reference-English-ebook/dp/B07CVM27L9/ref=sr_1_1_twi_kin_2?ie=UTF8&qid=1546217881&sr=8-1&keywords=Programming+ASP.NET+Core

ASP.NET Core の解説本で、特にプロジェクトを Empty で始めて、 Startup.cs や Program.cs の基本のところから説明してくれているのが非常に助かっています。

なお、今年買った本はまだまだたくさんあるのですが、読み終わったら別途書くことにします(白目)。

Surface Pro 6

色んなところへ連れまわすせいで、ラップトップ氏がお亡くなりに。。。

ということで Surface Pro 6 をお迎えしました。

www.instagram.com

良かったところ

何といってもバッテリーですね。

今までだと勉強会半日とかあると、予備のバッテリーを持っていっても最後まで持つか怪しい、といった状況があり、紙のノートに書き留める、といったことをしていました。

が、Surface だと長時間( 10 時間くらい?)使えるので、今のところバッテリーで困ったことはないですね。

あとタッチパネルが使えるので、タブレットとしても活躍してほしい、という目論見がありました。

こちら、性能自体は文句なし(タブレットモードも初めて使いましたが結構良いと思いました)なのですが、画面をバンバン叩く怪獣と爪でガリガリやってくる怪獣が家にいるため、あまり活躍できていません/(^o^)\

あと Windows Hallo による顔認識も非常に便利ですね。

まれに認識されない、されづらい場合もありますが、調子が良いと一瞬でログインできてしまいます。

悪かったところ

設定の問題だと思うのですが、スリープした瞬間に再度起動してしまい、かつ顔認証されてログインされてしまう、ということがあります。

マウスだったか電源だったかの設定で変更ができるようなのですが、該当する項目が見つからなかったりしてうまく設定できていません。

あと少し前に話題になっていましたが、 OneDrive とローカルのフォルダが混ざって管理しづらい。。。

共有するかを選択すること自体はできるのですが、スクリーンショットを自動で OneDrive に上げられなかったり、前までできていたことができなくなったりしています。。。

Microsoft 的には全部クラウドで共有しちまおうぜ、ということなのかもしれませんが、ローカルでのみ保管しておきたい(一時的な検証用プロジェクトとか)ものもありますし、そもそもクラウドのデータを全部持ってこられるとローカルのストレージが死んでしまうという。。。

この辺りもぼちぼち対策していきたいところです。

反省

ストレージがどれ位必要かをあまり考えずに買ってしまったため、 128GB でやりくりしているのですが、ツラい。。。
(Visual Studio 関連だけで 30GB 使ったり、 Core CLR ビルドしたら 10GB 持っていかれたり)

素直に黒モデル 256GB にしておくべきでした。。。(色に不満はないのですが、今回からの新カラーということで)

Alright, こぼれる

来年出る GRAPEVINE の新アルバム「ALL THE LIGHT」から先行配信された 2 曲。

感想は 2 曲とも一言で、「こうきたか!」でした。

ソウルやファンクといった影響が色濃く、 Alright は同じホーンを使っている Arma ともかなり違った雰囲気。

これじゃアルバムはどんななっちゃうの?と今からワクワクしています。

2019年について

2019 年は転職!!!。。。というのは目標にするものでもない気がするので、これはまぁぼちぼち考えていきます。

家族周りでは息子の入園とかまぁ色々あったり、1 、2 月に登壇予定があったりと年始早々バタバタする気がしてならないのですが、自分なりに頑張っていきます。

ということで、 2019 年もよろしくお願いいたします(..)_

【ASP.NET Core】Azure Pipelines でコンティニュアスにインテグレーションしてみる その1

はじめに

※ 2019/01/12 更新
Azure Pipeline → Azure Pipelines に修正しました。

以前 Unity のビルドを Azure Pipelines でしてみようと試みたわけですが、今の PC 一台体制では難しそう、ということで、題材を変えて ASP.NET Core で試してみることにします。

というお話。

今回はとりあえずプロジェクト作ってみるのと、 Build pipeline( azure-pipeline.yml )を追っかけてみることにします。

準備

まずは空でプロジェクトを作成して、 GitHubリポジトリを作っちゃいますよ。

https://github.com/masanori840816/AzurePipelineSample

で、 Azure DevOps( https://dev.azure.com/ユーザー名 )を開いて、プロジェクトを作ります。

Pipelines > Builds > New Pipeline で新しく Pipeline を作ります。

テンプレートは ASP.NET Core のものがあるのでそれを選択し、あとはデフォルトで進めます。

で、出来上がった azure-pipeline.yml はこんな感じ。

azure-pipeline.yml

# ASP.NET Core
# Build and test ASP.NET Core projects targeting .NET Core.
# Add steps that run tests, create a NuGet package, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core

trigger:
- master

pool:
  vmImage: 'Ubuntu-16.04'

variables:
  buildConfiguration: 'Release'

steps:
- script: dotnet build --configuration $(buildConfiguration)
  displayName: 'dotnet build $(buildConfiguration)'

ほとんど空っぽなのでビルドも問題なく通りますね。

Azure Pipelines 完全に理解した

yaml について

では、生成された yaml を見てみることにします。

trigger

自動で後述の steps の処理(今回は dotnet build )を実行するブランチを指定します。
対象ブランチで変更が Push されると steps が実行されます。

デフォルトでは master のみで、ワイルドカードも使用できるため、下記のように書くと全ブランチが処理の対象となります。

trigger:
- *

また開発者が複数いて、同時にビルドが実行されうる場合などは batch を true にすると処理を一つずつ順番に実行してくれるようです。

trigger:
  batch: true
  branches:
    include:
    - master
  • batch を指定する場合、ブランチの指定は branches > include で行う必要があります(エラーになるため)。
  • 今回は使っていませんが、除外するブランチを明示する場合、 exclude を使用します。

pool

後述の steps を実行する(つまりビルドを実行する) Agent です。

ここでは Ubuntu16.04 の VM イメージが指定されています。

variables

文字通り「変数」で、 変数名:値 の組み合わせになっており、 $(変数名) で使うことができます。

steps

trigger が引かれた場合(今回は master ブランチに変更が Push された場合)に実行する処理を指定します。

ここでは dotnet build --configuration Release というコマンドが実行されることになります。

steps で実行できる処理はビルドだけではなく、また複数指定することもできます。

下記のようにするとビルド実行後に CmdLine で「hello world」と出力されます。

steps:
- script: dotnet build --configuration $(buildConfiguration)
  displayName: 'dotnet build $(buildConfiguration)'
- script: echo hello world

script > displayName

script で実行するコマンドを指定するわけですが、この時 displayName を使うことで独立したものとして結果を見ることができるようになります。

f:id:mslGt:20181230152238j:plain

その他

他にもいろいろ指定できるようなので、気になったものを挙げてみます。

pr

steps の処理は、 Pull request を受けた場合も実行されますが、これを特定のブランチに限定したい場合に指定します。

pr:
- master

上記のようにすると、 master ブランチに対してのみ処理が実行され、他のブランチが Pull Request を受けた場合は実行されない、という状態になります。

jobs

ビルドなど複数のジョブを登録するときに使います。

jobs:
- job: Build
  steps:
  - script: dotnet build --configuration $(buildConfiguration)
    displayName: 'dotnet build $(buildConfiguration)'
  pool:
    vmImage: 'Ubuntu-16.04'
- job: Message
  steps:
  - script: echo hello world

この内容だと前述した steps に script を複数書くのとあまり変わらないのですが、例えば steps を実行する Agent が異なるジョブを登録したい場合などはこちらを使う必要がありそうです。

なお、jobs を使って複数のジョブを登録すると、それぞれのジョブは独立したものとして実行されます。

f:id:mslGt:20181230152001j:plain

注意点として、実行するジョブの内 1 つが失敗していても、他 (最後?) のジョブが成功していると Succeeded とメールが来てしまうことです(´・ω・`)

template

jobs や steps などをテンプレートとして、別ファイルに分けることができます。

azure-pipelines.yml

jobs:
- template: build_jobs.yml
  parameters:
    buildConfiguration: $(buildConfiguration)

build_jobs.yml

parameters:
  buildConfiguration: ''

jobs:
- job: Build
  steps:
  - script: dotnet build --configuration ${{ parameters.buildConfiguration }}
    displayName: 'dotnet build ${{ parameters.buildConfiguration }}'
  - script: dotnet test
    displayName: 'dotnet test'
  - script: echo hello world after build

  pool:
    vmImage: 'Ubuntu-16.04'
  • parameters として呼び出し元から値を渡すことができます。
  • jobs なら 呼び出し元、テンプレートともに jobs と内容をそろえる必要があり、 jobs - steps などになっているとエラーが発生します。

特にジョブが多い場合などは便利な気がしますが、 dev.azure.com 上で yaml として編集できるのは azure-pipelines.yml だけのようなので、複数プロジェクトで共有、とかでなければひとまとめにするのが良さそうです。

他パラレルに実行したり、というのも気になるところですが、今回はスキップします。

長くなってきたので一旦区切ります。
(タイミング的に別の記事を差しはさむかもですが)

参照

【ASP.NET Core】【Unity】JSONデータを受け取りたかった話

はじめに

早いものでクリスマスももう終わり、 Advent Calender も無事?完了です。

ということで今日からは通常通り私が気になったあれやこれやを書いていくことにします。

なお Advent Calender の記事との違いは、「これは( Advent Calender 名)の n 日目の記事です」から始まらないことです。

それはそれとして。

今回は ASP.NET Core で生成した JSON データを Unity で受け取ろうとしたらうっかりハマったので、悔し紛れに書き残しておくことにします。

環境

  • .NET Core ver.2.2.101
  • Unity ver.2018.2.19f1
  • Utf8Json ver.1.3.7.1

Utf8Json について

これまで Unity で JSON データを扱うときは JsonUtility を使っていたのですが、今回は Utf8Json を使うことにしました。

github.com

理由としては、ウリとしている処理速度、というところもありますが、以下のようなところが ASP.NET Core と共通して書ける、ということが大きかったです。

  • 変換対象をプロパティで指定
  • 変換対象に List が使える
  • JSON が配列( List )になっていても変換できる
  • JSON <--> クラス の変換対象を DataMember で指定

Unity Package でインストールできるところも便利ですね。

Unity 2018.2 以上?の場合は unsafe コードがデフォルトで禁止されているため、 Build Settings > Other Settings > Configuration > Allow 'unsafe' Code にチェックを入れる必要があります。

ASP.NET Core のクラス

JSON に変換する ASP.NET Core のクラスはこのような感じにしました。

User.cs

using System;
using System.Runtime.Serialization;

namespace AspNetCoreTestSample.Models
{
    [DataContract]
    public class User
    {
        [DataMember]public int Id { get; set; }
        [DataMember]public string Name { get; set; }
        [DataMember]public DateTime LastUpdateDate { get; set; }
    }
}

Item.cs

using System.Collections.Generic;
using System.Runtime.Serialization;

namespace AspNetCoreTestSample.Models
{
    [DataContract]
    public class Item
    {
        [DataMember]public int Id { get; set; }
        [DataMember]public List< User> Users { get; set; }
    }
}

HomeController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using AspNetCoreTestSample.Models;
using Microsoft.AspNetCore.Mvc;

namespace AspNetCoreTestSample.Controllers
{
    public class HomeController: Controller
    {
        [Route("/items/all")]
        [Produces("application/json")]
        public List< Item> GetAllItems()
        {
            return new List< Item>
            {
                new Item
                {
                    Id = 0,
                    Users = new List< User>
                    {
                        new User
                        {
                            Id = 0,
                            Name = "User1",
                            LastUpdateDate = DateTime.Now,
                        },
                        new User
                        {
                            Id = 1,
                            Name = "User2",
                            LastUpdateDate = DateTime.Now,
                        },
                    }
                },
                new Item
                {
                    Id = 1,
                    Users = new List< User>
                    {
                        new User
                        {
                            Id = 2,
                            Name = "User3",
                            LastUpdateDate = DateTime.Now,
                        },
                        new User
                        {
                            Id = 3,
                            Name = "User4",
                            LastUpdateDate = DateTime.Now,
                        },
                    }
                }
            };
        }
    }
}

クラスが入れ子になっていたのもややこしさの原因だったのですよね……。

Firefox で生成したデータを見るとこのようになっています。 f:id:mslGt:20181226004359j:plain

この時点で察した方は鋭いです。

Unity のクラス

User.cs 、 Item.cs は ASP.NET Core と同じ内容です。

User.cs

using System;
using System.Runtime.Serialization;

namespace Models
{
    [DataContract]
    public class User
    {
        [DataMember]public int Id { get; set; }
        [DataMember]public string Name { get; set; }
        [DataMember]public DateTime LastUpdateDate { get; set; }
    }
}

Item.cs

using System.Collections.Generic;
using System.Runtime.Serialization;

namespace Models
{
    [DataContract]
    public class Item
    {
        [DataMember]public int Id { get; set; }
        [DataMember]public List< User> Users { get; set; }
    }
}

以前試した UnityWebRequest を使ってアクセスしてみます。

JsonLoader.cs

using System.Collections;
using System.Collections.Generic;
using Models;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using Utf8Json;

public class JsonLoader : MonoBehaviour {

    public Button SendButton;

    public void Start() {
        SendButton.onClick.AddListener(() => {
            StartCoroutine(Send());
        });
    }
    private IEnumerator Send() {
        UnityWebRequest request = UnityWebRequest.Get(@"http://localhost:5000/items/all");
        yield return request.SendWebRequest();

        if(request.isNetworkError ||request.isHttpError) {
            Debug.Log(request.error);
        }
        else {
            List< Item> results = JsonSerializer.Deserialize< List< Item>>(request.downloadHandler.text);

            if (results == null)
            {
                Debug.Log("Results are null");
            }
            else if (results.Count <= 0)
            {
                Debug.Log("No items");
            }
            else
            {
                Debug.Log("There are some items");
            }
        }
    }
}
  • https の URL にアクセスしようとすると証明書の問題でエラーになるため http にアクセスしています。

これを実行すると、「There are some items」と出力されます。

なんだ簡単じゃないか。。。

問題

実はこの「results」、中身は全部デフォルト値が入っています。

そのため、「Debug.Log("There are some items");」の後下記のように書いてみると、 List< User> は空であることがわかります。

JsonLoader.cs

~省略~
else
{
    Debug.Log("There are some items");

    // 「Users are null」と出力される.
    if (results[0].Users == null)
    {
        Debug.Log("Users are null");
    }
    else if (results[0].Users.Count <= 0)
    {
        Debug.Log("No users");
    }
    else
    {
        Debug.Log("Success!! " + results[0].Users[0].Name);
    }
}
~省略~

「results」の数が合っていたりするため、なぜこのような結果になるのかがなかなかわかりませんでした。

解決

この原因は、最初に ASP.NET Core で生成された JSON の名前が、 LowerCamelCase になっていたことでした。

[{"id":0,"users":[{"id":0,"name":"User1","lastUpdateDate":"2018-12-25T23:51:47.6470112+09:00"}~省略~

これに対してクラスでの名前は UpperCamelCase であったため、この違いからうまく変換できていなかったようです。

ということで考えられる解決策をいくつか挙げてみます。

1. Unity 側のクラスの名前を LowerCamelCase にする

User.cs

using System;
using System.Runtime.Serialization;

namespace Models
{
    [DataContract]
    public class User
    {
        [DataMember]public int id { get; set; }
        [DataMember]public string name { get; set; }
        [DataMember]public DateTime lastUpdateDate { get; set; }
    }
}

確かに変換できるようにはなるのですが、命名規則に反することになるのであまりやりたくないですね。

2. Unity 側のクラスで DataMember の名前を指定する

User.cs

using System;
using System.Runtime.Serialization;

namespace Models
{
    [DataContract]
    public class User
    {
        [DataMember(Name = "id")]public int Id { get; set; }
        [DataMember(Name = "name")]public string Name { get; set; }
        [DataMember(Name = "lastUpdateDate")]public DateTime LastUpdateDate { get; set; }
    }
}

数が増えてくると大変ですが、何らかの理由で下記の 3. の方法が採れない場合に重宝しそうです。

3. ASP.NET Core 側で JSON の名前が UpperCamelCase で生成されるようにする

Startup.cs の ConfigureServices() で、 AddMvc() の戻り値である IMvcBuilder を使って JSON のオプションを指定できます。

Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Serialization;

namespace AspNetCoreTestSample
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc()
                .AddJsonOptions(option =>
                {
                    option.SerializerSettings.ContractResolver = new DefaultContractResolver();
                });
        }
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseMvc();
        }
    }
}

変更範囲が狭いため、可能であればこの方法が一番良さそうです。

おわりに

結果を見れば、大文字小文字で違ってるんだから問題が出て当然、という気はするのですが、それっぽいデータに騙されない、というのはなかなか難しいですね。

参照

【ASP.NET Core】単体テストってみる -導入と Controller のテスト

はじめに

これは C# Advent Calendar 2018 十七日目の穴埋め記事です。

前回 Azure Pipeline で Unity プロジェクトのビルドをしようとしたらちょっと厳しそう (少なくとも現状では)、という結論になったわけですが、まずは Azure Pipeline 力をゲットするやで!ということで、 ASP.NET Core のビルドに挑戦してみることにしました。

で、CI でビルド + マージしていくとなれば、テストが欲しいところ。

ということで、今回は xUnit と Moq を使ったテストに挑戦してみます。

どこからテストするか?というところで迷ったのですが、まずは Controller クラスから試してみることにします。

環境

  • .NET Core ver.2.2.101
  • xUnit ver.2.4.1
  • Moq ver.4.10.1
  • Rider ver.2018.2.3

テスト対象のクラス

過去の記事で書いたコードをもとにこんなクラスにしてみました。

HomeController.cs

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AspNetCoreTestSample.FileLoaders;
using AspNetCoreTestSample.Models;
using Microsoft.AspNetCore.Mvc;

namespace AspNetCoreTestSample.Controllers
{
    public class HomeController: Controller
    {
        private readonly ILocalFileLoader _fileLoader;

        public HomeController(ILocalFileLoader fileLoader)
        {
            _fileLoader = fileLoader;
        }
        [Route("")]
        public IActionResult Index()
        {
            ViewData["SampleUsers"] = Enumerable.Range(0, 5)
                .Select(n => "User " + n)
                .ToList();
            return View("/Views/Index.cshtml");
        }

        [Route("/items/update")]
        [HttpPost]
        [Produces("application/json")]
        public async Task< List< string>> ReloadAsync()
        {
            await _fileLoader.ReloadFilePathsAsync();
            return _fileLoader.FilePaths;
        }
    }
}

ILocalFileLoader.cs

using System.Collections.Generic;
using System.Threading.Tasks;

namespace AspNetCoreTestSample.FileLoaders
{
    public interface ILocalFileLoader
    {
        List< string> FilePaths { get; }
        Task ReloadFilePathsAsync();
    }
}

準備

テスト用のプロジェクトを追加する

テスト用にプロジェクト( Unit Test Project )を Type を xUnit にして追加します。

名前は テスト対象のプロジェクト名.Tests が良いらしいです。

プロジェクトが出来上がったら、 NuGet で Moq をテストプロジェクトにインストールします。

テストプロジェクト上で右クリック > Add > Add Reference... で、テスト対象のプロジェクトへの参照を追加します。

なお、この参照設定を行うため、 NuGet でパッケージをインストールする時はどちらか一方にのみインストールする必要があります(重複によるエラーを避けるため)。

NuGet でさらにパッケージをインストール

プロジェクトの作成自体は完了なのですが、実際に下記を書いてみるとエラーになります。

HomeControllerTest.cs

using System;
using AspNetCoreTestSample.Controllers;
using AspNetCoreTestSample.FileLoaders;
using Moq;
using Xunit;

namespace AspNetCoreTestSample.Tests.Controllers
{
    public class HomeControllerTest
    {
        private HomeController _controller;
        
        public HomeControllerTest()
        {
            Mock< ILocalFileLoader> mock = new Mock< ILocalFileLoader>();
            _controller = new HomeController(mock.Object);    
        }
        [Fact]
        public void Sample()
        {
            Assert.Empty("");
        }
    }
}

エラーの内容はこちら。

System.IO.FileNotFoundException : Could not load file or assembly 'Microsoft.AspNetCore.Mvc.ViewFeatures, Version=2.2.0.0, Culture=neutral, PublicKeyToken=example'. 指定されたファイルが見つかりません。

で、これを解決するには、テストプロジェクト側に NuGet で下記を追加インストールする必要があるようです。

テストを書く

では準備も揃ったところで、いくつかテストを書いてみることにします。

HomeControllerTest.cs

using System;
using System.Collections.Generic;
using AspNetCoreTestSample.Controllers;
using AspNetCoreTestSample.FileLoaders;
using Microsoft.AspNetCore.Mvc;
using Moq;
using Xunit;

namespace AspNetCoreTestSample.Tests.Controllers
{
    public class HomeControllerTest: IDisposable
    {
        private readonly HomeController _controller;
        
        public HomeControllerTest()
        {
            // 初期化処理.
            Mock< ILocalFileLoader> mock = new Mock< ILocalFileLoader>();

            // ILocalFileLoader.FilePaths が呼ばれたときに Returns の中身を返す.
            mock.Setup(m => m.FilePaths).Returns(new List< string> {"hello", "world"});

            _controller = new HomeController(mock.Object);    
        }
        public void Dispose()
        {
            // 完了後にアンマネージドリソースの処理したり.
            Console.WriteLine("disposed");
        }
        [Fact]
        public void Index_OpenAndGetType()
        {
            Assert.IsType< ViewResult>(_controller.Index());
        }
        [Fact]
        public async void Reload_GetTwoItems()
        {
            List< string> result = await _controller.ReloadAsync();
            
            Assert.NotNull(result);
            Assert.True(result.Count == 2);
        }
        
    }
}
  • コメントにも書きましたが、アンマネージドリソースの後処理が必要な場合など、 IDispose を継承すると Dispose が呼ばれるようになります。

結果はこちら。

f:id:mslGt:20181220224504j:plain

まだ数が少ないとはいえ、全部成功するのは気持ち良いものです。

Moq について

DI で挿入している依存クラスは、 Moq を使って自動でダミーデータを生成してもらうことができます。

ただ当然ながら Moq で生成したダミーデータのメソッドを呼んでも動作はしないため、その戻り値がテストに必要な場合、 Setup 、 Returns などを利用してダミーのデータを設定することができます。

制限事項として、 Setup で扱うには、そのメソッドが virtual である( override 可能)か、 class ではなく interface のモックを作る必要があります。

おわりに

俺たちの自動テストは始まったばかりだ。。。!

。。。というのは置いといて、 Moq を使って依存する class を自動生成できるのは結構便利ですね :)

まだどこをテストすべきか、といった部分が理解できていなかったり(なので作りながらテストを書いたり消したりしている)、 View や Model など他の機能のテストの書き方も分かっていなかったりするので、まぁ、まだこれからですがぼちぼち進めていきたいと思います。

参照

xUnit

Moq

Unity のプロジェクトを Azure Pipelines でビルドしたくて七転八倒した話

はじめに

※ 2019/01/12 更新
 Azure Pipeline → Azure Pipelines に修正しました。

これは Azure DevOps Advent Calendar 2018 の十八日目の記事です。

とあるきっかけで Azure DevOps に挑戦することになったのですが(今回は仕事じゃないです)、まずは CI/CD ツールである Azure Pipelines から試してみよう! となりました。

せっかくなので、仕事でよく触る Unity を題材にしてみよう、と思ったは良いのですが、初めてづくしということで大変でした、という話です。

準備

まずは準備から。

https://azure.microsoft.com/ja-jp/services/devops/pipelines

辺りからユーザー登録します。

初めての場合はアカウント名とリージョンの設定が求められます。

デフォルトでメールアドレス、アクセスしている場所から値が入っているので、特に変更なければそのままで。

ほいほいとプロジェクトの作成まで済ましてしまいます。

その他環境です。

  • Windows 10 ver.1803 build ver.17134.471
  • Unity Hub 1.3.2
  • Unity 2018.2.19f1
  • GitHub Desktop ver.1.5.0

Unity プロジェクトを作る

とりあえずビルドができるかどうかが確かめられれば、ということで、プロジェクトだけ作成してそのまま閉じてしまいます。

.gitignore は GitHub にテンプレートがあるのでそれを使用するか、 GitHub Desktop を使う場合はリポジトリ作成時にプルダウンから選択すると便利です。

https://github.com/github/gitignore/blob/master/Unity.gitignore

で、ちゃちゃっと GitHubリポジトリ作っておきます。

https://github.com/masanori840816/AzurePipelineSampleUnity/

Unity Build について

Azure Pipelines でのビルドですが、 Visual Studio Market に Unity ビルド用のプラグイン?があります。

また、これを使ってビルドする方法を紹介してくれているブログもありました。

基本的にはこの通り従っていけばビルドができます。

後でも触れますが、 Unity のビルドは Unity Editor がインストールされていないとできません。

そのため、クラウド環境上でビルドするのではなく、自分の(またはビルド用に準備した)マシンを Agent として登録し、 Azure Pipelines からマシンに登録した Unity Editor をコマンドラインから実行してビルドする、という流れとなります。

ただ、いくつか引っかかったことがあったので補足(蛇足?)的に書き残しておきます。

Agent のキーがコピーできない

いきなり Unity や Unity Build とは関係ない話ですがw

Edge で Project Settings > Agent pools を開き、 Agent を作成し、 Download Agent でダウンロード。

。。。の辺りまでは良かったのですが、キーのコピーボタンを押してもコピーができずorz (Copyボタンを押すと Copiedとなるもののコピーできてない)

またキーが一部省略されているので直接入力も無理/(^o^)\

どうしよう。。。と思っていましたが、 Firefox で上記ページを開いたら問題なくコピーできたため解決しました。

納得がいかないものを感じなくもないですが、まぁ良かったです。

Empty job が見つからない

これが一番ハマりました。

Agent をインストールして、 Pipelines > Builds > New pipeline から上記ブログの手順に従って登録を進めたところ、 Empty job が見つからないorz

これは、最初にリポジトリ参照元を選択する画面で、「Use the visual designer」をクリックすることで解決しました。

f:id:mslGt:20181218223430j:plain

ここで Azure Repos や GitHub を選んでしまうと Empty Job が表示されずつんでしまうという/(^o^)\ (記事では書かれていたのを見落としたかもしれません)

なおビルドの結果生成された実行ファイルは、下記のような場所に出力されます。

[Agent を置いた場所]_work\1\s\Build\standalone

課題

曲がりなりにもビルドはできるようになったので、このまま開発を進めていきたいところですが、課題が一つ。

先に触れた、 Azure Pipelines のビルドでマシンにインストールされている Unity Editor を使っている、という点です。

開発用のマシンとビルド用のマシンが同じである場合、 Azure Pipelines のビルドを走らせるためにはいったん Unity Editor を閉じないとエラーになります。

Unity Editor の実行ファイルを同時に2つ以上開けないためなのですが、これはちょっとキビしい。。。

また、 Unity Editor をインストール・実行する必要があるため、サーバーマシンでは難しそうな気がします(ここは調べていないだけで対応されているかもしれませんが)。

ということで、開発用マシン一台しかない私の環境では、現状素直に手動でビルドするか、 Unity Cloud Build 辺りを検討するのが良さそう、という結論になりました。

Azure Pipelines に限らず CI/CD ツールで Unity アプリをビルドすると類似の問題が起きそうですが、皆さんどのように解決しているのでしょうか。。。