Avoriaz と Sinon.JS で methods のメソッドが呼ばれたことをテストする

このあたりを調べたときのメモ。

  • avoriaz@6.3.0
  • sinon@4.0.0

例えば、こういうコンポーネントがあって、ボタンクリック時のイベントハンドラmethods のメソッドにバインドしているものとする。

<template>
  <div>
    <button @click="onClick">Submit</button>
  </div>
</template>

<script>
export default {
  name: 'AvoriazTest',
  methods: {
    onClick () {
      // ...
    }
  }
}
</script>

で、以下の方法でボタンクリックでメソッドが呼ばれたことをテストする。

import { shallow } from 'avoriaz'
import sinon from 'sinon'
import AvoriazTest from '@/components/AvoriazTest'

describe('AvoriazTest.vue', () => {
  it('calls methods when the button clicked', () => {
    const wrapper = shallow(AvoriazTest)

    const stub = sinon.stub() // スタブを作って
    wrapper.setMethods({ 'onClick': stub }) // setMethods で差し替えて

    wrapper.find('button')[0].trigger('click')
    expect(stub.calledOnce).to.equal(true) // 呼ばれたことを確認する
  })
})
その他

using with vuex · Avoriaz

Vuex の mapActionsmapGetters を使っていて、それらのメソッドが呼ばれたことをテストする方法は Avoriaz の公式ドキュメントに書いてあります。参考になり過ぎる。

コンポーネントの props で required を付けるときは default を定義した方がよさそう

小ネタですみませんが、Vue.js #4 Advent Calendar 2017 の15日目が空いてたので書きます。最近、Vue.js を実案件で使い始めました。Vue.js 歴は浅いです。

タイトルの通りなんですが、誤りや本来はこうあるべきというのがあればご指摘いただけますと幸いです。


Vue.js のスタイルガイドの「プロパティの定義」というセクションに「プロパティの定義はできる限り詳細とするべきです。」と書かれています。

プロパティの定義 - Vue.js

というわけで、

props: [ 'count' ]

と書いていたところをスタイルガイドの例に倣って次のように変更してみました。

props: {
  count: {
    type: Number,
    required: true
  }
}

が、コンソールに警告が出るようになりました。

[Vue warn]: Invalid prop: type check failed for prop "count". Expected Number, got Undefined.

で、このメッセージでググってみたら、この issue を発見。

github.com

この issue 自体は単純な定義ミスが原因のようなんですが、なんとなく 初期値をセットした方がよさそう なのかなと思い付きました。というわけで、次のように default を定義してみたところ、先述の警告は出なくなりました。

props: {
  count: {
    type: Number,
    required: true,
    default: 0 // これを追加
  }
}
補足

ちなみに、required を外すと default が未定義でも先述の警告は出ないようです。

なんとなく、requireddefault は一緒に定義するものなのかな?と思ったんですが、ドキュメントを探しても特にこれといったものは見つからず...。もしくは、コンポーネントの呼び出し方が原因なんだろうか。

このあたりは仕組みをきちんと理解しないとだめですね...。中途半端ですみませんが、現場からは以上です。

Spring Fest 2017 に行ってきた #jsug

簡単に所感をまとめます。

springfest2017.springframework.jp

What's New in Spring

会場に到着したらすでに翻訳機がなくなってたのでスライドから雰囲気だけ感じ取る。内容は Spring 5 と Spring Boot 2.0 について。Spring MVC ではこう書いていたコードが Spring WebFlux ではこう書くよというデモ。spring-boot-starter-webflux を依存に入れるだけでランタイムが Tomcat から Netty に変わる。あとは、Spring WebClient の話。Spring WebClient Scatter Gather ってなんだろうか。要復習。

Introduction to Spring WebFlux

www.slideshare.net

こちらも Spring WebFlux の話。100程度のリクエストを10スレッドくらいで処理できる。プロセッサーの数だけスレッドを立ち上げるらしい。Spring MVC のコードでランタイムを Netty に変えるだけで Reactive の恩恵を受けられるっぽい。flatMap は裏で subscribe してくれるが、map は subscribe してくれないので注意が必要。WebFlux を使うときは Blocking な処理を書かないこと。どうしても Blocking な処理が必要なら別スレッドで処理させる。RDBを使うシステムの場合、JDBCが Blocking なので別のスレッドで処理させるか、非同期JDBCのサポートを待つのがよい。

