ジンジャー研究室

長めのつぶやき。難しいことは書きません。

2つの順序キーの間のキーをいい感じに生成するライブラリを作った

RDB で ORDER BY するためのカラムを持つ時に、並び替えや挿入がうまく出来なくて困った。

f:id:jinjor:20180905182715p:plain

例えば、このテーブルで B と C の間に E を差し込みたい時に、

f:id:jinjor:20180905181617p:plain

こうなってくれると嬉しい。

作ったもの

🎉

github.com

TypeScript 用に書き直してくれてもいいのよ?

仕組み

  • キーは 0-9A-Za-z の 62 種類の文字が使える、ただし、最後の文字が 0 であってはいけない
  • 最初のキーは 1
  • 次のキーは「既存のキーの次」か「既存のキーの前」か「既存の2つのキーの間」のいずれかを指定して生成する
  • キーの左の桁を優先的にインクリメントしようとするが、無理な場合は桁を増やしてインクリメントする
  • 例:
    • between "1" "3" == "2"
    • between "1" "2" == "11"
    • between "1" "11" == "101"
    • after "1" == "2"
    • after "z" == "z1"
    • その他

技術的な話

Fuzzer を使って 10 万テストケースを自動生成して回しています。

今回は 0, 1, z などの境界値付近を重点的に攻めるために確率を操作しています

参考リンク:

Elm 0.19 の主な変更点

祝 Elm 0.19 リリース!

https://elm-lang.org/blog/small-assets-without-the-headache

1年半ウォッチしていたので覚えている範囲で書いてみる。

追記

↓ここに全部書いてあるじゃん。というか上の記事からリンクされてたし、この記事いらないじゃん。

github.com

コンパイルが速くなった

タプルで大量にパターンマッチした時に遅くなる件も改善。

--optimize でサイズの最適化

出力される JS のサイズが小さくなる。 関数単位のデッドコード除去が可能(Google Closure Library の advanced compile 相当) Debug モジュールを使用していると --optimize できないので注意。

単一コンストラクタでメモリを消費しなくなった

これは --optimize をつけた時だったかな、覚えてない。 type T = T Foo みたいにした時に T で包むのを省略する。

シングルバイナリになった

Elm Platform はもうない。 elm-make の代わりに elm make と打つ。

コマンドラインの刷新

  • --warn が消えた
  • elm init で最低限のプロジェクトを生成できるようになった
  • elm install をプロジェクトを初期化せずに elm make で全部やるようになった

elm-package.json が elm.json になった

アプリケーション用とライブラリ用で書き方が変わる。 アプリケーションの場合はバージョンが固定されて lock ファイルになる。 test-dependencies という項目ができたのでテスト用にもう一つ JSON を用意する必要がなくなった。 初期状態でインストールされるのは elm/coreelm/json の2つで、 HTML などは手動でインストールする必要がある。

elm-stuff の役割が変わった

パッケージやそのビルド生成物は ~/.elm にキャッシュされることになった。 CI とか Docker の設定で注意が必要かもしれない。

ユーザー定義の演算子が禁止された

もう作れない。

いくつかの演算子が消えた

  • ! はもうない
  • %modByremainderBy になった

トリッキーな関数が消えた

  • flip
  • curry
  • uncurry

人類には早かった。

シャドーイングが禁止された

同スコープに定義されている関数と同名のローカル変数を作ることができない。

公式リポジトリが elm-lang から elm になった。

ついでにバージョンが 1.0.0 にリセット。

Browser が登場

Html.program 系の関数がここに集約された。 Navigation, Mouse, Keyboard, Dom もここに統合された。 Navigation 相当の機能は fullscreen の時のみ使用可能。

Html.Events のデコーダーが柔軟になった

「デコードした時に特定の条件だった時に preventDefault しない」のようなことが可能になった。

DOM のイベントを同期的に実行するようになった

ブラウザのセキュリティ機能で、ユーザーインタラクションの直後じゃないとコピペなどが正しくハンドリングされない問題を解消した。

Debug モジュールの刷新

  • Debug.crash が Debug.todo になった。
  • toString が Debug モジュールに入った。

String.fromInt, String.fromFloat

toString は使わない。 型を変えた時に人知れず表示がバグっていたので嬉しい。

(,) が消えた

今は Tuple.pair

