JavaScript でテストを書く時のライブラリについて調べた

1年半前は 業務 とか 趣味 で TypeScript を使ってテストも書いてたんだけど、最近は iOS ばかりで忘れてしまっていた。

けどまた仕事で同じような環境を作ったので、テストを書くときにどういう Framework があって役割は何かをメモっておく。

テストフレームワーク

テストを書くために必要なライブラリ。
iOS でいったら Quick や XCTest, Rails でいったら rspec とかが該当する。

アサーションライブラリ

iOS でいうと Nimble が該当する。
XCTest や rspec には組み込みで入っている。

mock / stub / spy

sinon 以外に話題に上がっているのを見なかったので、デファクトスタンダードだと思っていいのかもしれない。

テストランナー

コマンドラインでテスト実行したらブラウザが開いて〜みたいなのができるっぽい。

用途に合わせて構成を決める

自分はモックやテストランナーは不要だけど TypeScript を使っているので mocha + power-assert + espower-typescript という構成になった。
今後モックしたくなったら sinon を追加すると思う。

ReactNative とかでがっつりテスト書くなら jest は良さそうだし、ブラウザでテストをするなら Karma などが必要かもしれない。
その辺は人によって違うので各自選ばないといけない。

参考

PlantUML でシーケンス図を書いたがめっちゃ良かった

PlantUML

シンプルなテキストファイルで UML を作ることのできる、オープンソースのツール

今までは astah とかでユースケース図とかシーケンス図書いてたんだけど、 GUI で書くの辛いなと思って PlantUML というのを使ってみた。

コードで UML を書ける、今回はシーケンス図を書いたけどユースケース図やフローチャートなども書ける。

環境構築

Web のエディタだと PlantUML Editor というのがある。

けどローカルでやりたかったので VSCode の拡張を入れた。

marketplace.visualstudio.com

これを入れるだけ、シンタックスも効くようになって良い。
Java, Graphviz などを入れておけとあるけど、特に何もせず使えているので別アプリケーション経由でインストールされてたのかもしれない。

拡張子は .pu, .plantuml, .wsd などあるっぽいが .pu にしている。

書いてみる

記法

今回自分が使ったやつだけ。
シーケンス図の構文と機能 に網羅的に書いてあるけど多すぎて使いこなせていない。

  • 登場人物の宣言
    • 表示形式や表示順などを最初に定義できる
      • actor Hoge
      • database Fuga
  • 矢印
    • -> は直線、 --> は破線
    • Hoge -> Fuga: description
    • Fuga --> Huga
  • 動作中を示す表示
    • activate, deactivate で動作中を示せる
      • activate Hoge
      • deactivate Fuga
  • メモ
    • note, hnote でメモが書ける
    • note は四角、 hnote はひし形
    • left of, right of, over で位置を指定できる
      • note left of Hoge: desc
      • note right of Fuga
      • note over Hoge
  • タイトル
    • == で文字を囲むとタイトルになる
        • == タイトル ==

この 5 つの要素を覚えるだけでいいシーケンス図がかけた。

サンプル

@startuml

actor GameManager
database Application
database Database
actor User

== マスタデータ更新 ==

GameManager -> Application: データ登録
activate GameManager
activate Application

note over GameManager: 処理中は画面操作できない

Application -> Database: Insert
activate Database

Database --> Application: Success
deactivate Database

Application -> Application: データ加工

note right of Application #aqua
攻撃力↑↑
end note

Application -> Database: Insert
activate Database

Database --> Application: Success
deactivate Database

Application --> GameManager: Complete
deactivate Application
deactivate GameManager

== データ表示 ==

User -> Application: データ取得
activate User
activate Application

hnote over User : 読み込み待ち

Application -> Database: select * from item
activate Database

Database --> Application: result
deactivate Database

Application -> Application: jsonize

Application --> User: render json
deactivate User
deactivate Application

@enduml

f:id:star__hoshi:20171208181929p:plain

iOS Test Night #6 1周年 まとめ #ios_test_night

testnight.connpass.com

iOS Test Night #6 にブログ枠として参加したので、そのレポートです。 ㊗️ 1周年 🎉

開始前の挨拶として、Qiita に投稿された iOS のテスト関連の記事がこの一年で増えたのか? という話から始まりました。
結果として、増えはしたけどそこまで増えていないということで、今後も勉強会を通して iOS のテストをもっと盛り上げていくぞ 💪 とのことでした。

