ソフトウェアのテストと、それを簡単に行えるようにすることは、ここ数年私の関心事です。この関心は、以前よりもフロントエンド アプリケーションに重点が置かれています。具体的には、reactjs アプリケーションです。数か月前から、5 ~ 10 年前の reactjs コードを詳しく調べており、そこから洞察と課題が得られています。
私は個人的に数年前から reactjs コードを開発しています。私が共有した最初のオープンソース プロジェクトの 1 つはtestableです。これは 2020 年頃にリリースされ、それ以来、私は学習パスの一部を reactjs に費やしてきました。
最近、私はjson-toolやtext-toolなど、テスト可能性の部分に焦点を当てた他のオープンソース プロジェクトに取り組んでいます。どちらのアプリケーションもオープンソースであり、Snapcraft にデプロイされています。それらに加えて、 reactjs-playgroundというリポジトリで頻繁に実験を行っています。そこは、reactjs の機能を試し、学習に時間を費やす場所です。クローズドソース プロジェクトとオープンソース プロジェクトで長年培った経験により、一般的な落とし穴と利点を特定できる基礎ができました。
reactjs パターンとツールを利用する開発者は、ライブラリが提供するビルディング ブロックが理解しやすく、簡単に構成できることに気付きます。コンポーネントの最も抽象的な概念は、ユーザー インターフェイスをすばやく構成するために使用できます。ただし、コンポーネントの階層構造に関しては、間違いの原因になることもあります。システムの分解は、長年にわたって研究されてきたテーマです。
ソフトウェアにおける抽象化の統一性の大きさも議論の対象となっています。メソッドとクラスの行数は少なくすべきだと主張する人もいれば、より大きく、よく構造化された部分を好む人もいます。規模に関係なく、あらゆるソフトウェア プロジェクトと同様に、私は、より多くのコンテキストを提供し、コードを読む際の認知負荷の摩擦が少ないサイズを優先するつもりです。
偶然かどうかはわかりませんが、私の経験では、作業中のコードとビジネス コンテキストの境界が適切に設定されている場合に、これが可能になると思います。そのための魔法の数字はまだ見つかっていませんが、何をする必要があるかを理解するためにファイル間でジャンプする必要がある回数が、私の基準になりました。
ジャンプの数が増えるほど、コンテキストと情報を頭の中で保持する必要が増し、コード ベースを維持しながらこれを維持することが難しくなります。これらのコンポーネントを分解することは困難ですが、コード ベースをより適切に維持するために考慮する必要があります。
プロフェッショナルな開発では、グローバル状態は基本的な要件です。Reactjs アプリケーションでも同じです。グローバル状態は、アプリケーションのユーザーが簡単に見つけることができます。何かを購入して商品をカートに追加すると、追加したアイテムの数を確認できます。これがグローバル状態です。
reactjs アプリケーションでは、かつては広く使用されていたグローバル状態管理パッケージ redux の使用は減少し、アプリケーションの境界付近のより小さなコンテキストが優先されるようになりました。ただし、コミュニティではグローバル状態管理に redux を使用する必要性についてさまざまな意見が共有されており、このテーマに関するブログがいくつか作成されました。
コミュニティからの反発にもかかわらず、 npm トレンドによると、react-redux (redux を reactjs コンポーネントにバインドするために使用されるライブラリ) の採用は過去 5 年間で増加しています。2025 年 2 月 1 日現在、 npm に表示される redux のダウンロード数は 6,752,764 です。
これに代わるものは何かと疑問に思っている場合、答えは reactjs コンテキストとクエリ付きフックです。
redux パッケージに依存するアプリケーションの場合、それを回避すること自体が課題です。グローバル状態は、アプリケーションのテスト可能性を低下させる依存関係の 1 つです。私の経験では、必要なグローバル コンテキストには、ドメイン知識の複雑さが増すことがよくあります。特定のコンポーネントまたはアプリケーションの一部だけをテストしたい場合でも、この部分に必要なグローバル依存関係を共有しないとテストできません。
この記事の執筆時点では、エンタープライズ アプリケーションに reactjs を採用することは、保守性の観点から正しい決定でした (Facebook がライブラリ ライセンス モデルを変更した重要な時期にもかかわらず)。ReactJs は、使用されなくなった API に対して下位互換性を提供するため、長期的な採用が重要な利点の 1 つになります。
コンポーネントだけでは抽象概念として十分ではないかのように、コンテキストもテストに関して reactjs が提供する利点の 1 つです。このテーマについては、 reactjs コンテキスト テストに関するブログ投稿で詳しく説明しています。 reactjs コンテキストが提供するカプセル化のレベルは、テストを改良し、 reactjs コード ベースでリファクタリングを可能にする上で重要な利点の 1 つです。
コンポーネントとビジネス ロジックの分解は課題であるため、コンテキストはコードの再構築をより適切に行うためのツールです。テスト可能性の部分では、使用されるテスト ダブルの数を減らすメカニズムであるため、これは利点であり、よりテストしやすいコードにつながります。
ここまでのリストを見ると、ソース コードの構造とアプリケーションの境界の構成方法によってテストの部分が決まることがわかってきたと思います。ただし、テスト コードが実稼働コードに依存するのと同じくらい、テスト ダブルを使用して範囲を限定することができます。テストを改良するための思考プロセスは、いくつかの簡単な手順で構成されています。
2.1. テストは合格しましたか?
2.1.1 - Yes → Check if the feedback is correct 2.1.2 - No → Provide the dependency without questioning
注意:ここで説明するアプローチは、マイケル・フェザーズが「特性評価テスト」として説明したものと似ています。
レガシー コードを効果的に扱うための本。
LLM の登場により、最初のテストを記述するときや、テストなしでコード ベースを改良するときにも洞察が得られます。この戦略の各ステップは反復的であることが意図されており、TDD のさまざまなスタイルを組み合わせることもできます。提案されているアプローチは、すべてをやめて、考えられるすべてのテスト ケースとコード ベースが抱える考えられるすべての問題を改良することではなく、パズル ゲームです。テストされる各機能は、パズルに収まるピースです。
リファクタリングにも同じアプローチが推奨されます。開発者は、コードの一部を継続的にリファクタリングできる必要があります。これは別のプロジェクトではなく、それに専念することだけを目的としているわけではありません。主な目標は、コード ベースを以前よりも改善することです。反復的に。テストがまったくないコード ベースにテストを組み込むために使用できる側面がいくつかあります。
このアプローチの中心となるのは学習プロセスであり、コード ベースを学習するすべてのステップが考慮されます。
Copilot を使用すると、前のセクションで説明した最初の探索的テストを自動化できます。3 つのルールに従って、依存関係を識別し、ベースとして使用する包括的なテストのセットを作成することから始めることができます。
Testable を例に挙げてみましょう。これは 5 年以上前に reactjs で作成され、テストに Enzyme を使用するアプリケーションです。今日の標準では、テスト ライブラリとともに vitest または jest が引き継がれています。テスト ケースを testable 用に改良し、LLM を活用するには、copilot を使用して面倒な作業を軽減できます。
Testable は、さまざまな課題を通じて状態、レベル、およびユーザーの進行状況を示すゲーミフィケーション エクスペリエンスで構成されています。次の画像で説明されているコンポーネントは、ダイアログを表示し、エクスペリエンスの履歴を進むために使用されるコンポーネントです。vscode 用の Copilot を使用して、関心のある製品コードでテストを作成するように依頼しました。
質問を読み込んでコードを分析すると、回答が提供されます。
回答とともに、Copilot は、まだテスト ライブラリを使用していないことに気づき、それを使用することを提案し、その後、生成されたテスト ケースが表示されます。表示されているとおりにテスト ケースを実行すると、テストは不合格となり、赤でマークされます。
この点は、追加の設定を考慮する必要があるが、Copilot ではそれを判断できなかったことを示しているため重要です。追加の設定は次のとおりです。
このセクションで前述したフローはここでも適用されます。フィードバック ループは、これらの問題の修正を開始し、TDD サイクルが完了するまでテストを実行することです。