ジンジャー研究室

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

依存の多い npm のパッケージをあぶり出す

直接依存しているパッケージが間接的に依存しているパッケージ数を知りたい。 npm ls でそういうオプションがありそうだけどないような?

仕方がないのでスクリプト書いた。

const cp = require("child_process");
cp.exec("npm ls", (e, out, err) => {
  const results = [];
  out.split("\n").forEach(line => {
    if (line.charAt(0) === "├" || line.charAt(0) === "└") {
      const splitted = line.split("@");
      splitted.pop();
      const name = splitted
        .join("@")
        .split(" ")
        .pop();
      const p = [0, name];
      results.unshift(p);
    } else if (results.length) {
      results[0][0]++;
    }
  });
  results
    .sort((a, b) => b[0] - a[0])
    .forEach(([count, name]) => console.log(`${count}\t${name}`));
});
15   json-schema-deref-sync
12  better-ajv-errors
11  ts-node
6   chalk
5   ajv
4   axios
2   xregexp
1   @types/chalk
0   typescript
0   openapi3-ts
0   @types/xregexp
0   @types/node

本当はここから気になった箇所を GUI で掘っていけると便利。

画像の差分を見つけるツールを作った

f:id:jinjor:20180907111251p:plain

作ったのは大分前なんだけど、想定するユースケースで実際に使えそうだと確認できたので。

作ったもの

github.com

動機

  • デザイナーから新しいカンプをもらった時にどこが変わったのか分かりにくかった
  • 作った機能をレビューしらもらう時にスクショのどこが変わったのか分かりにくかった

微妙なところ

完成度はぶっちゃけ高くないというか、自分の用途のために使う MVP 的なやつなので最低限しかできない。 具体的には、

  • 遅い
  • PNG のみ
  • 上下にずれると全部変わったことになる

遅いのはアルゴリズムが愚直なのもあるけど、せめてファイル変わってない時は checksum 取るくらいの対策はしたい。 最後のは改善したかったけど蓋を開けたらそういう修正はほとんどなかったというか、あってもあまり問題にならなかったので放置。

ところで画像差分検知と言えばもっと有名なのがあるので、ちゃんとしたやつを使いたい人はこっちを使ってね!

github.com

こっちも試したけどリグレッション検知が目的っぽいので、ちょっと UI が合わないという些細な点が気になってしまった。

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 化したいんだけど、要求のバリエーションが無限にありすぎて個人ではとてもメンテできなそう。「作ったよ」と言いたかったが、しんどいのが目に見えてるから仕方なくブログで方法だけ紹介することにした。誰かすごいひと、作って!