ところで、Blocking/Non-blocking はスレッドを占有するかしないか、同期/非同期は別スレッドで処理させるかどうか、と理解したんですが合ってるんだろうか...。

Spring Security 5 解剖速報

www.slideshare.net

Spring Security 5 では、WebFlux と OAuth 2.0 が対応される。従来の Spring Security は Servlet API に依存しているが、WebFlux では Servlet API に依存しない。ただし、Servlet API 依存のアーキテクチャがなくなるわけではない。Spring WebTestClient を使うと WebFlux のエンドポイントをテストできる。これまでは各サブプロジェクトが OAuth2 対応していたが、今後は Spring Security が OAuth2 をサポートし、サブプロジェクトは Spring Security の機能を使う。

Struts -> Spring 移植のテクニックとノウハウを公開

struts-config.xml や validation.xml から Controller のスケルトンコードや Form クラス/バリデーションを自動生成する。移植元のアプリで独自に拡張されたものは手で移植する。フレームワークの移植では不具合修正やリファクタリングに手を出さないことがポイント。

自分もフレームワーク移植をやりましたが、バリデーションロジックとかの移植は割と大変でした。自動化については移植元のアプリの作りにかなり影響されると思うし、きれいに作られているとは限らないので結局は手作業が多くなるんではないかと思っています。

また、フレームワークの移植では再現性を重視するようですが、重視し過ぎるのもどうなのかなと思っています。再現性を重視するあまりオリジナルのフレームワークの機能拡張に追随できなくなったりしないだろうか、移植先のフレームワークのお作法に従う方がいいのではないかと考えてしまうわけです。まぁ、コストはかかるし、そううまくはいかないかもだけど。

Pivotal認定講師が教える!Spring Data JPAによるデータアクセス徹底入門

www.slideshare.net

JPA の特徴やハマりどころの話。めっちゃ分かり易かった。要復習。

Spring と TDD

www.slideshare.net

Spring にはテストの仕組みがたくさん用意されているよという話。いろいろなパターンのテストの仕方が説明されていてよかったが、TDD はあまり関係なかった。ちょうど テスト駆動開発 を読んでるところだったので少し残念。

Spring BootとSpring Cloudで始めるマイクロサービス

前半はマイクロサービスの話、後半は Spring Boot と Spring Cloud の話。せろさんのマイクロサービスの話は何回か聴いたことはあるが、分かり易いし納得感がある。

その他

あとで読む。

JJUG CCC 2017 Fall に行ってきた #jjug_ccc

簡単に所感をまとめます。セッション資料はどこかで公開されるはず。

www.java-users.jp

Selenide or Geb? あなたはそのときどちらを使う?

  • Selenide
    • Java製WebDriverラッパー
    • DSLによるjQueryライクなセレクタ
    • IDEの補完を活用できる
    • Ajaxなどの非同期処理に対する記述が書きやすい
  • Geb

    • じぇぶ
    • Groovy製WebDriverラッパー
    • DSLによるjQueryライクなセレクタ
    • waitが書きやすい
  • Selenideのメリ/デメ

  • Gepのメリ/デメ

    • Selenideより細かい制御ができる
    • Groovy/Gradleの知識が必要
    • IDEの補完は効きづらい
  • ローカルでは動くのにJenkins上では落ちることがある

    • おそらく環境依存などが原因 (jsのロードが遅れるなど)
    • Selenideは失敗して落ちるとスクリーンショットとHTMLが残る
  • マルチブラウザテスト

    • GebConfig上でWebDriverの生成処理を変更することで対応
    • Selenideも設定ファイルでWebDriverの生成処理を変更することができる
  • PhantomJSやChromeのヘッドレスモードを使ってテストする

    • E2Eのテストなので実際は本物のブラウザでテストするべきという考えがある
  • 読むのを楽にしたいか/書くのを楽にしたいか

    • 書きやすさならSelenide
    • 読みやすさならGeb
  • SelenideはIDEのサポートがあるので取っ付きやすい
  • Gebは学習コストがやや高め
  • Selenideはテスティングフレームワーク (簡単なアサーションはある)
  • GebはSpockなどと組み合わせる