発表

Appiumで行う対話的テスト by @alligator_tama

TODO: 公開されたら資料はる

Appium で 2 つのアプリを立ち上げて、それらのアプリを対話的に実行させテストを行う、という内容。
Xcode9 から iOS Simulator が複数立ち上げられるようになってできるようになり、Appium の実行時に Port 番号を指定するだけで複数 Simulator を立ち上げることができる。

実際に 2 つのアプリが連携するデモが行われました。

Quick / Nimble をより快適に使うために by @tobi462

  • Quick / Nimble をライブコーディングを交えながら快適に使うはなし
    • Quick の概要や rspec のように構造的なテストにかけることのメリットの話や、 assertion ライブラリの Nimble についての話。
    • Nimble は英文のように読めるし、エラーメッセージもわかりやすく出力されるというメリットがある。
  • しかしメリットだけではなくデメリットもあって
    • 学習コストがかかる、ドキュメントの量も結構ある
    • BDD フレームワークの慣れが必要
    • 補完が微妙
      • Quick の expect ではなく XCTest の expect が出てきてしまったり、メソッドチェーンが切れると補完が効かない
  • これらのデメリットはラッパーを作ったり、スニペットを作って補完を改善できた。

fastlane snapshotの並列実行についてまとめた by @tarappo

fastlane snapshot の速度改善のはなし。

snapshot は指定端末・言語でスクショを簡単に取ることができるもの。snapshot は端末数・言語数が増えるほど実行時間が増加する。
今までの改善策はマシン3台用意するなど、金で殴っていた。

しかし Xcode9 から Simulator の並列実行ができるようになった、snapshot の設定を concurrent_simulators: true にするだけで良い。端末は devices で指定する。

MacPro (6コア) で並列実行した結果、5台同時起動する場合は 2 倍早くなったのでとりあえず並列実行するのが良さそう。

LT枠

assertion を積極的に使って役だった話 by @kboy_silvergym

筋肉 (assertion) の話。

  • assertion はわざとクラッシュさせること。
  • Assert, precondition, fatalError などがある。
  • assertion する目的は、実装漏れを防ぎたい、不安なところに念のため書いておく。
  • 実装もれを防ぐ
    • id == 0 だったらおかしい!
    • enum で絶対に通らないであろうところに assertionFailure()
  • 実際に防げた
    • サーバから uid というパラメータをサーバからもらえなかったことに気がつけた

未来のエンジニアのために assertion を残すというのはとても良さそう。

iOSシミュレータでのUIテストの様子を録画してみよう by @Kesin11

  • UITest は時間がかかるのと、テストが落ちた時にスクショを撮ってくれるけどなんでテストが落ちたのかわからない…。
    • なのでスクショじゃなくて録画したい。
  • QuickTime
    • コマンドからできないし不便
  • recordVideo

Test Nightきっかけでテストをはじめた人の発表枠

Mocking With Firebase by @d_date

TODO: 資料公開されたらはる

Firebase Realtime DB のテストについての話。

  • ターゲットで読み込むファイルを変えることでモック化する。
  • インターフェイスを同じにして処理を変えてテストをする。

プロジェクトの1ファイルにテストを書いてみた! 〜本当にこれで合ってるの…??〜 by @takattata

XCTest で CleanArchitecture のテストをかいてみたり、Firebase をモックしたり、RxSwift でテストを書いてみたけど本当に正しいのかわからんのであとでみんなに教えてほしい、みたいな感じだった。

何故テストが書けないのか by @fromkk

techblog.timers-inc.com

個人アプリなのでテストは書いてない。というかそもそもテストをかける設計になっていない。
なので設計を見直して適切に継承するようにした。

(自分の個人アプリもシングルトン + FatViewController + Realm + ノーテストなので心が痛かった... )

iTunes Connect に Upload 後の bitcode 再コンパイルでエラーが出た

iTunes Connect にバイナリのアップロードは完了したんだけど、 iTunes Connect: Processing stopped for HogeApp みたいなメールが来た。 何か問題があって bitcode の再コンパイルが止まってしまったようだ。

メール全文:

iTunes Connect: Processing stopped for HogeApp

Dear Kensuke Hoshikawa,

