ジンジャー研究室

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

自動テストの「開拓」と「慣れ」

自動テストを書くべきだということに異を唱える人は居ないと思うが、実際の現場ではあれこれと理由をつけて省略されがちだ。あれこれと言っても理由はひっくるめると一つで「コスト」だ。そう、テストを書くのはコストが高い。本当だろうか。

コストが高いとは言いつつ、何故かテスティング・トロフィーの両端はそこそこ頑張っていたりする。型検査とかリントは当たり前のように入れている現場が多いし、一気通貫でテストしたいときは Playwright で E2E テストを書く。あとは申し訳程度にユーティリティ関数にユニットテストが書かれている。しかし、最も重要とされるトロフィーの真ん中あたりはなかなかテストが増えなかったりする。

なぜそこにテストを書かないのか。仕事なので「コストが高いから」と答えるが、「腰が重いから」というのが本音である。じゃあ何故そんなに腰が重いのかというと、個人的な感覚では「どう書けば良いのかパッとイメージが出来ないから」というのが大きい。丁度いい粒度の美味しいテストは、モックなどを駆使しないとテストが出来ず、クリエイティブな思考を要求されることが多い。もちろん、大抵のテストライブラリはモックの仕組みを備えているので簡単なものならすぐに書けるのだが、コードベースの規模が大きくなるにつれて一筋縄ではいかなくなってくる。きちんと吟味する前から漠然と「面倒そう」と想像して手を止めてしまうのだ。

腰が重い一つの理由は「先人がいないから」である。既に誰かが同じようなことをするテストを書いていればそれを真似することが出来る。最悪コピペして変数名などを書き換えれば終わる。しかし、過去に誰も似たようなコードを書いていなければどういう書き方をすればテストできるのか分からない、まさにフロンティアである。しかし、そこで立ちすくまずに新たな領域を「開拓」していきたい。一度そこで苦労して書いておけば、次に同じようなテストが必要なときはそれを参考にすればいいし、他のメンバーも真似してすぐに書き始めることが出来る。

腰が重いもう一つの理由は「慣れていないから」である。当たり前だが書けば書くほど慣れて速く書けるようになる。そして書けたことが自信につながって、次の時も「きっとすぐに書けるだろう」と楽な気持ちでテスト実装に入ることが出来る。思い返せば、最初に自動テストを学んだときは「1+1=2」すら大仕事だった。「足し算のサンプルは動いたが...実際の関数をこれでどうテストする?」という気持ちになったりもした。しかしそこは既にクリアしたからユーティリティ関数などはしっかりテストが書けているのだ。

十分に「開拓」された環境で「慣れ」た人が書けば、テストを書くコストはそこまで高くない。機能を実装したついでに同じブランチにテストを含めてマージできる。そこで書いたことによってまた慣れるので次はもっと書くのが速くなるという好循環も生まれる。大げさにしてはいけない。「テスト増量プロジェクト」とかではなく、日常の「当たり前」の作業としてシュッとやるのがクールだ。2人の開発者がいて、片方の人が機能を実装する間に、もう片方の人は同じ時間でテストもセットで実装していた、ということは良くある。言うまでもなく後者を目指したいし、そういう人が一人でも増えるように開拓を続けていきたい。

設計を考える時のメモ

お仕事で Design Doc を書く前に色々整理するのだが、その作業をシンプルなフォーマットで効率化してみた。 やることは本当に簡単で、箇条書きに色をつけるだけ。

  • 赤:誰かに質問や相談をしないといけないところ
  • 緑:他の人のボールで待ち状態になっているところ
  • 青:自分で調べるところ
  • 黒:その他

設計を考える時、最初から全てのコードが頭の中に入っているわけではないので基本的には調査から入る。 その際、やるべきこと、分かったこと、分からないこと、気になったこと、不安なこと、全てを片っ端からメモっていく。 そうすると巨大な箇条書きのリストが出来上がるので、そこに上記の要領で色をつけていく。

まず「誰かに質問や相談をしないといけないところ」を赤文字にする。

  • こういう場合はどういう挙動になるのが正解?
  • ここはもっと良いデザインがあるんじゃないか
  • 既存仕様がどうなってるのか分からん