Html.Lazy が 8 引数まで拡張された

今まで3だった。

Array, Dict, Set の実装が新しくなった

速くなった。 Array は今までバグがあった。

Random の実装が新しくなった

PCG アルゴリズムを使うようになった。

Time の実装が新しくなった

より実用的になって elm/time として生まれ変わった。

Color が core から消えた

旧石器時代からあったやつ。

Regex が core から消えた

Parser を使って欲しそう。

elm-tools/parser が elm/parser に昇格

API も色々刷新された。

再帰的な関数のバグが直った

どれのことだったか忘れた。

リテラルで巨大なリストを作れないバグが直った

[1,2,3, ... 5800 ] みたいなやつ。

Module.Record.property を正しくパースするようになった

定数を使う時に苦労していたので地味に嬉しい。

エラーメッセージがさらに改善された

どれのことだったか忘れた。

Native -> Kernel

Native モジュールが Kernel モジュールに名称変更。 JS を書くのが許されているのは elm と elm-exploration の2つだけ。

ドキュメントが充実した

公式サイト・ガイド、コアライブラリの解説が増えた。

Union Type という表記が消えた

名称が紛らわしかった。今は Custom Type と書いてある。

個人的によく使う npm ライブラリを紹介してみる

偏ってます。

もっと有名なのは沢山あるけど、自分が普段よく使うのじゃないと紹介できないので。

argv

引数をパースするやつ。

chalk

色をつけるやつ。

dotenv

環境変数をファイルから読むやつ。

fs-extra

fs に欲しいけどないやつ。

watch

ファイルを監視してコマンドを実行するやつ。

nodemon

ファイルを監視してサーバーを再起動するやつ。

mocha

テスト。

prettier

フォーマッター。

puppeteer

ヘッドレス Chrome

express

サーバー。

passport

OAuth 。

typescript

TypeScript 。

elm

Elm 。

npm

Node.js 用のパッケージマネージャー。

puppeteer + express + mocha で快適 TDD している話(続編)

前回の記事の続き。

jinjor-labo.hatenablog.com

会社で開発中でオープンソース化していないテストツールがまたいくらか便利になったので進捗報告してみる。 ツールの概要は上の記事で説明しているけど、一言で言うと「mocha から puppeteer 叩いて express に届いたリクエストにアサーションをかける」ようなテストが楽に書ける。

前との差分

以下、前回より便利になったポイントを自慢していく。

APIカバレッジが取れるようになった

API が一度でも叩かれたかどうかを調べて網羅率を見る。 テストが一通り終わった後、こういうのが出る。

[ OK ] GET /foo/foo
[ OK ] PUT /foo/foo
[ OK ] GET /bar/:barId/bar
[ NG ] POST /bar/:barId/bar
covered 3/4

ページのカバレッジが取れるようになった

こちらはテストケースで訪問したページの網羅率を見る。 全体でどんなURLがあるかはサイト内をクローリングして調べる。

[ OK ] /#/
[ OK ] /#/config
[ OK ] /#/articles
[ NG ] /#/articles/(number)
covered 3/4

UI のカバレッジが取れるようになった

こちらはページ内の UI をどれだけ網羅したか。

/#/articles/(number)
[ OK ] #article-tilte-input
[ OK ] #article-body-input
[ OK ] #submit-button
[ NG ] #submenu-toggle
covered 3/4

テストケースに併せてモックデータを差し替え可能になった

今まで一枚岩のデータしか無かったのが、柔軟に適切なデータをサーバーから返してもらえるようになった。 これでログイン中のユーザーの権限を変えてみたり、ページネーションのテストために大量のデータを放り込める。

おかしな HTML 要素を警告するようになった

<a> なのに href が無いぞーだったり a11y 的によろしくないものを警告する。 まあこの辺は普通の E2E のツールにありそうな機能ではある。

記法が進化した

前回ちょっとダサかったアサーションpower-assert でいい感じになった。

    it("should send fields correctly", async () => {
      await inputText("#signup-email-address", "a@b.c");
      await inputText("#signup-password", "Passw0rd");
      await inputText("#signup-user-name", "A B");
      await $.click("#signup-submit");
      at("POST /auth/signup", results => {
        assert(results.length === 1);
        assert(results[0].body.email_address === "a@b.c");
        assert(results[0].body.password === "Passw0rd");
        assert(results[0].body.user_name === "A B");
      });
    });