While processing your iOS app, HogeApp, errors occurred in the app thinning process, and your app couldn’t be thinned. If your > app contains bitcode, bitcode processing may have failed. Because of these errors, this build of your app will not be able to be submitted for review or placed on the App Store. For information that may help resolve this issue, see Tech Note 2432.

Regards,

The App Store team

解決策: 再アップロード

なんとなく再度バイナリアップロードしたら直る気がしたので、再アップロードした。
そしたら次は has completed processing の連絡が来た。

まあよくありますよね、 Apple 様の機嫌が悪かったようだ。

Tech Note 2432

メール本文には Tech Note 2432 に対処法が書いてあるから見てくれって書いてある。
今回は再アップロードで済んだけど、本当に何か問題がある場合は修正が必要になりそう。

参考

iTunes Connect に Upload しようとしたら Info.plist 関連で怒られた

f:id:star__hoshi:20171202012835p:plain

Unable to process application at this time due to the following error: This bundle is invalid. The Info.plist file is missing or could not be parsed. Please check it for embedded control characters..

iTunes Connect にバイナリアップロードしようとしたらこんなエラーがでた。
Info.plist が見つからないか、パースができないよ、と書かれている。

しかし Info.plist は問題ないし plist のフォーマットも問題なさそう。

ググる

ここに書いてる問題ではなさそう...。

LaunchScreen

Info.plist を眺めてみると、UILaunchStoryboardNameLaunch Screen になっている、正しくは LaunchScreen だった。
このスペース 1 つでエラーが出ていた、スペースを消したら無事提出ができた。

The Info.plist file is missing or could not be parsed. と書いてあって、 Info.plist そのものには問題がなくても UILaunchStoryboardName などがちゃんと設定されてないとうまく読み込めずこのエラーが出てしまうようなので注意しましょう。

console.log で改行させたくない

node: v8.2.1 でのはなし。

JavaScript でログを出力するときは console.log が一般的だけど、これだと改行が入ってしまう。

console.log('hoge')
console.log('hoge')

# hoge
# hoge

そうじゃなくて、 hogehoge って続けてログ出力してほしい。

ググる

node.jsで標準出力に改行なしで出力する - Qiita

という記事が出て来た。

util.print

sys モジュールは deprecated なんで、util モジュールを使いましょう。

なるほど、 util なんてもんがあるのか、と思って使ってみる。

const util = require('util')

util.print('hoge')
util.print('hoge')

# hogehoge(node:67917) [DEP0026] DeprecationWarning: util.print is deprecated. Use console.log instead.

hogehoge って出たけど Warning も出ている。

The util.print() API is deprecated. Please use console.log() instead. Deprecated APIs | Node.js v9.2.0 Documentation

なるほど util.print() は deprecated だから console.log() を使えと...。

process.stdout.write

process.stdout.write('hoge')
process.stdout.write('hoge')

# hogehoge

これで良さそう。

Node.jsのconsole.logとprocess.stdout.writeの違い - Tatsuya Oiwa を読むに、 console.log は process.stdout.write のラッパーっぽいので信用して大丈夫そうだ。

Stripe の Order で送料を商品によって変更するときは Callback API を実装する必要がある

Stripe の Order で送料を設定するパターンは 4 つある。

  • Free
  • Flat-rate
  • Callback
  • Provider

f:id:star__hoshi:20171129120506p:plain

Stripe Document

この情報は 2017-11-29 時点の情報であり、正確な情報は Orders API Dynamic Shipping and Tax Calculation を参照してほしい。

Order の種類

Order は sku に対し行うもので、初期状態だと送料を設定できない。
送料を設定するには https://dashboard.stripe.com/account/relay/settings の設定を変更する。

Free

送料無料。デフォルトでこれ。

Flat-rate

一律同じ送料。商品の個数や配達先に関係なく送料が同じになる。

Provider

サードパーティの配達先と連携できる。
海の向こうだとこういうサービスが整っていて、 API 投げたらよしなに送料など計算して配送してくれるようだ。

Callback

一番自由度が高く、しかし実装コストが面倒。コールバック URL を指定する必要がある。

流れとしてはこんな感じになる。

  1. ユーザが商品を購入する
  2. Stripe で Order.create する
  3. Stripe から送料いくらにする? って聞かれる
    • Callback API が叩かれるので、 API を用意しておく
  4. 送料が設定され Order.create が完了する