最近、Selenide を試してみたところだったので話が聴けてよかったです。両方ともIDEのサポートはあるものの、Intellij IDEA との親和性が高そう。書くのをラクにしたいか、読むのをラクにしたいかで選べばよい、とのこと。確かに、Selenide は書きやすそうな印象。非同期処理に対する記述のところは調べておきたい。

Apache Camel + hawtio + Spring Boot によるモダンなインテグレーション・マイクロサービス

  • インテグレーションマイクロサービス
  • Smart Endpoints and Dump Pipes

    • マイクロサービス自体に他のサービスと連携する機能を持たせる
    • ルーティングロジックがエンドポイント(各マイクロサービス)の中にある
    • パイプはシンプルにする
  • マイクロサービス間が連携したらスパゲッティにならないか

    • そのために Enterprise Service Bus がある
  • コンウェイの法則

    • サービスと開発チームが小さくなる
    • インテグレーションはスパゲッティになるが、個々の開発チームはシンプルになる
  • Apache Camel

  • ルーティングはXMLでも書ける (Spring XML DSL)

    • IDEGUIを使う場合はXMLを使う必要あり
  • Content-Based Router

    • コンテンツの内容に応じてルーティング先を決める
    • choice / when / to
  • hawtio

    • Angular 1.x + Jolokia ベースの監視ツール
    • HTTP/JSONで通信
    • Spring Boot 連携 (監視用のポートで起動する)
    • Camelサポートが豊富
      • ルーティングがグラフィカルに見れる
      • やり取りしているメッセージが見れる
  • Camelのテストフレームワークでルーティングのテストができる

  • コンポーネントが多いので選定などは大変っぽい

マイクロサービスの開発自体あまりなく名前程度しか知らなかったんですが、Apache Camel はよさそうです。機会があれば使ってみたい。hawtio は初めて聞いた。

DDD × CQRS - コマンドとクエリでORMを使い分けた話

  • CQRS
    • コマンドクエリ責務分離
    • 単独でも適用可能
    • イベントソーシングと組み合わせる必要はない
  • 書き込みと読み込みのモデルを分離する

  • 従来は単一のデータストアで同じモデルが読み書きを担う

    • 1モデルの処理が複雑になる
  • そもそも読み込みと書き込みの要件は異なる

    • パフォーマンスを求められるのは参照系
  • 1モデルに詰めるとどこかで無理がでてくる

  • モデルを読み書きで分離する

  • データストアを分離する

    • 参照系は read-only なレプリカにするなど
  • イベントソーシング

    • すべてのアクションをイベントとして記録する
    • 既存のデータはupdateしない
    • Writeモデルはイベントとして記録される
    • Readモデルはイベントから変形される
  • 読み込み書き込みそれぞれの要件に合わせてORMを選定する
  • JOOQ
    • SQLに近い記述でタイプセーフにSQLが書ける
  • Writeモデルの制約はコンストラクタと公開メソッドで実現する

CQRS についてはよく分かってないマンですが、読み込みと書き込みでモデルを分けるというのは参考になりました。特に要件に合わせてORMも分けるというのは思い付かなかった...。

劇的改善 CI4時間から5分へ〜私がやった10のこと〜

  • R-SET
  • CIは10分で終わらせるのがいい (遅くとも20分以内が望ましい)

  • レイヤごとにテストの責務を閉じる

    • RepositoryとRDBはまとめてテスト
  • Controllerのテストは @WebMvcTest にする
    • @SpringBootTest はすべてのBeanを登録してアプリケーションを起動するので遅い
  • ServiceレイヤのテストではDIは使わない
    • Springの機能は使わない (RepositoryはMock化する)
  • RepositoryはFilterでBeanを選択する
    • @MyBatis or @MyBatisTest で必要なBeanのみを登録する
    • MyBatisに限らず必要なBeanをフィルタリングするのは大事
  • テストサイズ導入, テストサイズによる実行タイミングの分割
    • Googleが提唱している
    • Small, Medium, Large
    • 実行タイミングはプルリクエストやブランチのマージなど
    • LargeテストはいわゆるE2Eテスト
    • JUnit@Category, maven-surefire-plugin の groups で実現する
    • mvn の Profile で実行するテストサイズを切り替える