TypeScript 化した

時代は TypeScript らしいですよ。

以上

応援してもらってオープンソース化と出社のモチベーションを高めたい。

puppeteer + express + mocha で快適 TDD している話

TDD という用語を使うとテストおじさんがやってきて、それはそうじゃないとか色々言い出すと思うんだけど、それが趣旨ではないので勘弁して欲しい。予防線ここまで。

Puppeteer でテスト

Puppeteer が世間的にも個人的にもブームだ。ヘッドレス Chrome を操ってクローリングしたりスクリーンショットを撮ったり色々出来る。

github.com

で、あれこれと遊んでいるうちにテストに使えるんじゃね?ということに気づいたので実践してみたら快適だったという話。ブラウザ操作してテストというのは昔から Selenium というのがあり、こちらはクロスブラウザで出来たりするんだけどまあ大掛かりでだるさを感じてしまう。メリットデメリットの比較はさておき、どうせならナウいやつを使ってみたい。よし使おう。

何をテストするか

普段から画面を見ながら開発しているので、どこに何が表示されているべきというテストはあんまりやる気が起きない。まあマイナーな画面だと知らず知らずのうちに表示されなくなってたりするんだけど、大体の画面は目で見て壊れてるのがすぐに目につくし、テストが通っていたところで見た目が整っていることは結局目で確認することになるので一向にモチベーションが上がらない。

だけど、そんな中にも「テスト書かなきゃ…」というのがあって、何かと言うと更新系 API を正しく叩けているかというやつ。画面はポチポチやってるとなんとなく動いているような気がするんだが、実は裏で大変なことになってたりする。

  • 実はデータを送信していない
  • バリデーションエラーがあるのにデータを送信している
  • 1文字入力する毎にデータを送信している
  • フォームの入力項目のうち一部しか送信していない
  • 更新前のデータを送信している
  • 別の API を叩いている

アホみたいだが、実際に上のは全部踏んだ。しかも、画面に現れないので気づくのが遅れる。 というわけで真っ先になんとかしよう。

仕組み

前提としては、サーバーサイドが JSON を返す REST API で、そのモックサーバーが Express.js (Node.js) で書かれている。これを改造して、Puppeteer で入力したデータが正しく Express に届いたかを調べられるようにする。

Express で API を記述するときは大抵 app.get('/foo/:id', (req, res) => { ... }) のように書いていくので、この get とか post にフックを仕掛けてAPI にどういうデータが届いたかを全て記録する

app = wrap(app); // 色々仕込む

app.get ...
app.post ...
...

そうすると何回かアクセスすると

state = {
  'GET /foo/:id': [ req1, req2, req3, ... ],
  'POST /foo/:id': [ req1, req2 ... ],
};

こういうオブジェクトが手に入るので、ここにアサーションをかける。幸いにして Puppeteer と Express は同じ Node.js の同一プロセス上で動かせるので、サーバー側で作られたこのオブジェクトは Puppeteer 側でそのまま手に入る。

で、あとはこれを mocha でテストできるようにする。大体次のような感じのテストになる。

describe('ArticleEdit', function() {
    beforeEach(async () => {
      await goto('/#/article/0');
    });
    it('should send title', async () => {
      await inputText('#article-title', 'foo');
      // 1 回のアクセス
      assertTimes('PATCH /articles/:articleId', 1);
      // 0 番目の body の title が 'foo'
      assertBody('PATCH /articles/:articleId', 0, ['title'], "foo");
    });
});

アサーション関数が洗練されてないのは認める。

mocha に一工夫する

mocha はまともなテストライブラリなので before とか after とかが書ける。工夫次第でどんどん快適になる。

before(全体の)

  • サーバーのセットアップ
  • Puppeteer のセットアップ

before(各ページの)

  • 目的のページにアクセスして操作可能な UI を一覧表示する (a, button, input, select とそれらの id )

beforeEach

  • サーバーの状態をリセット
  • localStorage (セッションなど)をリセット
  • 目的のページにアクセス

afterEach

after(全体の)

  • サーバーを終了
  • Puppeteer を終了

テスト駆動にする