ここは一人で考えても時間が勿体ないので速攻で関係者に質問・相談しにいく。回答が得られたらメモって黒文字にする。 「うーん、それはすぐに分からないので調べます」とか「ちょっと明日まで考えさせてください」などと言われたら、緑の文字(他の人のボールで待ち状態になっているところ)にして回答を待つ。

次に「自分で調べるところ」は青文字にしておく。

  • この機能はどこに実装されてる?
  • 影響範囲はどこまでなのか

緑文字の回答を待っている間にやると効率的。結論が出たら黒文字にする。不明点が出たら赤文字にする。

これを繰り返していくとそのうち全て黒文字になるので、後は他の人にも読めるように書いて仕上げるだけ。書いている間にも疑問点は出てくるのが、そうしたらまたメモを追加して同じようにする。

これまでも箇条書きで整理はしていたのだが、色をつけたら途端に取るべきアクションが明確になった感じがする。 まだ2回しか試していないが、今のところ良い感じ。

優先順位が口癖になる危機感

開発サイクルの終盤に近づくと「今回は優先順位の高いここまでを実装して、残りは優先順位が低いのでまたの機会にしましょう」という話になりがちだ。自分もこれまで何度もそうしてきたし、その場の判断としては正しい。が、このやり方に味をしめて常にこの調子で進めて、なんとなく上手く仕事をこなしている気になってしまうことには危機感がある。

以下、普段考えていることを自戒を込めてメモしておく。(なお、筆者の経験は toB ・Web 系・自社開発が中心なので読者の置かれている状況とは一致しないかもしれない)

優先度が低いタスクに着手する機会が一生訪れない

仮にあるタスクの優先度を下げたとする。バックログを眺めるとそのタスクに着手できそうなのは3ヶ月後だ。そして3ヶ月後、やっとそのタスクに着手できるかというと、そんなことは決してない。3ヶ月の間にそれよりも優先度の高いタスクが積まれているからだ。タスクを消化する速度とタスクが積まれる速度を比較した時、「消化する速度=積まれる速度」だとしたら、「3ヶ月後に着手可能」タスクは、3ヶ月経ってもやはり「3ヶ月後に着手可能」のままだ。さらに「消化する速度<積まれる速度」の場合は、時間が経てば経つほど4ヶ月後、5ヶ月後、と着手予定日がどんどん延びていく。そういうわけで、一度優先順位を下げたら最後、なんらかの力学が働かない限りそのタスクに着手する機会は一生訪れない。

常に及第点の品質

すると、どうなるか。「今は 60 点だけど後で 90 点にしよう」と思っていた機能の品質は、その先もずっと 60 点のままだ。その機能を 90 点にする前に次の機能開発が始まる。そして、次の機能開発もやはり 60 点で終了する。このようにして常に及第点を取り続けてしまう。しかし、ここで期限を延長して 90 点を取るまで開発するという判断はできない。この機能を 60 点から 90 点にするよりも、次の機能を 0 点から 60 点にする方が優先順位が高いからだ。

表に出てこない UX の悪さ

ここで犠牲になるのが UX で、「とりあえず必要な機能を満たしているから大丈夫」という判断で使いにくい状態で機能がリリースされる。特に toB だと「業務が回る」ことが最優先され、運用者・決済者を満足させることに比べて利用者の体験は後回しにされがちだ。また、中途半端にスクラムなどを齧っていると(自戒)「最低限欲しいものから順に小出しにリリースしよう」という思考になるので危険だ。小出しにリリースしたいのは検証サイクルを早くするためであって、最低限で許してもらうためではないし、きちんと検証していなければ意味がない。しかし UX が悪いことによる影響を測るのはとても難しい。「ネガティブフィードバックがあったら考える」で運よくフィードバックをもらえることもあるが、多くは不便が顕在化して分かりやすい部分であって、残りは言語化されず深層意識の中に不満が蓄積されていく。ユーザーは「なんとなく使いづらい」と感じているが、それを直接フィードバックをすることなく利用をやめてしまい、「なんだか分からないけど定着率が悪いですねぇ」ということになる。A/B テストという方法はあるが、それが行われるのはユーザー体験にフォーカスした時のみで、優先順位の都合でユーザー体験を妥協した時ではない。

