はじめてのconsole.log
先日Meguro.es #8で登壇させてもらったときのLT資料です

if i look back, i am lost
Claire Keane
Show & Tell

JVL

⁂
trying on a metaphor
noise dept.
PUT YOUR BEARD IN MY MOUTH
h
Monterey Bay Aquarium
AnasAbdin

JBB: An Artblog!

#extradirty
Game of Thrones Daily

No title available
No title available
sheepfilms
ojovivo
Sade Olutola
One Nice Bug Per Day
seen from United States

seen from Germany
seen from Malaysia
seen from United States

seen from United States
seen from United States
seen from United States

seen from United States

seen from United States
seen from United States

seen from United States
seen from United States

seen from Malaysia
seen from Germany

seen from Germany
seen from United States

seen from Germany

seen from Singapore
seen from Switzerland
seen from United States
@edwardkenfox
はじめてのconsole.log
先日Meguro.es #8で登壇させてもらったときのLT資料です
JSと型みたいな話を最近よく聞く。実際にJSに型システムが導入されるとしたら、もたらされる一番のメリットはなんなのだろうか。複雑化するフロントエンド開発でも型安全を手に入れる、みたいな話はよく聞く。とはいえ、JSに型を導入する一番のメリットは、おそらJSを書く側にとってのものよりも、結局はパフォーマンスなんだろうなあ、と思う。
通信はどんどん速くなるし、クライアントのマシンスペックも(ムーアの法則が終焉しつつある、という話は置いておいて)良くなっている。HTTP/2やQUICが主導する形で、通信プロトコルも日に日に進化を遂げている。そういう状況下では、クライアントでのスクリプトの実行速度が、ブラウザのパフォーマンスのボトルネックとなりうる。 どのブラウザのJSエンジンもスクリプトの実行状況に応じて適用する最適化戦略を変えるわけで、それならいっそ初期段階から型付きのプログラムを実行できれば、パフォーマンス良くなるはずである。asm.jsなんかはまさにその発想から来ているだろう。WebAssemblyの開発も進んでいるが、ゲームとか3Dのレンダリングが主な用途だろうと言われていてる。この流れを考えても、JSに型が導入されるとしたら、ブラウザベンダがパフォーマンス改善を目的として進めていくんだろうな、と思ったりした。
ハイパフォーマンス ブラウザネットワーキング 読み終わった
C++で書いたクラスをemscriptenを経由してVue.jsで利用する
この記事は @janus_wel さんの emscripten でドメイン層に型と速度を持ち込む に~~触発された~~ロマンを感じた筆者が、C++で書かれたクラスをemscriptenを通してVue.jsにバインドしてみた記事です。「なぜそもそもそんなこと面倒なことを?」と思われた方がいたら、元記事の「コンセプト」や「経緯」の部分を読んでもらえればと思います。「要するにロマンです」です。
概要
リポジトリ: edwardkenfox/vue-emscripten-todo デモ: vue-emscripten-todo
TodoクラスはC++で実装
最近趣味でC++触り始めていたので自分で書いても良かったんですが、Todo.cppやビルド用のスクリプトは元の実装を拝借しました
AppコンポーネントおよびTodoコンポーネントをVueで実装
Appコンポーネント(コンテナ)は複数のTodoコンポーネントを持つ
Todoコンポーネント(Vue)はTodoクラス(C++だったやつ)のインスタンスを持ち、Todoのドメイン知識はTodoクラスのインスタンスが管理する
ややこしいですね。でもロマンがあるからいいんです。
課題
TodoオブジェクトをVue.jsから利用する際に、Todoクラスが持ってるプロパティがリアクティブにならない(!)
emscriptenを通して出力されたTodo.jsファイルの中身を見ていないのでなんとも言えないですが、Change Detection Caveats に書かれている内容に該当するんじゃないかと推測
this.$set(todo, 'isDone', Boolean)もうまくいかず
まとめ
C++で書いたクラスのプロパティがリアクティブにならない点は要調査
これが出来ないとそもとも全く使いものにならないw
emscriptenやWebAssemblyなどの技術は、ブラウザゲームや3Dレンダリングが主な目的として利用されるだろうと想定していましたが、こういう感じでカジュアルに取り込むことができるなら、Webアプリケーションが利用することも出来なくはない、という印象でした
とはいえ、1KBのcppファイルが500KBのjsファイルになる時点で全然カジュアルではない
Vue.jsを使う際のベストプラクティスについて考える
Vue.jsは公式のドキュメントが非常に充実しており、またフォーラムでの議論やコミュニケーションもとても活発です。開発中に何か問題に遭遇した際には、ドキュメントやフォーラムに載っている情報を参照することで、多くの問題は解決できるといって差し支えないでしょう。しかしながら、現実世界のアプリケーションを開発していると、そういった情報だけでは解決が難しい個別具体的な問題や、そもそもどう実装すれば良いのかわからない、といった場面に遭遇することも多々あります。
筆者自身がVue.jsを利用してフロントエンド開発をしてきた経験に加え、Vue.jsの公式のドキュメントやサンプルプロジェクト、そしてVue.jsを利用しているOSSのプロジェクトやVue.jsのプラグインなどのソースを読んで蓄えてきたノウハウを本資料にまとめました。
「ベストプラクティス」と銘打ってはいるものの、筆者の好みや開発経験に依存する部分は大きいでしょう。本資料をより意義のあるものにするために、Vue.jsを利用して開発をする際のノウハウやプラクティスをお持ちの方は、ぜひコメントや編集リクエストを通して教えてください!
バージョン
当たり前の話のようですが、新規で開発をはじめるプロジェクトであれば、Vue.jsの最新のバージョンを使うに越したことはないでしょう。2016/12/18の時点ではv2.1.6が最新となっています。
また1系のバージョンを利用しているプロジェクトは、2系にアップグレードすることをオススメします。破壊的な変更も含まれているので、1系から2系へのアップグレードは容易ではないかもしれませんが、それでも多くのメリットを享受できるでしょう。1系から2系にアップグレードする際に利用できるマイグレーションツールがあるほか、deprecateされるAPIについての案内やアップグレードガイドも丁寧に書かれているので、それらを参照してください。
ref:
vue-migration-helper
Migration from Vue 1.x
パッケージ管理ツール
Vueプラグインのリポジトリを色々と見てみると、従来通りnpmパッケージの管理にはnpmを利用しているところがほとんどのようでした。開発が盛んな一部のリポジトリでは、yarnを利用しているところも見かけましたが、数としては多くはないようです。
しかしながら、npmと比較してyarnは依存するライブラリが多いときほどインストールが速いというベンチマーク結果も出ているので、新規のプロジェクトではyarnを使わない理由はないかもしれません。CIなどでyarnを使う場合は、キャッシュ周りに気をつけて使う必要がありそうです。
ref:
yarn vs npm@2 vs npm@3
ビルドツール
Vue.js公式によって用意されているvuejs-templatesには、webpackとbrowserify用のサンプルの設定ファイルと実装があります。それぞれのリポジトリのスター数を見ると、webpackの方が圧倒的に人気のようです。 Vue.jsのライブラリなどを見ても、webpackを利用しているところがほとんどですし、アセット周りの強力な機能を備えたwebpackを使うのが良さそうです。
記法
v-onやv-bindを利用する際は、やはり簡潔な短縮記法を利用した書き方が好まれるようです。また複数人で開発を行うプロジェクトであれば、eslintや.editorconfigなどを利用して、記法や構文の統一化を図るのが一般的になっています。
propsの型チェック
propsを通して渡ってくる値の型チェックは、出来る限り使った方が良いでしょう。例えば正の整数を期待するpropsである場合は、Number型のチェックだけではなく、許容してはいけない値をvalidateすること(マイナスの値でないことをチェックする)なども、気づきにくいバグを減らす大きな助けになります。
// not so good Vue.component('child', { props: ['age'] }) // good Vue.component('child', { props: { age: { type: Number } } }) // better Vue.component('child', { props: { age: { type: Number, required: true, validator: function(value) { return value >= 0; } } } })
propsの型チェックには、複数の型を指定したり、あるいはnullと指定することですべての型を許容することも可能ですが、これらは出来る限り使わないことをオススメします。どうしてもnullを指定しなければならないような場合は、ただ1つの型の値を渡せるように、コンポーネントやpropsとして渡すべきデータの構造を見直してみると良いでしょう。
ref:
Prop Validation
ライフサイクルフックの活用
Vue.jsには便利なAPIがたくさん用意されていますが、イベント系のメソッドや細かいコンポーネントオプションを駆使する前に、実装しようとしている処理や挙動がライフサイクルフックをうまく活用することで実現できないか検討してみましょう。筆者の経験上、コンポーネント同士が思ったとおりに協調しないときは、ライフサイクルフックの使い方が悪かった、ということがよくあります。そういう場合は、得てしてwatchや$emitを必要以上に複雑な形で濫用してしまっていました。ライフサイクルフックをうまく組み合わせることで、実装は自然とシンプルな形に落ち着きます。
コンポーネントが単体で存在するようなミニマルな状況下では、createdとmountedのどちらのライフサイクルを利用してもあまり大きな差はないかもしれません。しかし、コンポーネント同士がpropsを通じて協調動作をするような場合では、親コンポーネントのcreatedと子コンポーネントのmountedなどのタイミングの差をよく理解してコンポーネントを組む必要があります。
new Vue({ mounted: function() { console.log("Hello from parent"); } }) // => "Hello from parent"
Vue.component('child', { mounted: function() { console.log("Hello from child"); } }) new Vue({ mounted: function() { console.log("Hello from parent"); } }) // => "Hello from child" // => "Hello from parent"
Vue.component('child', { mounted: function() { console.log("Hello from child"); } }) new Vue({ created: function() { console.log("Hello from parent"); } }) // => "Hello from parent" // => "Hello from child"
v-forで表示された要素の削除
v-forでレンダリングされたアイテムのリストのうち、ユーザーの操作によって特定の要素を削除する、というような場面はよくあると思います。これを実現する方法は色々と考えられますが、多いのは以下の2つのアプローチのようです。
削除対象の要素(子コンポーネント)が、自身の削除を親コンポーネントに移譲するパターン(削除をイベントとして$emitし、実際の削除処理は親が行う、もしくはstoreにコミットする)
親コンポーネントの関数として削除処理を実装し、削除関数を子コンポーネントにpropsとして渡す。実際に削除する際は子コンポーネントが受け取っている関数を実行し、自身を削除する
筆者個人的には、どちらの方法がもう一方に比べて特に優れているとは思いませんが、UIやコンポーネントの分割単位の観点から見て、処理の流れがより自然な方を選ぶのが良いと思います。なお、子要素の削除に加えて、追加の処理がいろいろ付随するような場合は、2つめの方法の方が扱いやすいかもしれません。
// パターン1 Vue.component('child', { methods: { removeItem: function() { this.$parent.$emit('removeItem', this.index); } } })
// パターン2 Vue.component('child', { props: { removeItem: { type: Function } } })
外部ライブラリのコンポーネント化
サードパーティのライブラリ、特にUI関連のライブラリの場合は、Vueインスタンスの中から直接ライブラリを利用するのではなく、Vueのコンポーネントとしてラップすることを検討してみてください。別のコンポーネントからであっても、VueのAPIを通じて協調や命令ができるようになり、サードパーティライブラリのAPIについて関心を寄せる必要がなくなります。
また、汎用的に使いたいUIの効果(アニメーション、トランジションなど)などは、コンポーネントとディレクティブの両方を用意しておく、というのも1つの手かもしれません。こうすることで、この効果を利用する側のコンポーネントの事情に合わせて、コンポーネントとして利用するか、あるいはディレクティブを通じて利用するかを選ぶことができます。
ref:
Wrapper Component
vue-touch-ripple
非同期通信ライブラリ
少し前まではvue-resourceがVue.js公式の非同期通信ライブラリでしたが、現在Vue.jsが公式として提供している非同期通信ライブラリはありません。そもそもがVue.jsは外部ライブラリを組み込みやすいようにできているので、普段使っていて慣れている非同期通信ライブラリを使うのが良いでしょう。
公式からは外れたものの、vue-resourceを利用しているプロジェクトも多いようですし、JavaScript界隈の状況を見るとaxiosやrequestなどが人気のようです。また、ブラウザのfetch APIを利用するのもアリでしょう(Safariではまだネイティブ実装されていないため、polyfillの利用が必要です)。
ref:
Retiring vue-resource
fluxアーキテクチャの導入
昨年ごろからfluxアーキテクチャがフロントエンドに新しい潮流をもたらし、Vue.jsの世界にもVuexというfluxライクなライブラリが公式から提供されています。Vuex自体は非常によく出来たライブラリではありますが、アプリケーションの規模や複雑度とよく照らし合わせて、利用するかどうかを検討するべきだと筆者は感じます。
筆者個人の感覚としては、小〜中規模のアプリケーションであれば、Vuexを導入するよりも前に、Vue.js公式ドキュメントに書かれているようなstoreパターンの導入による状態管理などを適用する方がベターだと考えています。Vuexは状態の管理や更新に秩序をもたらすものの、中途半端に導入してしまうとかえって整合性がとれなくなったり、また小さいアプリケーションの場合では、fluxアーキテクチャの導入により無用な複雑さを生んでしまう懸念もあります。
Vue.jsアドベントカレンダー2016にもある、@nekobatoさんによる構造の複雑さとVuex書き分け が非常に参考になるので、興味のある方はそちらを参照してみてください。
シングルファイルコンポーネント
ビルド環境やプロジェクトの開発環境に大きく依存しますが、シングルファイルコンポーネント(.vueファイル)を利用できる場合は、積極的に利用した方が良いでしょう。コンポーネントとは、ユーザーインターフェースの「見た目」と「振る舞い」をもとに分割した部品の単位であり、テンプレート(HTML)と装飾(CSS)、そして振る舞い(JavaScript)が1つのファイルで完結していることは理にかなっていると考えています。フロントエンド開発者とデザイナーが協働するような場合においても、シングルファイルコンポーネントは統一的な作業環境を用意してくれるので、効率良く協調作業ができると期待しています。
コンポーネントの再利用
「シングルファイルコンポーネント」の項でも書いたように、コンポーネントとは極端に言えばUIを分割した部品であり、またUIの部品はそれが所属するページの文脈化にあります。同じように見えるUIであっても、実は振る舞いが違っていたり、異なるエッジケースを持つ、といったことは少なくありません。
こういった「一見同じように見えて実は違う」部品を共通のコンポーネントとして実装してしまうと、引数やpropsによる制御に加え、コンポーネント内の条件分岐が増えることになりかねません。これは共通化されたコンポーネントを利用する側にも大きな負担を強いることになり、バグの温床にもなりうることは想像に難くありません。
こういった状況は、UIの設計を見直す良いタイミングと捉えることもできますが、いずれにしろ無理な共通化/汎用化は良い結果をもたらさないことが多いことは強調しておきます。異なるコンポーネントから共通部分を見出して共通化するよりも、一度共通化されたものをバラす方が、はるかに難しいです。ブラウザのパフォーマンスも日々向上し、ほとんどのクライアントは高速なネットワークからWebアプリケーションを利用します。多少ファイルサイズが大きくなってしまったとしても、無理な共通化は控え、コードベースを扱いやすい状態に保つことの方がメリットは大きいのではないでしょうか。コンポーネントを再利用/共通化する前には、一度共通化のpros/consや必要性をじっくりと検討してみることをオススメします。
ref:
Authoring Reusable Components
テスト
Vue.jsアドベントカレンダー 2016にもいくつかテスト関連の記事が上がっていますが、フロントエンド開発がどんどんと複雑化してきている昨今、Vue.jsを利用したコンポーネントのユニットテストを書く必要性は高まってきていると感じています。
そのコンポーネントの実装を見ただけで、コンポーネントの動きや処理がすべて理解できるような閉じたコンポーネントであれば、わざわざテストは書かなくても良いかもしれません。しかし、コンポーネント間でpropsが受け渡しされ、それをもとにコンポーネントの挙動が変わるようなケースでは、そのコンポーネントの振る舞いが宣言的に記述されたテストコードがあると安心です。また筆者自身は、コンポーネント内で非同期通信を発行し、レスポンスの内容にもとづいてVueインスタンスに値をセットするような振る舞いをコンポーネントが持つときも、ユニットテストを書くようにしています。このあたりのテストを書くコストとそれによって受ける恩恵のバランスは難しいですが、やはりテストがあるとリファクタリングをする際の安心感は絶大です。
ref:
Unit Testing
Vue.js Vueコンポーネントのユニットテストを書いてみよう
axiosを利用したVue componentのUnitTest
まとめ
Vue.jsに限らず、どんなフレームワークであっても、フレームワークの性質や癖を理解して利用することが重要であることは疑いようがありません。また、開発するアプリケーションもさまざまであり、開発者には必要とされている要件に合致した設計や実装、ライブラリの利用をすることが求められています。本資料がVue.jsを利用してフロントエンド開発をしている人、これからしようとしている人の一助になれば幸いです。
Swift愛好会合宿 – 参加録
諸事情あってfeature specはdockerコンテナ上で実行する必要があり、docker cpにいちいちコンテナIDを渡すのが面倒だったのでショートカットをつくた。テストプロセス内でファイル名取得しなきゃいけないのがまだ面倒だけど、pryからシェルを実行するときに引数を渡す方法がわからなかったのでとりあえずこれで凌ぐ。
#!/bin/bash function docker-cp() { usage_exit() { echo "Usage:\tdocker-cp /path/to/your/file" 1>&2 return 0 } if [ $# -eq 0 ] then usage_exit return 0 fi FILE_PATH=$1 CONTAINER_ID=`docker ps | grep your_app | head -1 | awk '{print $1;}'` docker cp $CONTAINER_ID:$FILE_PATH ~/Desktop/capybara/ }
Railsのjquery-ujsに絡んだ挙動をデバッグしていて、data-methodつきのaタグをクリックしたあとでステップ実行したいんだけど、どこに処理が行くのか追いづらかったとき用
window.startDebug = function() { debugger };
= link_to "Foo", "/foo", method: :post, "onclick": "window.startDebug()"
やさしいC++ 第4版 読み終わった
ブロックチェーン 仕組みと理論 サンプルで学ぶFinTechのコア技術 読み終わった
はじめに
Railsでアプリケーションを開発していると、E2Eの試験としてfeature specを書くことはよくあると思いますが、JavaScriptで実装したフロントエンドのモジュールやライブラリのユニットテストは「feature specがあるから大丈夫」とおざなりになっていませんか?E2Eテストですべて網羅的に検証できていれば良いという意見には一理あるのですが、UIやUXをはじめとして、フロントエンドの要件が大きく複雑になる傾向にある最近のWEBアプリケーションにおいては、フロントエンドのユニットテストがあることのメリットは大きいと考えています。またfeature specはえてして実行時間が長く、ユーザーの操作パターンをすべて網羅的に検証するのは現実的ではありません。
今回の記事では、バックエンドのフレームワークとしてRails、フロントエンドのフレームワークとしてVue.jsを利用して簡単なTodoアプリケーションを作成し、Vue.jsで書いたコンポーネントのユニットテストを書きます。本記事がフロントエンドのユニットテストを書くきっかけになり、堅牢なフロントエンド開発の一助となれば幸いです。
なお今回本記事で紹介するアプリケーションと同じものをGitHubに置きましたので、適宜サンプルコードを見てもらえればと思います。 edwardkenfox/rails-vue-unit-test-sample
今回利用したライブラリ
Rails: 5.0.0.1
Vue.js: 2.0.1
npm: 3.10.8
webpack: 1.13.2
注)RailsもVue.jsも現時点で最新のバージョンを利用していますが、ここで作成するアプリケーションおよびテストは非常にシンプルなものなので、これよりも古いバージョンでも問題なく動作するはずです(未検証)。
Railsでビューの作成
まずは新しいRailsアプリケーションを作成します。
$ rails new rails-vue-unit-test-sample $ cd rails-vue-unit-test-sample
簡単な画面を作っていきましょう。ここではまだVue.jsは利用せず、このあと実装するVueコンポーネントが乗っかるための土台となるビューをRailsで作成します。
次にルーティングを用意します。rootへのアクセス(localhost:3000/)を直接TodosController#indexへルーティングします。
root to: "todos#index"
つづけてリクエストを制御するコントローラを作成します。さきほど書いたルーティングに沿ったアクションを用意すれば十分です。
class TodosController < ApplicationController def index end end
これで画面を作成する準備が整いました。次はビューを書いていきましょう。
Railsの規約に沿うように、コントローラ名とアクション名に合致するテンプレートファイルを用意します。<todos></todos>というのが出てきていますが、これがこのあとVue.jsを使って実装するVueコンポーネントに該当します。
<article id="todos-sample"> <h1>Todos</h1> <todos></todos> </article>
ここまで書き終わったらlocalhost:3000/にアクセスしてみましょう。下図のように表示されたら成功です。<todos>の実装がまだないので、当然<h1>以外には何も表示されません。
Todosコンポーネントの実装
つづけてVueコンポーネントの実装に入ります。pure JavaScriptで書くことも可能ですが、せっかくなのでモダンな感じのセットアップで開発してみることにします。
まずはアプリケーションのルート直下に/frontendディレクトリを作ります。さらにその中に/config、/src、/testというディレクトリを作ります。それぞれのディレクトリに必要なファイルを順を追って作成していきます。
/frontend ├── /config -- webpackの設定ファイルなど ├── /src -- Vueコンポーネントなどの実装 └── /test -- テスト用の設定ファイルや実際にテストファイル
必要なnpmパッケージをインストール・管理するためにpackage.jsonを作り、Vue.jsをはじめ今後必要となるライブラリをすべてインストールします。
$ npm init # コマンドライン上の質問に対してはとりあえず全部EnterでOK $ npm install --save-dev babel-core babel-loader babel-plugin-transform-runtime babel-preset-es2015 babel-preset-stage-0 babel-runtime function-bind karma karma-mocha karma-phantomjs-launcher karma-spec-reporter karma-webpack mocha vue-html-loader vue-loader webpack webpack-dev-server
npm installが完了するとpackage.jsonはこんな感じにになっているはずです。
{ "name": "rails-vue-unit-test-sample", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "webpack --config ./config/webpack.config.js", "test": "karma start ./karma.conf.js" }, "author": "Edward Fox", "license": "ISC", "dependencies": { "vue": "^2.0.1" }, "devDependencies": { "babel-core": "^6.0.0", "babel-loader": "^6.0.0", "babel-plugin-transform-runtime": "^6.0.0", "babel-preset-es2015": "^6.0.0", "babel-preset-stage-0": "^6.16.0", "babel-runtime": "^6.0.0", "function-bind": "^1.1.0", "karma": "^1.3.0", "karma-mocha": "^1.2.0", "karma-phantomjs-launcher": "^1.0.2", "karma-spec-reporter": "0.0.26", "karma-webpack": "^1.8.0", "mocha": "^3.1.0", "vue-html-loader": "^1.2.3", "vue-loader": "^8.0.0", "webpack": "^1.12.2", "webpack-dev-server": "^1.12.0" } }
次にwebpackをセットアップします。以下のファイルを作成します。
var webpack = require('webpack') var path = require('path') module.exports = { entry: { application: './src/javascripts/main.js', }, output: { path: '../app/assets/javascripts', filename: '[name].js' }, module: { loaders: [ { test: /\.vue$/, loader: 'vue' }, { test: /\.js$/, loader: 'babel', exclude: /node_modules/ } ] }, babel: { presets: ['es2015'], plugins: ['transform-runtime'] }, resolve: { alias: { vue: 'vue/dist/vue.js' } } }
さらにES2015の記法で書かれたファイルをJavaScriptにトランスパイルするために必要なBabelの設定を追加します。
{ "presets": ["es2015", "stage-0"], "plugins": ["transform-runtime"] }
これでやっとVueでコンポーネント書く準備ができました!さっそくVueコンポーネントを書いていきましょう。
まずはTodosコンポーネントの元となるファイルを用意します。Vue.js公式のサンプルなどを見ると、App.vueというアプリケーション全体のコンテナ層を設ける方式が採られているようですが、ここではなるべく簡潔にするためmain.jsで直接必要なコンポーネントをimportします。
import Vue from 'vue' import Todos from './todos.vue' new Vue({ el: '#todos-sample', components: { Todos } })
Todosコンポーネントをインスタンス化する準備ができました。次に実際のTodosコンポーネントを作成しましょう。
<template> <div> <ul> <li v-for="(todo, index) in todos"> {{ todo.title }} </li> </ul> <input type="text" placeholder="Create new todo" v-if="showTodoInput" v-model="newTodoTitle"> <button @click="addTodo">+ Add Todo</button> </div> </template> <script> export default { data () { return { todos: [], showTodoInput: false, newTodoTitle: null } }, methods: { addTodo: function() { if (this.showTodoInput && this.newTodoTitle) { let newTodo = { title: this.newTodoTitle, completed: false } this.todos.push(newTodo) this.newTodoTitle = null } else { this.showTodoInput = true } } } } </script>
これでアプリケーションが完成しました!ここまでが完了した状態でfrontendディレクトリに移動し、$ webpack --config ./config/webpack.config.jsを実行してみましょう。frontend配下に書いたVue.jsの実装をwebpackがビルドし、app/assets/javascripts/application.jsとして出力します。ビルドが完了したらlocalhost:3000/をリロードしてみましょう。
さきほどはなかった「+ Add Todo」というボタンが追加されているのがわかります。実際にボタンをクリックして新しいTodoを作ってみましょう。
これでTodoアプリケーションが完成です!このままではTodoアプリケーションとして何の面白みもないですが、本記事の目的はVueコンポーネントのテストを書くことなので、次に進みたいと思います。
Vueコンポーネントのユニットテストを書く
フロントエンドのテストを実行するための環境を準備します。今回テストランナーにはkarma、テスティングフレームワークにはmochaを利用します。必要なライブラリはすでにインストール済みなので、karmaの設定ファイルを以下の要領で作成しましょう。
var webpackConf = require('./config/webpack.config.js') module.exports = function (config) { config.set({ browsers: ['PhantomJS'], frameworks: ['mocha'], reporters: ['spec'], files: ['./test/index.js'], preprocessors: { './test/index.js': ['webpack'] }, webpack: webpackConf, webpackMiddleware: { noInfo: true }, singleRun: true }) }
テストもES2015で書いていくので、先にセットアップしたwebpackの設定をここでも流用します。ほかにも、テストの実行環境(ブラウザ)やテスティングフレームワーク(mocha)、テストのエントリポイントとなるファイル(index.js)などを指定しています。
つづけてテストのエントリポイントとなるindex.jsを作成します。
// Polyfill fn.bind() for PhantomJS /* eslint-disable no-extend-native */ Function.prototype.bind = require('function-bind') // require all test files (files that ends with .spec.js) var testsContext = require.context('.', true, /\.spec$/) testsContext.keys().forEach(testsContext)
これでやっっっとユニットテストを書く準備ができました!いよいよお待ちかねのテストを書く作業に入りましょう。
Todosコンポーネントの実装に沿って、「新しいTodoの内容(newTodoTitle)を与えた上でTodosコンポーネントのaddTodo()メソッドをコールすると、todosに新しいオブジェクトが出来ること」を検証するテストを書きたいと思います。まずは最終的に期待するアサーションだけを書き、その状態でテストが失敗することを確認してみます。
import assert from 'assert' import Vue from 'vue' import Todos from '../src/javascripts/todos.vue' describe('Todos', () => { it('#addTodo creates new todo', function(done) { const vm = new Vue({ template: '<div><todos ref="todos"></todos></div>', components: { 'todos': Todos } }).$mount() let todosComponent = vm.$refs.todos; // Assert new todo is added assert.equal(todosComponent.todos.length, 1) assert.equal(todosComponent.todos[0].title, "Buy milk") done() }) })
$ npm testを実行してみましょう。$ npm testコマンドはpackage.jsonに書いたショートカットで、実体は$ karma start ./karma.conf.jsです。
ちゃんとテストが失敗しました。エラーの内容を見ると、todosの数が期待する値(1)と実際の結果(0)とでズレているのがわかります。Todosコンポーネントを初期化した段階では1つもtodoはできていないので、期待通りの結果ですね。
ではこのテストが正しく通るように、必要な手順を追記しましょう。showTodoInputとnewTodoTitleをセットし、addTodo()をコールする処理をアサーションの前に追加します。
... describe('Todos', () => { it('#addTodo creates new todo', function(done) { const vm = new Vue({ template: '<div><todos ref="todos"></todos></div>', components: { 'todos': Todos } }).$mount() let todosComponent = vm.$refs.todos; // Create new todo todosComponent.showTodoInput = true todosComponent.newTodoTitle = "Buy milk" todosComponent.addTodo() // Assert new todo is added assert.equal(todosComponent.todos.length, 1) assert.equal(todosComponent.todos[0].title, "Buy milk") done() }) })
ふたたび$ npm testを実行してみます。
テストが通りました!これでやっとVue.jsで実装したTodosコンポーネントのユニットテストが書けました。
実際のアプリケーションで動作するコンポーネントはもっと複雑なロジックを持っていることが多いと思いますが、Vueコンポーネントのテストを書く要領はここまで書いた内容と同じです。フロントエンドのユニットテストを充実させ、バグやデグレを未然に防ぐ堅牢なフロントエンド開発をしていきましょう!
Appendix
いざフロントエンドのユニットテストを書き始めてみると、テストが書きにくいと感じることがあります。アプリケーションのロジックが複雑すぎたり、ユーザーの操作にもとづく一時的なUIの状態が多いとこの傾向は強くなるようです。テストが書きにくいと感じる場合は、実はテストではなく実装を見直すべきであることが多々あります。テストしやすいコードは保守性や可読性の向上にもつながるので、テストが書きにくいと感じた際はコンポーネントの粒度や設計を見直してみてください。
XMLHttpRequestを利用して非同期でリクエストを発行しているコンポーネントなどには、sinon.jsなどのモックライブラリを利用すると便利です。
なお本記事を書くにあたって、以下のページを参考にしました。
Unit Testing - vue.js
vuejs/vue-loader-example
コンピューターで「脳」がつくれるか 読み終わった
GitHub新機能「required review」の使い方
GitHubに新しく「required review」という機能が追加されました。この機能を利用すると、修正を求めるコメントが付いた場合、追加の変更が承認されない限りはプルリクエストをマージできないようにする、といった厳格なプルリクエスト運用が可能になります。これまでのように、プルリクエストで上がってきた差分にコメントを付けるだけでなく、修正を要求したり承認できるようになっているのが特徴です。若干複雑にはなりますが、masterブランチや、マージにフックしてCIでのビルドやデプロイが実行されるようなブランチに対しては厳格な運用が出来るので、部分的に取り入れるメリットは大きそうです。
GitHubから公式のリリースノートは発表されていますが、日本語での資料がなかったため翻訳してみました。原文に忠実あることよりも翻訳文の読みやすさを意識し、意訳している部分もあります。
↓↓↓以下翻訳文↓↓↓
プルリクエストのレビューコメントについて
プルリクエストが作られると、リポジトリのread権限を持っている人なら誰でもそのプルリクエストの差分をレビューしコメントすることが可能です。レビューコメントは、そこで上がってきた差分について議論したり、リポジトリのコントリビューション・ガイドラインに沿っているか、などのクオリティチェックを促進する働きがあります。
レビューコメントには以下3つの種類があります。
コメント(comment): 変更内容を承認したり追加修正を要求することはせず、一般的なフィードバックとしてコメントを残す
承認(approve): 該当する変更のマージを承認するコメントを残す
修正要望(request changes): プルリクエストがマージされる前に修正を求めるためのコメントを残す
Tips:
「必須レビューコメント(required review)」がオンになっており、リポジトリにwrite・admin・owner権限を持つユーザーが「修正要望」コメントを残すと、そのコメントをしたユーザーによって「承認」コメントがつかない限り、プルリクエストはマージできなくなります。
リポジトリのownerあるいはadministratorは、レビューコメントが「承認」されていなかたり、「修正要求」コメントを残したレビュワーがそのorganizationを離れたりしていても、プルリクエストをマージすることができます。
「必須レビューコメント」について
リポジトリのadministratorは「保護されたブランチ(protected branch)」機能を使うことで、どんなプルリクエストであっても、マージされるまでに最低でも1つは「承認」レビューコメントがつかない限りマージできないようにすることが可能です。すでに「修正要望」コメントの付いたプルリクエストが存在する場合、保護されたブランチに対して「必須レビュー」コメントをつけることはできません。
さらに詳しく
Reviewing proposed changes in a pull request
Enabling required reviews for pull requests
情熱プログラマー 読み終わった
JavaScriptデザインパターン 読み終わった
実行環境
npm: 3.9.5
karma: 0.13.10
手順
テスト対象のJavaScriptコード、あるいはテストコードでブレイクポイント貼りたい箇所に debugger を記述
--single-run=false オプションを追加してkarmaを実行
例: $ karma start ./karma.conf.js --browsers=PhantomJS_custom --single-run=false
package.jsonにscriptsとして保存しておくと、npm testといったショートカットで実行できるので楽
karmaのプロセスにdebuggerが刺さるので、http://localhost:9000/webkit/inspector/inspector.html?page=2 を開く
Happy debugging!
参考: Debugging Karma Unit Tests
fetchとCORSに潜む罠
Meguro.es #4で発表した資料です