ここまで来たら、せっかくなので画面ができる前にテストを書いてしまう。デザインカンプがあると大体どういう UI があるのかイメージしやすいので、「この URL にアクセスしてこの ID の要素を操作したらサーバー側にこれが届くだろう」が書けてしまう。これで見た目がボロボロでもとりあえず一通り期待通りの動きをするページを、画面を見ずに作れる状態になる。

副作用としてよかったことは、要素の ID がいい感じに統一されたこと。今までは必要になった時点で「そういえば id 振らなきゃ名前どうしよ」みたいな行き当たりばったりをしていたが、こうすると仕様として先にビシッと決まるので統一感が取れて良い。

まとめ

というわけで、会社で使ってて今の所まあ快適にできている(テスト対象のコードは Elm )。

できれば既存のライブラリを探しても見つからないから OSS 化したいんだけど、要求のバリエーションが無限にありすぎて個人ではとてもメンテできなそう。「作ったよ」と言いたかったが、しんどいのが目に見えてるから仕方なくブログで方法だけ紹介することにした。誰かすごいひと、作って!

CSS in Elm 方式を導入してから1年半以上たった感想

CSS in JS(Elm)したら想像以上に良かった という記事をずいぶん前に出して結構ブクマを貰えたんだけど、今は「なんだかなー」と思っているので整理する。冷静に考えると、そもそもこのエントリで書いていることのほとんどが「良かった」ではなく「悪くなかった」としか言ってない。

class が無いのでどのスタイルを直せば良いのか分からない

これが一番大きい。このビューのスタイルを直したい、と思った時に class が書いてないのでどこを直しにいけば良いのか分からない。

DevTools であれこれ試す時に同じスタイルが一気に変わらない

class でやれば同じところが変わってくれっる

DevTools からコピペできない

あれこれ試した後「これだ」と思ったらそこからコピペした後、 Elm のコードに直さないといけない。

スタイルを変えるのにコンパイルが必要

Sass とか Post CSS をすでに導入していたら同じ条件なので関係ないが、何かの都合で見た目ををサクッと変えたい時に CSS をいじって解決したくなる。

CSS が進化した

今は変数も使えるしグリッドも使えるし、ブラウザも追いついてきたので相対的に Elm でロジックを書く必要性が減った。スコープももう少ししたら Shadow DOM とか使ってどうにかなるのではないか。

関連アプリと共通化出来ない(仮説)

これは実際に困ったことではなく「そういうことが想定される」というケースなのでまだ仮説段階。例えば隣のアプリは React を使っているが見た目は統一したいとか、徐々に JS から Elm (あるいは逆)に置き換えたいといった場合に、CSS を共通基盤に使うことはあると思う。

引継ぎが厳しい(仮説)

今回は引き継いだ人は普通に出来る開発者だったのでそれほど問題なかったが、もしデザイナーとかだったら厳しかったかもしれない。

というわけで、今は「普通に CSS ファイルで良くね?」に傾いているけど、それでまた問題になったらその時はまた考える。

Promise でリトライや同時実行数の制御をするやつ作った

f:id:jinjor:20170917145732p:plain

JavaScript でバッチ実行を少しでも楽にしたいという思いで作ってみた。

www.npmjs.com github.com

まだバージョン 0.7.0 だけど、大体のことは出来るはず。

機能

デモ を触ると大体何ができるかわかると思います。

  • 実行間隔の指定
  • 同時実行数の制限
  • リトライ数の指定
  • リトライ間隔の調整
  • 失敗したリクエストの返却

動機

DynamoDB への書き込み時に流量を制御しようと色々していて、 Promise が不便だと思った。SDK でもなんか色々パラメータがあると後から知ったのだけど、まぁ Promise が便利になるのに越したことはないということで。もちろん既存のライブラリも沢山探したけど、しっくりくるのが無かった => 作ろう。

API に関しては、シリアライズ可能なデータとしてのリクエストの配列を受け取るようにして、失敗した時に情報を保存したり渡したりできるようにしている。リクエストの数はそんなに多くない想定なので、ストリームで読みつつ書き込むみたいな処理は今の所ない。

今後

やる気が持続すれば付くかもしれない機能。

  • バックオフのもう少し緻密な設定
  • タイムアウトの設定
  • ログ埋め込みの仕組み
  • 入力バリデーションエラー

それでは良い Promise ライフを!