筋の悪い設計

内部品質も犠牲になる。新たに追加しようとしている機能が既存の設計に上手く乗らない場合、根本から考えて作るのはコストがかかるので、ちょっと誤魔化して筋の悪い設計で作ろうという判断になりがちだ。いわゆる技術的負債である。もちろんそうせざるを得ない場合もあるが、単に怠慢の場合も多い。隣のチームとコミュニケーションを取るのはコストがかかるし、なるべく面倒の少ない方法を取りたい。特に、当初開発コストを小さく見積もっていたが着手したらコストが大きいとわかった場合、とても焦る。「1日で終わると思っていたけど、ちゃんとした設計で作ると1週間だ」となった時に「今はちょっと誤魔化して実装して、後で設計を見直しましょう」となる。そして負債を返済する機会はなかなか訪れない。

強い人は最初から 90 点を取る

しかし、ちょっと賢い人々が「優先順位」を振りかざして「今は最低限の機能でリリースしましょう」「計測してから考えましょう」と色々こねくり回している間に、強々な人が最初から 90 点の品質で仕上げてくることを忘れてはいけない。並の人が作る初期品質は 60 点なので 90 点まで上げるのにとても時間がかかってしまう(ので残りはやらないという判断になる)が、最初から 90 点ならコストは問題にならない。そうすると、同じ時間で 60 点を取り続ける人と 90 点を取り続ける人がいて、それがそのままプロダクトの品質になる。「あの人は時間がない中で沢山レビューで指摘してきて面倒だな...」となった時に、それは自分が 60 点を取るからではないのかというのは自問した方が良い。エンジニアの話をしているが、デザイナーや PdM にも当てはまると思う。並の人が「やってみないと分からない」と言っているうちに、センス抜群の人がやらずに答えを出しているのではないかと思うことがある。

強くありたい

その場その場で「優先順位」をもとに判断するのはもちろん正しい。上手くマネジメントすれば後回しにしたものに着手する機会が生まれるかもしれない。が、そもそも品質の低いものをどうやってリリースするかに頭を捻られなくて済むだけの圧倒的な実力を身につけたいものである。

QA と出会う

現職はスタートアップには珍しく、しっかりした QA 組織がある。日々一緒に仕事をしていて色々と発見があるので、今思うことをメモしておく。

前提として、自分は今までちゃんとした QA の人と仕事をしたことがなかった。前職はアルバイトの人に QA 相当の仕事をやってもらっていたが別に専門性があるわけではなかった。前前職には QA 組織が存在していたが、自分は技術基盤的な組織に所属していてプロダクトを作っていたわけではなかったので、関わりは全くなかった。 そうなると、そういう職種・組織が存在するということを本で読んで知ってはいたが実際どのように連携して仕事をしているのか分からない。そういう人々と関わってみたいというのが転職動機の一つでもある(ちなみに QA と同様に PdM という職種とも関わりがなかったので興味があった)。 そして9ヶ月ほど経った今になってみると「よく今まで QA なしで開発してたな」と思う。

(ところで QA ってロールを指したりプロセスを指したりでややこしいが、この記事ではロールを指している)

継続的なテスト活動

いま一緒に仕事をさせてもらっている QA は出来上がった成果物に対してテストを実施するだけでなく、企画段階から関わって仕様の明確化を手伝い、設計ドキュメントを読んで細かい仕様を質問し、テスト計画書を開発者にレビューしてもらい、そして最後にテストを実施しながら逐次フィードバックするという一連の活動をしている。このように、なるべく前工程から活動を開始することを「シフトレフト」と呼ぶようだ(恥ずかしながらこの言葉を知ったのは最近のこと)。また、テスト設計・実施だけではなくプロセス全体に関わって品質向上に貢献することを「テスト活動」と界隈では呼んでいるようだ。

テスト技法