Callback API

コードは雑なんだけど、ざっくりこんな感じで実装する、TypeScript で書いてある。
Stripe から飛んできた Order に対し送料を渡して Update する。

export const orderCallback = functions.https.onRequest(async (request, response) => {
  if (request.method.toLowerCase() !== 'post') { response.status(405).end() }

  const stripeOrderID: string = request.body.order.id
  const itemIDs: string[] = request.body.order.metadata.itemIDs
  const items: Item[] = itemIDs.map(itemID => {
    // 送料などの情報を持った Item を取得
  }

  const shippingMethods = items.map(item => {
    return {
      id: item.id,
      amount:item.postage,
      description: item.shippingMethod,
      currency: 'jpy'
    }
  })

  const json = {
    order_update: {
      order_id: stripeOrderID,
      shipping_methods: shippingMethods
    }
  }

  response.status(200).json(json)
})

雑実装なので、 Production 入れる前にもっとエラーハンドリングしないとまずいですね。

おわり

Order するときに shipping_methods も渡せればいいのにと思うけど、まあ色々事情があるのだと思う。

ちなみにこの情報は https://stripe.com/docs を眺めていてもどこにもリンクがなく、 Stripe の設定画面を眺めていたら配送設定みたいなのがあって、そこから document にたどり着いた。
重要な情報だし最初からリンク載せておいてくれ〜...

Xcode の Other Swift Flag で -D STAGING を指定したのに #if STAGING が有効にならなかった

Xcode で環境別にコンパイルするために Staging の Configuration を作ったんだけど、 -D STAGING を指定しても #if STAGING が false になっていた。

f:id:star__hoshi:20171127214536p:plain

なんでかな〜 って設定眺めていたら、 Active Compilation Conditions が Debug になっていたのが原因だった。

f:id:star__hoshi:20171127215036p:plain

これを STAGING にしたら #if STAGING がうまくいった。

f:id:star__hoshi:20171127215120p:plain

Rails 開発で GitHub に Push して CircleCI のテストが通ったら Heroku に deploy する

CircleCI を使って、テストが通ったら Heroku に deploy するという流れを構築する。

サンプルリポジトリ: starhoshi/rails-circleci

環境

  • GitHub
  • Rails 5.X
  • Heroku
  • CircleCI 1.X
    • ここでのサンプルは 2.0 ではない

ソースコードstarhoshi/rails-circleci

まず rails new

$ gem install rails
$ rails new . --api -d=postgresql
$ rails g controller

これで Rails Project + users_controller が作成される。
また、コメントアウトされている test のコメントを外し、通るようにする。

test/controllers/users_controller_test.rb

require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest
  test "the truth" do
    assert true
  end
end

GitHub に Push

GitHub に Repository を作って、 Push しておく

CircleCI と Repository の連携

CircleCI に GitHub でログインしておく。
CircleCIでRails4アプリをHerokuへデプロイする - Qiita にあるように、 ssh key の登録が必要かもしれない。

Projects > Add Project > rails-circleci を Set Up する。

f:id:star__hoshi:20171126170222g:plain

そうすると勝手に CircleCI が回り出して、テストが成功する。

Heroku

Heroku App 作成

こんな感じで作成する。

f:id:star__hoshi:20171126170458p:plain

API Key

https://dashboard.heroku.com/account から API Key を取得

f:id:star__hoshi:20171126170540p:plain

CircleCI の Heroku Deployment 設定

https://circleci.com/account/heroku に取得した API Key を設定する。

f:id:star__hoshi:20171126170746g:plain

これで Heroku / CircleCI の設定が完了。

circle.yml

deployment:
  production:
    branch: master
    commands:
      - heroku maintenance:on --app rails-circleci
      - git push git@heroku.com:rails-circleci.git $CIRCLE_SHA1:refs/heads/master
      - heroku run 'rake db:migrate; rake db:seed_fu' --app rails-circleci
      - heroku maintenance:off --app rails-circleci

こんな感じで作成。 --app と git@heroku.com:rails-circleci.git は自分の環境に合わせる。

git push すると CircleCI が動いて Heroku Deploy される

git push すると自動で CircleCI が動いて、 test が実行されたあとに deploy される。

f:id:star__hoshi:20171126171100p:plain

これでめでたく当初やりたかったことができた。

test がコケたらどうなる?

Not Deploying になって、 deploy がされないことがわかる。

f:id:star__hoshi:20171126171248p:plain

bundle update のプルリクエストを毎日自動で作成する

Gemfile.lock を最新に保つため、bundle update を毎日自動でできるようにしたい。
Tachikoma.io というサービスもあるみたいだけど、 private repo は有料っぽいので自作した。

勝手に update されてアプリケーションがバグると困るので、

  1. Gemfile.lock を更新したプルリクエストを作る
  2. プルリクエストを人間が確認してマージ

という手順で行う。

先人の知恵

ここら辺を参考にした。

環境

  • CircleCI 1.X
    • 1年前に構築した環境なので 2.0 ではない...
  • GitHub Private Repository
  • Heroku
  • Rails 5.X

流れ

  1. Heroku Scheduler が CircleCI を叩く
  2. CircleCI で bundle update を実施
  3. Gemfile.lock 更新があれば CircleCI が GitHub に PR を作成
  4. PR を人間が確認してマージボタンをポチ

1. 前準備

2. rake task 作成

lib/tasks/bundle_update_pull_request.rake を作成する。

# frozen_string_literal: true
namespace :bundle_update_pull_request do
  desc 'pull request if bundle updated'

  task check: :environment do
    project = 'repo-team/repo-rails'
    branch = 'master'
    token = 'CircleCI Token'
    params = '{"build_parameters": {"BUNDLE_UPDATE": true}}'

    header = { 'Content-Type' => 'application/json' }
    client = Net::HTTP.new('circleci.com', 443)
    client.use_ssl = true
    response = client.post("/api/v1/project/#{project}/tree/#{branch}?circle-token=#{token}", params, header)
  end
end

CircleCI の Nightly Builds を叩くだけの task。
params に '{"build_parameters": {"BUNDLE_UPDATE": true}}' を渡していて、 CircleCI ではこのパラメータをみて bundle update するのか判断する。

この task は以下のコマンドで実行できる。

$ bundle exec rake bundle_update_pull_request:check

3. circle.yml

circle.yml をこんな感じで書く。

test:
  post:
    - >
      if [ -n "${BUNDLE_UPDATE}" -a "${CIRCLE_BRANCH}" = 'master' ] ; then
        bundle update
      fi

deployment:
  master:
    branch: master
    commands:
      - >
        if [ -n "${BUNDLE_UPDATE}" ] ; then
          bash script/circleci/create_pull_request_if_needed.sh
        fi

2 で params に渡した BUNDLE_UPDATE が true だったら bundle update 実行 + PR 作成するようになっている。

4. create_pull_request_if_needed.sh

script/circleci/create_pull_request_if_needed.sh を作成する。

#!/bin/bash

export BRANCH=bundle-update-`date -u "+%Y%m%d"`
if [[ -n `git status -sb 2> /dev/null | grep Gemfile.lock` ]] ; then
  git config --global user.email git@git.com
  git config --global user.name 'git'
  git add Gemfile.lock
  git commit -m 'Bundle update'
  git branch -M $BRANCH
  git push origin $BRANCH
  bundle exec ruby script/circleci/create_pull_request.rb
fi

3 で実行された bundle update で、 Gemfile.lock に差分があれば PR を作成するだけ。

5. create_pull_request.rb

script/circleci/create_pull_request.rb を作成。

# frozen_string_literal: true
require 'octokit'

client = Octokit::Client.new(access_token: 'GitHub Personal access tokens')
client.create_pull_request(
  'repo-team/repo-rails',
  'master',
  ENV['BRANCH'],
  'Bundle update',
  ''
)

これで PR が作成される。

ここまでで bundle exec rake bundle_update_pull_request:check で Bundle update の PR が自動で作成されるようになった。

6. Heroku Scheduler で定期実行

ここでは Heroku を使っているけど、定期実行できればなんでも良い。

f:id:star__hoshi:20171126030819p:plain

動かしてみる

こんな感じで GitHub に毎日 PR がくるようになります。

f:id:star__hoshi:20171126031005p:plain

これを人間が目視で確認して問題なければマージしましょう。

しかし、目視といっても version が変わったことしかわからないので、これを実際に運用するにはテストがしっかりと書かれていて、そのテストが壊れてないことを確認したらマージするようにしないとダメですね。