たぶん、無駄なテストを改善する/除去するというのが最も効果ありそう。テストサイズについては初めて知りました。あとで調べてみよう。

Java SE 9の紹介: モジュール・システムを中心に

Java SE 9の紹介: モジュール・システムを中心に

  • Maven, Gradle を使っても複数バージョン混在地獄は解決できない

    • jarファイルの名前しか見ないため (中身までは見ない)
  • モジュールシステム

    • モジュール単位で依存関係を整理する
    • 内部パッケージの使用を制限する
  • 複数のモジュールで同一パッケージが含まれる場合、JVM起動時点で検知される
  • module-info.java

    • 外部に公開するパッケージは exports 命令で指定する
    • 自分が使うモジュールは requires 命令で指定する
    • requires したモジュールの exports されたパッケージの public なクラスが使える
    • それ以外を使うとコンパイルエラー/実行時例外
  • 標準ライブラリである java.base モジュールは暗黙的に requires される

  • モジュールシステムはリフレクションにも適用される

    • opens 命令で外部からリフレクションでアクセスできるようになる
    • モジュール宣言に open を修飾するとすべてのパッケージが opens 指定される
  • モジュールパスとクラスパスは別

  • 無名モジュール/自動モジュール

  • ライブラリ提供者はすぐにモジュール化すること

    • 使う側が requires 命令を書けない
  • Properties クラスは Latin-1 のまま

この日、最も聴きたかったセッション。モジュールシステムの概要が分かってよかった。要復習。なんとなく、ちゃんと作ってるライブラリであれば、モジュール化はそこまで難しくなさそうな印象。

その他

今年春のCCCは参加できなかったのですが、参加人数が多くてかなり混雑してた模様。今回は、前回より参加人数が少なかったのか、それほど混雑は感じられませんでした。というか、通路が一方通行になってたりいろいろ工夫されてたのがよかったのかも。移動がいつもよりずっとラクでした。

Vue.js でウィジェットっぽいもの (仮) その2

前回の続き

Vue.js でウィジェットっぽいものを作ってみる (仮) - kntmr-blog

続編として今回は以下をやってみようかと。

  • Webpack で出力するファイルをまとめる
  • ウィジェットにパラメータを渡せるようにする
  • 任意のタグ/キーワードで表示できるようにする

Webpack で出力するファイルをまとめる

vue-cli の Webpack テンプレートでは CommonsChunkPlugin プラグインがデフォルトで設定されている。普通はこのままでよいが、今回はウィジェットとして作成するので、HTMLから読み込むファイルをまとめたい。

出力ファイルの設定は webpack.prod.conf.js に記載されている。で、plugins の中に記載されている webpack.optimize.CommonsChunkPlugin の箇所をコメントアウトする。尚、デフォルトで出力される vendor と manifest についてはまだよく分かっていません...。

あと、CommonsChunkPlugin プラグインについては、以下が参考になりました。

qiita.com

ウィジェットにパラメータを渡せるようにする

以下が参考になりました。Vue インスタンス生成時に render 関数の中で App.vue の props に渡すというもの。

qiita.com

今回は、HTML に data 属性でパラメータを設定する。

<div id="app" data-tag="vue.js"></div>

で、main.js の中で取得して store に格納する。

new Vue({
  el: '#app',
  router,
  store,
  template: '<App/>',
  components: { App },
  render: function (createElement) {
    const dataset = this.$el.dataset
    this.$store.dispatch('setQuery', dataset.tag)
    return createElement('App')
  }
})

その他、Vue インスタンス生成前にやるなら以下でも可。

import store from './store'

const element = document.getElementById('app')
const dataset = element.dataset
store.dispatch('setQuery', dataset.tag)

任意のタグ/キーワードで表示できるようにする

上記、store に格納したパラメータを API アクセス時にクエリで渡す。とりあえず、これで任意のタグでウィジェットを表示できる。キーワードも同様にできると思うので今回は省略。