QA がスプレッドシートにテスト項目をずらっと並べてチェックしているのを眺めていたのだが、あれは「ディシジョンテーブル(DT)」と呼ばれる歴とした技法であることを後から知った。同じエンジニアなのにテストの話をする時に今まで全然そういうワードが出てきたことがなかったなと思った。面白そうだったので、有名そうな「はじめて学ぶソフトウェアのテスト技法」を読んだ後、おすすめで出てきた「ソフトウェアテスト技法練習帳 ~知識を経験に変える40問~」を一通り解いてみた。結構気づきが多くてユニットテストや E2E テストにも応用できそう。

品質保証という観点

E2E テストは QA と開発者が同時に関わっているのだが、テストケースの見方が大分違うなという感想を得た。一言で言うと、我々開発者が適当すぎた(笑)。開発者はユニットテストについて語る時「不安なところにテストを書いて自信をつけよう」などと言うが、「不安とか自信とかなんなの」感は正直ある。ユニットテストの延長で E2E テストも「まーこんなもんだろ」みたいに感覚で書くので、多分 QA としては困惑していたと思う。結局「そのテストでどこまで品質を保証できていますか?」に答えるためには、テストケースも整然と管理されている必要がある。

コミュニティ

QA 界隈にも WACATE とか JaSST のようなイベントが頻繁あり、コミュニティが形成されているようだ。ホント全然知らなかった。観測範囲では、個別の技法を突き詰める話よりは QA が組織にどう関わるかみたいな話が多い印象。まあ重要だからそうなるの分かる。

いま考えていること

そもそもなぜ今 QA 活動に興味があるかというと、最近設計ドキュメントの粗を QA に指摘されまくって凹んだからである。「それちゃんと考えればケースが漏れてること気づいただろう」と。つまり QA がテスト設計を始める前に開発者が設計段階でミスに気づければ、その分だけ戻りが少なくなりシフトレフトに貢献できるわけ。でも QA が旗を振って開発者にそういう活動をしてもらうようにお願いするのってかなり難しい気がするので、じゃあもう自分が開発者として直接協力するのが一番早いでしょうと。痒いところにも手が届くので。 そして、前回の反省からの TRY として直近の機能では設計時に PdM・デザイナーと仕様をとことん議論してみた。単に意識を変えただけだが、おかげで今度は QA に質問攻めにあって泣きながら書き直すことは無さそうだ(多分)。

近況:転職して半年

7月から新しい職場で働いているので、そろそろ半年。整理のために近況を綴ってみる。以下は個人的な話で会社の紹介とかはしないので、気になった人はホームページを見てほしい。

kwork.studio

前職に引き続き、職種としてはフロントエンドエンジニアという位置付け。

業務として書く React

前職では業務で Elm を書いていたので React は小さなプロジェクトや趣味で触っていた程度。なので大規模アプリケーションで必要となるような React の知見は当然なく、 Suspense とかもちょっと聞いたことある程度でそもそも何なのかよく分かっていなかった。あとは Next.js とか Storybook とか Jest とか MSW とか Recoil とか TanstackQuery みたいなフレームワーク・ライブラリも未経験のものが多く、何からキャッチアップすればいいのやら途方に暮れた。が、流石に半年も触っていたのでだいぶ分かってきた。

それまで触っていた Elm はシンプルで綺麗な唯一の方法を提供する一方で、禁欲的というか「そんな無理して複雑な事をするならボイラープレートを書いてた方がマシでしょ」みたいなところがある。React (というかフロントエンドは大体そう)は真逆で、大勢がとにかく物凄いエネルギーとコストを投じて理想を追求していく。自分はミニマリスト気質なのもあって前者で満足していたのだが、正直ブルーオーシャンすぎて5年もするとやることが無くなってしまった(それは良いことでもある)。現職は React をバリバリやる感じなので正直ついていけるのかという不安はあったものの、他にもいろんな要因が重なり「もう一度フロントエンドやり直してみるか〜」という気持ちになった。

