この記事は hacomono Advent Calendar 2024 20日目の記事です。
はじめに
こんにちは。UX部エンジニアのがーみーこと石上(@gaaamii)です。最近、With Wellness *1 というありがたい制度を活用してジムで筋トレを始めました。たくさん運動して、おいしくラーメンを食べたいと思っています。
本記事では、hacomonoの1つのプロジェクトにおいて、私がフロントエンドテストコード追加とリファクタリングを通してhacomonoのフロントエンド実装の理解を深めた体験について書きます。機能の当初の実装者でなく改修に関わっていない身でも、そこへテストコードを書いてリファクタリングをすることで、その機能や仕様、実装について詳しく知ることができました。
この記事が同じようなプロジェクトに関わっている人、あるいは既存のソースコードに後追いでテストコードを追加しなくてはならないような場面に遭遇した人に、モチベーションを少しでも与えられたら嬉しいです。
テストコード追加とリファクタリングの背景
今年に入ってから、リアーキテクチャ&イネーブルメント部と、私が所属するUX部の一部のメンバーで、あるフロントエンドプロジェクトの大規模なリファクタリングを進めています。これはライブラリのアップデートのための作業で、ライブラリの新しいバージョンに互換性のあるコードへと書き換えを行っています。
やることはリファクタリングなので、書き換えた後に既存の動作を変えていないことを確認する必要があります。テストコードがあれば自信を持って進められますが、残念ながらこのプロジェクトの開始時には、フロントエンドのテストコード、特にUIコンポーネントに関するテストコードはほとんどありませんでした。
今年の4月に Vue Testing Library を導入し、UIコンポーネントにもテストコードを書けるようになったため、このプロジェクトとしても、基本的にはテストコードを書いてから書き換えを行う、という進め方をしています。書き換えの量がなかなか多く、書き換え対象のUIコンポーネント(ページ含む)はおよそ700ファイルです。
テストコード追加とリファクタリングのステップ
既存実装へのテストコード追加とリファクタリングは、以下のようなステップで行っています。
- 仕様を理解する
- 手元で動かす
- 実装を理解する
- テストコードを書く
- リファクタリングする
- テストコードが通ることを確認する
- 手元で動かす
- (必要に応じて)検証環境に出してQAにテストを依頼
この活動を通して、リファクタリング作業をする私としては何を行なって、そこから何を得ているのかを、紹介できたらと思います。
仕様に詳しくなる
リファクタリングの準備の最初、仕様を理解するステップでは、まずお客様向けのサポートサイトで、対象機能の説明を読みます。完成している製品に関するお客様向けの説明の文書なので、これが最もわかりやすいです。機能によっては複雑なものもありますが、サポートサイトの説明もとても丁寧に書かれているため、じっくり読めば理解できることがほとんどです。歴史的経緯で残っている隠し機能のようなものも稀にあるため、そういったものは人に確認する必要があります。
どこをリファクタリングするにしてもだいたいここからスタートなので、良く言えば、この活動を続けるほどhacomonoの仕様に詳しくなっていくということです。私は昨年の10月入社で、それほど多くのプロジェクトに関わってきたわけではないので、hacomono製品についてまだまだ詳しくない領域があります。そのため、広く仕様に詳しくなれるというのは、私がリファクタリングのプロジェクトに関わってよかったと思う点の一つです。
複数の実装を読んでパターンがわかる
1つの実装を読んでリファクタをして、またもう1つの実装を読んでリファクタをして、ということを続けていると、実装のパターンもわかってきます。APIへリクエストする際の手続きの書き方、状態管理の流れ、コンポーネント整理などは、複数の実装を読むうちに慣れ親しむことができました。
あるパターンに対して、どれくらい必要なものか、変えてはいけないものなのかという肌感も、具体的な実装をいくつも見るうちに、ついてきました。それによって、今後の改善案も考えやすくなってきたと感じています。
どんな仕様をどんなデータで実現しているのかがわかる
現在私がリファクタリングに取り組んでいるプロジェクトでは、Vueのコンポーネントに対してテストを書いています。具体的には、以下のようなコードで、Vue Testing Library を使って、renderした結果に対してアサーションを書いています。
describe('何かしらの条件のとき', () => { const props = { ... } // Vueのprops const store = createStore(...) // createStoreはテスト用のユーティリティ関数。アプリケーションが持つstoreと同じ構造のオブジェクトを返す。 it('ボタンが活性化する', () => { const { getByRole } = render(MyComponent, { props, store, ... }) expect(getByRole('button', { name: '登録' }).toBeEnabled() }) })
ここで、 render
の引数にどんなpropsやstoreのデータを渡すかを考えるときに、どんな仕様をどんなデータで実現しているのかを理解することができます。
具体的な例を挙げます。以下は私が以前書いたテストコードの一部を、わかりやすいように改変したものです。独自のユーティリティを使った記述など細かいところを気にしないでいただければ、やっていることは伝わるかと思います。
describe('プラン変更手数料がかかる場合', () => { const changePlanItem = entityBuilder(ItemEntity).props({ name: '変更手数料', price: 1000 }).toTypedRaw() const oldPlan = entityBuilder(PlanEntity).props({ name: '古いプラン', change_plan_items: [changePlanItem], price: 8000 }).toTypedRaw() const changePlanContext = buildChangePlanContext({ before_target: [buildBeforeTarget({ plan: oldPlan })], changeable_target: [buildChangeableTarget()], }) const store = setupStore({ changePlanContext }) it('プラン変更手数料を表示', () => { const { getByRole } = render(ChangePlanPage, { store }) const changePlanItemSection = getByRole('region', { name: '手数料' }) expect( within(changePlanItemSection).getByText('¥1,000') ).toBeVisible() }) })
何をしているかというと、契約変更(hacomonoはフィットネスジムに多く導入されているSaaSですので、フィットネスジムの契約変更を想像してください)をするときに手数料を設定していたら、ちゃんとそれを表示しますよね、というテストです。
これを実現するときに、具体的にどういうエンティティのなんというプロパティにどういう値を入れて、それを実現しているのかというのがわかります。
これはサポートサイトを読むだけでは得られない知識であり、改修や調査に入る際には持っていると心強い知識です。
おわりに
テストコード追加やリファクタリングはお客さんに何か直接価値を届ける活動ではなく、どちらかというと地味な活動ですが、時間をかけて仕様や実装を知れるチャンスでもあります。いろんな箇所の実装を読み、hacomonoのフロントエンド実装ともだいぶ仲良くなってきた気がします。
当面はリファクタリングそのものの完遂を目指しつつ、この活動を通して蓄えた知識を、今後の設計の改善や改修に活かしていけたらと考えています。
株式会社hacomonoでは一緒に働く仲間を募集しています。
エンジニア採用サイトや採用ウィッシュリストもぜひご覧ください!
*1:月15,000円までフィットネスやヨガ、サウナなどウェルネス領域の店舗の利用料金を一部会社が補助してくれる制度です