DRYなソフトウェアテストで保守性を向上 | Reactテストで学ぶ効率的なテスト戦略

当サイトでは一部リンクに広告が含まれています

日々、開発の中でテストを書くことも多いと思います。

しかし、テストを書くことに対して、以下のような悩みを抱えていませんか?

  • テストコードが冗長になってしまう
  • テストコードの重複が多く、保守性が低い
  • 実装の変更に伴い、テストコードの修正箇所が多くなってしまう

テストコードの重複を減らすことは、テストの品質を向上させる上で重要なポイントです。

そこで本記事では、ソフトウェアテストにおけるDRY原則の重要性と、React テストでDRYを実現する方法について解説します。

本記事を読むことで、テストコードの重複を減らし、テストの品質を向上させることができるようになります!

目次

DRY原則とは

ソフトウェア開発におけるDRY原則とは、「Don’t Repeat Yourself」の略で、同じことを繰り返さないことを指します。

たとえば、同じコードを複数の場所に書く代わりに、関数やクラスとして一箇所にまとめ、必要な場所で再利用することです。

これにより、変更が必要になった際に、一箇所の修正で済み、エラーを減らしながら効率的に開発を進めることができます。

DRYの重要性

DRY原則を適用することで、ソフトウェアの保守性が飛躍的に向上します。

コード内の重複がなくなると、ある機能にバグが発見されたときの修正は一箇所だけで済むことになります。

これは、特に大規模なアプリケーションや長期間にわたるプロジェクトでその価値を発揮します。

なぜなら、コードの重複が多いと、同じ変更を何度も繰り返さなければならず、作業の効率が落ちるだけでなく、変更漏れによるバグのリスクが高まるからです。

したがって、DRY原則を尊重することは、信頼性の高いソフトウェアを作る上で不可欠な要素の一つです。

テストでDRYにできるポイント

実装において DRY が重要なのはもちろんですが、テストコードでもDRY原則を意識してみましょう。

特にテスト内で検証する前のセットアップコードの共通化、テストデータの抽象化、アサーションの再利用が重要なポイントです。

例えば、複数のテストケースで利用する初期化コードを共通のセットアップ関数にまとめることで、テストの追加や変更が容易になります。

結果的にテストコードの見通しが良くなり、バグを見つけやすくなるだけでなく、新しいテストを書く際の手間も大幅に削減されます。

ReactテストでDRYのメリットを実感する

Reactプロジェクトを作成

Reactテストを行う前に、まずはReactプロジェクトを作成しましょう。

Create React App を用い、簡単に新しいReactアプリケーションを作成します。

ターミナルを開き、以下のコマンドを実行してください。

npx create-react-app dry-test-react

次に、以下のコマンドで作成したディレクトリに移動します。

cd dry-test-react

移動したら、次のコマンドで開発サーバーを起動します。

npm start

これで、デフォルトブラウザが開き、新しく作成されたReactアプリケーションが localhost のポート3000で表示されます。

通常は http://localhost:3000 というURLでアクセスできます。

テスト対象のコンポーネントを作成

Reactテストでは、まず実際にテストを行うコンポーネントを作成することから始めます。

今回は簡単なカウンターアプリケーションを例に取り上げます。以下にApp.jsのコードを示します。

import React, { useState } from 'react';