そういうわけで、それまで横目でウォッチしていた数年分のフロントエンド技術を一気にキャッチアップすることとなった。心配していたのは、ずっと Elm をやっていたせいで React のやり方に拒否反応が出て「Elm ではこんな風に書くんだけどなぁ」みたいな事をつい口走って煙たがられないか、というところだったが、蓋を開けたら全然そんなことはなくて安心している。アンラーンは特に意識していたので順調に出来たし、むしろいろんな新しい書き方を覚えて発想の幅が広がった。転職前の思惑通りとにかく学びが多い。

フロントエンドガチ勢の集い

そもそも転職のきっかけは現職のフロントエンドエンジニアからお誘いを受けたこと。その方は界隈でも有名な方だということは知っていたのだが、入社して一緒に仕事をしてみるとやっぱり凄い。フロントエンドガチ勢は CSS の手札がとにかく多い。多い時は知らない文法を4、5個見つける日があったほど。しかも同等の実力者が何人もいたりする。

前職ではフロントエンドが実質的に自分一人のような状態だったので、設計をゼロから好き勝手に出来た一方で他の人から学ぶことも難しかった。そうすると、自分の発想の幅の中で問題を解決するのだが、なかなか手札が増えていかない。70 点が求められる仕事で 80 点の解決策を思いついたらそこで終わってしまい、90 点以上の解決策は一生知ることができない。

人によって得意分野と不得意分野があってパラメータが上手く凸凹になっているのも面白い。ある人は CSS が得意、別の人はライブラリにめちゃ詳しい、Figma を使いこなす人もいればアクセシビリティに強いこだわりを持っている人もいる。なのでお互いに学び合えるし、それらが集合知になっていくのが良い。

職種の多い開発組織

フロントエンド内でも多種多様なスキルを持った人が居るのだが、開発組織全体でも同じことが言える。デザイナー・PdM が多数在籍するほか、スタートアップには珍しく QA 組織(内製)がある。あとセキュリティとかイネーブルメントをやるグループがあったり。プロダクトの開発者はフロントエンドとバックエンドが明確に分かれている。もちろんフルスタックなスキルを持ったメンバーも居るが、皆がそうであるよりはそれぞれが専門性を活かした方が良いという思想が根底にある。

前職ではエンジニアのレベルが総じて高かったが、職種のバリエーションは少なかった。なので、本などで「世の中にはそういう職種(例えば PdM とかデザイナーとか QA とか...ただし正社員で)が存在するらしい」ということは知っていたが実際に見たことはなかったので、そういう人々のいる環境に興味があった。自分はどっちかというと、誰か器用な人が片手間にやったり大勢がたまに集まって何かするよりも、ある専門領域をガッツリ見る人が居た方が上手く回るだろうと信じているので、その辺の思想ともマッチした。

というわけで、色んな人が居て面白い。ただし、そのぶん開発プロセスを整えるのは難しい。それはもう本当に。

最近の興味

最近フロントエンドの中で担当分野を決める動きがあったので「テスト(ユニット, 結合, E2E)」の担当になってみた。理由としては、相対的に手薄かつ他にやりたい人が居なかったから。設計とか UI コンポーネントとかも興味なくはないけど、他にやりたい人・得意な人が沢山いるからいいやという感じ。テストは書き慣れていないとどんどん腰が重くなるので、書くのが面倒そうな場所を開拓してハードルを下げていきたい。

Vitest の Browser Mode (experimental) でファイル読み込みのテストを書く

趣味でブラウザ上に画像や音声を読み込んで作業する React アプリを作っているのだが、 Vitest + Testing Library でテストをしようと思ったらファイル読み込み部分でつまづいた。Node.js 上でブラウザ環境をシミュレートしている部分がそのままでは上手く動かないので、 polyfill を入れたり沢山モックを差し込んだりするとなんとか動く。が、色々弄りすぎて本当にテスト出来ているのか怪しいし、やはりリアルなデータでテストしたい。

で、リアルなブラウザ環境でテスト出来ないかなと調べていたところ、2つの候補が挙がった。

両方とも experimental 。前者は Vitest をそのままブラウザ上で実行するというもので、後者は Playwright のテストをコンポーネント単位で行えるようにするもの。どちらも一長一短だが、今回はアサーションもブラウザ上で行うのが都合が良かったので前者の Vitest Browser Mode を採用した。

導入