import axios from 'axios'

const api = {
  get: (url) => return axios.get(url)
}
export default {
  getItems: (query) => api.get(`https://qiita.com/api/v2/items?query=${query}`).then(resp => {
    return Promise.resolve(resp.data)
  })
}

あと、store にデータとクエリを一緒に持たせるのはどうなんだろうか。個人的には悪くない気もするけど...。

Vue.js でウィジェットっぽいものを作ってみる (仮)

Qiita Widget : Qiitaの投稿を表示できるブログパーツ

こんな感じのウィジェットっぽいものを、Vue.js と Veux の学習を兼ねて作ってみようかと。今回は Qiita の Vue.js タグを表示するもの。

github.com


以下、備忘録。

vue-cli

vue-cli の Webpack テンプレートを使用してプロジェクトを作成する。

npm install -g vue-cli

最小構成でやるのでテストとかはないです。

vue init webpack vue-widget-sample
cd vue-widget-sample
npm install

es6-promise

IE 対応のため、es6-promise をインストール。IE では Promise が使えない。

npm install --save es6-promise

src/main.js の先頭に以下を追加。

import 'es6-promise/auto'

Vuex

本家サイト(ja)GitHub の examples を参考に実装。当初、下図が全然ピンと来ていなかったのですが、コードを書いてみてようやく理解できてきました。(たぶん)

https://vuex.vuejs.org/vuex.png

ドキュメントに書いてありますが、小規模なアプリであればイベントバスで十分かもしれません。もしくは、props だけでイケるかもしれません。が、ある程度は規模に関係なく Vuex は導入してよいかと思います。このアーキテクチャに沿うことで全体的にシンプルに書けるようになると感じています。

あと、以下が参考になりました。

chibinowa.net

ウィジェット

こんな感じで読み込む。このあたりはまだ改善の余地あり...。

<!DOCTYPE html>
<html>
<head>
  <meta charset=utf-8>
  <title>vue-widget-sample</title>
  <style type=text/css>
    .my-widget {
      width: 360px;
      height: 400px;
    }
  </style>
  <link href="/css/app.css" rel="stylesheet">
</head>
<body>
  <div id=app></div>
  <script type="text/javascript" src="/js/manifest.js"></script>
  <script type="text/javascript" src="/js/vendor.js"></script>
  <script type="text/javascript" src="/js/app.js"></script>
</body>
</html>

これからやりたいこと

  • Webpack で出力するファイルをまとめる
  • ウィジェットにパラメータを渡せるようにする
  • 任意のタグ/キーワードで表示できるようにする

今さらながら Selenide を試してみる

簡易なテストページを作って試してみました。

サンプルコード


以下、備忘録。

各ブラウザの WebDriver をダウンロードして配置する。

Third Party Browser Drivers - Downloads

テスト対象のブラウザを設定する。

{
    Configuration.browser = WebDriverRunner.CHROME;
    System.setProperty("webdriver.chrome.driver", "driver/chromedriver.exe"); // WebDriver のパス
}

テストコードからは jQuery のようにセレクタを使って要素を指定する。

open("http://localhost:8080/");
$(By.name("input")).val("foo").pressEnter();
$("#output").shouldBe(text("foo"));

テストコードに同じセレクタが重複するのを避けるため、Page Object パターンを使う。

Page Objects - Documentation

ページ要素を Page Object クラスに隠蔽する。

@FindBy(name = "input")
public SelenideElement input;
@FindBy(id = "output")
public SelenideElement output;

テストコードからセレクタを排除できる。

IndexPage page = open("http://localhost:8080/", IndexPage.class);
page.input.val("bar").pressEnter();
page.output.shouldBe(text("bar"));

Selenide としては、ページ要素のフィールドを private にして、ページ要素に対するロジックをメソッドで提供することを推奨している模様。

IndexPage echo(String val) {
    $(By.name("input")).val(val).pressEnter();
    return this;
}
String output() {
    return $("#output").text();
}

テストコードにはページ要素を操作するメソッド呼び出しとアサーションを記述する。

IndexPage page = open("http://localhost:8080/", IndexPage.class);
page.echo("buz");
assertEquals("buz", page.output());