function App() {
  const [count, setCount] = useState(0);

  return (
    <div style={{ margin: '2rem' }}>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default App;

このコンポーネントは、状態(state)としてcountを持ち、ボタンをクリックすることでカウントを増加させる機能を持っています。

http://localhost:3000 に再度アクセスすると、以下のようにシンプルなカウンターが表示されます。

「Increment」と書かれたボタンをクリックすると、Count の数字が 1ずつ増えていくものです。

カウンターコンポーネント

このコンポーネントのテストを例に、Reactのテストがどのように行われるか、またDRY原則を適用することでテストコードの管理がどれほど簡単になるかを見ていきましょう。

DRYではないテストの例

まずはデフォルトで用意されている App.test.js を書き換え、先ほどのコンポーネントをテストしてみましょう。

ただし、まずはDRYではない(冗長な / 重複した)テストコードを書いてみます。

その後、DRY原則に基づいてリファクタリングしていくことで、テストコードをより効率的に書く方法を学びます。

App.test.js のコードを以下のように書き換えてください。

import { render, screen, fireEvent } from '@testing-library/react';
import App from './App';

// カウンターの初期表示をテストする
test('初期状態でカウンターが0であること', () => {
  render(<App />);
  const countElement = screen.getByText('Count: 0');
  expect(countElement).toBeInTheDocument();
});

// カウントアップボタンの機能をテストする
test('ボタンをクリックするとカウンターが1増えること', () => {
  render(<App />);
  const incrementButton = screen.getByText('Increment');
  fireEvent.click(incrementButton);
  const countElement = screen.getByText('Count: 1');
  expect(countElement).toBeInTheDocument();
});

// カウントアップ後の表示をテストする
test('カウンターが1になったことを再確認する', () => {
  render(<App />);
  const incrementButton = screen.getByText('Increment');
  fireEvent.click(incrementButton);
  fireEvent.click(incrementButton);
  const countElement = screen.getByText('Count: 2');
  expect(countElement).toBeInTheDocument();
});

書き換えたら、npm run test コマンドをターミナルで実行し、テストが正常に実行されることを確認してください。

上記のテストコードでは、render(<App />) を各テストケースで実行してしまっています。

これは、同じセットアップ処理が繰り返され、コードの重複を引き起こしていることを意味します。

また、各テストケースでfireEvent.click()を使用してボタンクリックイベントを発火させており、これも繰り返しの一因となっています。

上記のコードは、重複を減らすためのリファクタリングが必要です。

次に、このテストをよりDRYにする方法を見ていきましょう。

DRYなテストの例

では、ボタンをクリックする処理を共通化することで、テストコードをDRYにしてみましょう。

import { render, screen, fireEvent } from '@testing-library/react';
import App from './App';

// ボタンをクリックする共通の関数
const setup = () => {
  render(<App />);
  const incrementButton = screen.getByText('Increment');
  return incrementButton;
};

// カウンターの初期表示をテストする
test('初期状態でカウンターが0であること', () => {
  setup();
  expect(screen.getByText('Count: 0')).toBeInTheDocument();
});

// カウントアップボタンの機能をテストする
test('ボタンをクリックするとカウンターが1増えること', () => {
  const incrementButton = setup();
  fireEvent.click(incrementButton);
  expect(screen.getByText('Count: 1')).toBeInTheDocument();
});

// カウントアップ後の表示をテストする
test('ボタンを2回クリックするとカウンターが2増えること', () => {
  const incrementButton = setup();
  fireEvent.click(incrementButton);
  fireEvent.click(incrementButton);
  expect(screen.getByText('Count: 2')).toBeInTheDocument();
});

上記のコードでは、setup()関数を定義し、レンダリング処理とボタンクリック処理を共通化しています。

テストコードの重複が減ったことで、各テストケースがよりシンプルになり、何をテストしているかが明確になったことがわかります。

また、コンポーネント側で変更があった場合にも、テストコードの修正箇所が少なくて済むようになりました。

たとえば、ボタンのテキストをIncrementからCount Upに変更したい場合、setup関数の中身を修正するだけで済みます。

const setup = () => {
  render(<App />);
  const incrementButton = screen.getByText('Count Up'); // ボタンのテキストを変更
  return incrementButton;
};

このように、テストコードをDRYにすることで、テストの追加や変更が容易になります。

では最後に、さらにDRYにする方法を見ていきましょう。

さらにDRYにしてみる

最初にお伝えしておくと、シンプルなカウンターアプリケーションのテストでは、先ほどのテストコードぐらいがちょうど良いかと思います。

しかしあえて、徹底的にDRYにした例を見てみましょう。

たとえば次のように、カウンターの値を検証する関数を定義するなどの工夫により、テストコードをさらにDRYにすることができます。

import { render, screen, fireEvent } from '@testing-library/react';
import App from './App';

// ボタンをクリックする関数
const clickButtonTimes = (buttonText, times) => {
  const button = screen.getByText(buttonText);
  for (let i = 0; i < times; i++) {
    fireEvent.click(button);
  }
};

// カウンターの値を検証する関数
const expectAppToBe = (expectedCount) => {
  expect(screen.getByText(`Count: ${expectedCount}`)).toBeInTheDocument();
};

// レンダリングと初期状態のアサーションを行うセットアップ関数
const setupAndCheckInitialCount = () => {
  render(<App />);
  expectAppToBe(0);
};

// カウンターの初期表示をテストする
test('初期状態でカウンターが0であること', () => {
  setupAndCheckInitialCount();
});

// カウントアップボタンの機能をテストする
test('ボタンをクリックするとカウンターが1増えること', () => {
  setupAndCheckInitialCount();
  clickButtonTimes('Increment', 1);
  expectAppToBe(1);
});

// カウントアップ後の表示をテストする
test('ボタンを2回クリックするとカウンターが2増えること', () => {
  setupAndCheckInitialCount();
  clickButtonTimes('Increment', 2);
  expectAppToBe(2);
});

上記の例では、クリック回数を指定してボタンをクリックする関数と、カウンターの値を検証する関数を定義しました。

これにより、同じ処理を繰り返すことなく、テストコードをDRYにすることができました。

ただし、ここまでDRYにする必要があるかどうかは、テスト対象のコンポーネントの複雑さによって異なります。

たとえば上記のテスト例だと検証用の関数を定義しましたが、関数の定義をチェックしながら読み進める必要があるため、テストコードの可読性が低下してしまう可能性があります。

とにかくDRYにすれば良いというわけではなく、本当に必要か?という視点で判断することが大切です。

まとめ

本記事では、ソフトウェアテストにおけるDRY原則の重要性と、ReactテストでDRYを実現する方法について解説しました。

DRY原則をテストコードに適用することで、以下のようなメリットがあります。

  • テストコードの重複を減らし、可読性が向上する
  • テストの追加や変更が容易になる
  • テストの品質が向上する

ただし、DRY原則を適用する際には、テスト対象のコンポーネントの複雑さに応じて、DRYにする必要があるかどうかを判断する必要があります。

とはいえ、テストコードの重複を減らすことはテストの品質を向上させる上で重要なポイントですので、ぜひ実践してみてください。

React テストの書き方をもっと学びたい方へ

本記事では React テストの書き方について、細かい説明は省きました。

しかし、本来は書き方のルールや、今回ご紹介したようなコツを体系的に学ばないと、テストを書くことは難しいです。

テストの書き方を知らないために、テストに時間をかけてしまうのはもったいないですよね。

そのため、一度で良いのでしっかりとテストの書き方を学んでおくことをおすすめします!

React の実装は学習済みで、テストだけを効率的に学びたいならば、ベストセラーにもなっている以下の Udemy 講座一択です。

>>> Reactソフトウェアテスト(Hooks+ReduxToolKit時代のモダンテスト手法)

React Hooksにも対応しつつ、API との連携をするコンポーネントテストも学べるため、実践的な React のテストを一通り学ぶことができます。

4.5時間と、比較的コンパクトにまとめられているので、気軽に始められるのも良いところです。

今後、テストはずーっと書いていくことになりますので、早めに高品質なテストを書けるようになっておきましょう!

おすすめの React 学習教材はこちら!

React のおすすめ教材については、以下の記事で厳選したものを紹介しているので参考にしてみてください!

プログラミングスクール教材の執筆経験もある筆者の目線で比較しています。

目次