導入は簡単。vite.config.ts の testbrowser の設定を追加するだけ。デフォルトでは webdriverio が使われるのだが、馴染みのある playwright を使うことにする。

export default defineConfig({
  ...
  test: {
    ...
    browser: {
      enabled: true,
      provider: "playwright",
      name: "chromium",
      headless: true,
    },
  },
});

このように設定を変えてテストを実行すると、あれこれライブラリが足りないと親切なプロンプトが出るので従っていくと色々インストールしてくれる。(CI には npx playwright install を追加する必要がある)

テストが再び動くまで

上記の手順でとりあえずテストを起動できるようにはなるのだが、そのままでは正常に動かなかった。 @vitejs/plugin-react can't detect preamble というエラーに遭遇して途方に暮れていたところ、Vitest のコントリビュータが書いている example を発見。この example は公式ドキュメントにマージされていないので、 2023/9 現在この fork されたブランチでしか見ることができない。惜しい。

まず、 setupFiles におまじないを入れると上記エラーが消える。

// 引用: https://github.com/vitest-dev/vitest/blob/userquin/feat-isolate-browser-tests/examples/react-testing-lib-browser/src/test/setup.ts
import "@testing-library/jest-dom";

if (typeof window !== "undefined") {
  // @ts-expect-error hack the react preamble
  window.__vite_plugin_react_preamble_installed__ = true;
}

あとは、ブラウザ環境なので本物の DOM の上にマウントするようにテストコードを書き換える必要がある。

// 引用: https://github.com/vitest-dev/vitest/blob/userquin/feat-isolate-browser-tests/examples/react-testing-lib-browser/src/utils/test-utils.tsx
export async function createContainer(id: string, node: ReactNode) {
  const container = document.createElement('div')
  container.setAttribute('id', id)
  document.body.appendChild(container)
  const root = createRoot(container)
  root.render(node)

  await nextTick()

  return root
}

testing-library を使っている場合、 root の代わりに within(root) を返しておくと screen と同じように使えたりする。

ところで、上のコードの通りにすると複数の root からそれぞれ React アプリが起動するため、同じグローバルオブジェクトを参照していたりすると問題になることがある。自分のケースでは jotai の Provider で <App> を囲むなどして問題を回避することができた。

他に細かい部分では timers/promisessetTimeout が応答しなくなったりしたのでブラウザ標準の setTimeout で書き直したりした。

が、問題としてはそのくらいで、テストの本質的な部分に関してはほぼノータッチで移行することができた。 polyfill にも別れを告げることができた。

実ファイルを読み込むまで

ブラウザ環境で動かすため、テストに使うファイルを fs モジュールで入手することが出来なくなってしまった。そこで、代わりに Vite の loader を使って直接 import した。Vite では import foo from "foo.bar?raw" のように書くとファイルをテキスト形式で読み込むことが出来るが、バイナリは対応していないようだったので、自前で用意した。

const arrayBufferLoader = () => ({
  name: "arraybuffer-loader",
  transform(_code: any, id: string) {
    const [path, query] = id.split("?");
    if (query != "buffer") {
      return null;
    }
    const hex = fs.readFileSync(path, "hex");
    return `
    const hex = "${hex}";
    const arrayBuffer = new Uint8Array(hex.match(/../g).map(h => parseInt(h, 16))).buffer;
    export default arrayBuffer;
    `;
  },
});

export default defineConfig({
  plugins: [arrayBufferLoader(), ... ],
  ...
});

参考: javascript - Import raw image data into a script with vite - Stack Overflow

これで import foo from "foo.bar?buffer" のようにして ArrayBuffer を読み込むことが出来るようになった。ただしこのままだと foo の型が分からないと TS のコンパイラに文句を言われるので、vite-env.d.ts に次のような宣言を加える。

declare module "*?buffer" {
  const src: ArrayBuffer;
  export default src;
}

これで @ts-ignore とか as とか使わなくても ArrayBuffer として扱われる。嬉しい。

その他、モックの制約など

今回は最終的に使わなくなったのだが、vi.mock が Browser Mode では動かないと書かれている。その Issue は未だ open だが、vi.hoisted が実装されたことにより named export に対して spyOn できるようになったとのこと。

it's now possible to use vi.spyOn on an ES module named export in browser environment

実際やってみたら確かに問題なく動いていそうだった。

実際の移行の様子

github.com

感想

  • ファイル読み込みのテストでモックまみれになっていた部分がごっそりなくなってスッキリした
  • テストの本質的な部分に関してはほぼ書き換えずに移行できた
  • Playwright を起動する時間だけ立ち上がりが遅いが、個人的には全く気にならないレベルだった

ブラウザ上のテスト実行、流行るといいなぁ。

Deno Deploy で WebAuthn を使ったサイトを作ってみた

作ったもの

Kaleidoshare という、オンライン万華鏡を作って共有できるサービスを作ってみた!

kaleidoshare.deno.dev

コードはここ。

github.com

作品は Twitter で公開できる。

まだまだ粗い部分が多いのだが、そもそもの目的が技術検証なのでまあこのくらいの完成度で良いでしょうということで(メンテするのがめんどくさい)。でも面白いと思ったら試してみてね。

動機

  • Deno Deploy は Cloudflare Workers のようにエッジに簡単にデプロイできるサービスの中でもパフォーマンスで群を抜いているらしく、以前から気になっていた。最近 Deno KV という分散ストレージが使えるようになったので試してみたい。
  • せっかくなので WebAuthn によるパスワードレス認証も試してみたい。最近はプラットフォームのパスキー対応が進んでいるのでデバイスを跨げるはず。
  • ついでに最近のフロントエンドのツールを色々キャチアップしたい。

この辺を満たす適当なお題を考えたところ、万華鏡に行き着いた。

サインアップ・ログイン体験

指紋認証でパスワード不要。最高。

サインアップ

ログイン

この例ではデバイスに保存しているけど、パスキーで登録すれば別のデバイスでもログインできる。

感想など

  • 開発時のコードがそのまま Deno Deploy 上でも動いて素晴らしい。サーバーレス環境で動くようにコアレベルで抽象化されていて、例えるなら express で作ったアプリが Lambda 上で動くような感じ。KV も LocalStorage くらいの手軽さで使えて面倒なセットアップが不要。
  • Deno 自体には慣れる必要がある。deno.json に import map を書く方法とか、エディタのプラグインの使い方とか(再起動・ライブラリのキャッシュが必要)。Node.js 互換は日が浅いので制限があり、例えば esm.sh を使うなどして回避する必要がある。最初は Vite も Deno で実行していたがバグがあったので諦めて Node.js に切り替えた。今後に期待。
  • Deno KV は value のサイズが 64KB に制限されており、作品ごとの OGP 画像がそのままでは保存できなかった。仕方がないので image uri を分割して保存して、取得時に結合して復元するということをした。あまり大きなデータの保存は想定されていないのかもしれない。
  • WebAuthn はクライアント側が簡単なので油断していたらサーバー側がしんどすぎたのでライブラリに頼った。とはいえフローは簡単で、サインアップ・ログイン共に「サーバーにリクエスト -> サーバーが生成した情報をブラウザに渡す -> ユーザーが認証ステップを踏む -> 結果をサーバーに返す -> サーバーで検証して情報を保存」をすればいい。
  • Playwright で WebAuthn を動作させるには Chromium で virtual authenticator を使う。ただしこの方法ではログイン時にユーザを指定しないと動作しなかったため、テスト時だけはサインアップの時と同じユーザー名をサーバーに送信するようにした。
  • まだまだベストプラクティスが分からない。パスキーはデバイスを跨げるがプラットフォーム(Apple, Google, Microsoft)は跨げない。一応それを考慮して複数のクレデンシャルを登録できるようにしてはみたが、そんな罠を知っているユーザーは稀なので不慮の事故が避けられない。復帰用にメールアドレスなどを登録した方が良いかも。

(Appendix) 要素技術一覧

想像以上に多岐に渡る技術をキャッチアップ出来て面白かった(本当は Deno Deploy や WebAuthn 以外にも色々工夫した部分を語りたいのだが、流石に話題がとっ散らかるので泣く泣く削った)。