Closed39

フロントエンドのテストに関するZenn記事を漁る

hajimismhajimism
hajimismhajimism

また、テストを正確に書きやすくするために、以下のeslintのプラグインを追加すると良いと思います。これはオプションです。

$ yarn add -D eslint-plugin-jest-dom
$ yarn add -D eslint-plugin-testing-library
hajimismhajimism

最後に、状態管理やUIライブラリのProviderをラップしておいてくれるように、@testing-library/reactのrenderをカスタムしたものを用意しておくと、テストを書くのが少し楽になります。

import { FC, ReactElement } from 'react';
import { render, RenderOptions, RenderResult } from '@testing-library/react';
import { Provider, store, ThemeProvider, theme } from './providers';

const AllTheProviders: FC = ({ children }) => (
  <Provider store={store}>
    <ThemeProvider theme={theme}>
      {children}
    </ThemeProvider>
  </Provider>
);

const customRender = (ui: ReactElement, options?: Omit<RenderOptions, 'wrapper'>): RenderResult =>
  render(ui, { wrapper: AllTheProviders, ...options });

export * from '@testing-library/react';
export { customRender as render };
hajimismhajimism

先ほど書いたようにgetByはgetできなかったときはエラーを吐くため、getByを使うとscreen.getByText(/[0-9]文字以上で入力してください/)の部分ですでにテストが失敗するため、エラーメッセージが無いということが確かめられません。
そのため、取得する要素が存在しなくてもエラーを吐かないqueryByの方を使っています。

hajimismhajimism

testing-library-selectorというライブラリでこういう書き方ができるみたい

import { byRole } from 'testing-library-selector';

const ui = {
  loginButton: byRole('button', { name: /ログイン/ }),
};

...

expect(ui.loginButton.get()).toBeDisabled();
hajimismhajimism
const spy = jest.spyOn(console, 'error');
spy.mockImplementation(() => {});
...
spy.mockRestore();

この3行はコンソールへのわざと出したエラーの出力を省き、テストの結果を読みやすくしています。

コードの詳細な意味がわからなかったので要確認

hajimismhajimism
hajimismhajimism

自分の経験的には、ユニットとテストの最初の一つを書いたらあとは自然とその周辺で増えていく。サンプルがあったら人はコピペする。逆にいうと最初の一つを書かない限り一切書かれない。まず一つ用意するのが大事。

mizchiさんの開発者に関する洞察かなり好きだ

hajimismhajimism

...プログラマにとってはユニットテストの方が「安く、速く、うまい」。

ユニットテストは E2E テストと独立しているが、間接的に E2E を通すために下支えとなる。責務を分割できない実装すると、自然に E2E が増えてしまう。つまり、理論的にはモジュールごとに正しい責務を分割できていれば、本来の目的である E2E テストは最小限で済むはずである。

KentさんもE2Eはhappy pathをカバーするために使えって書いてたな

hajimismhajimism

今回は vitest を使う。mocha, ava, jest と今まで使った中で一番体験がいい。 API が jest 互換なので、移行しやすい。今の jest は commonjs/ESM の混乱が直撃してしまっており、正直設定が面倒になってしまっている。vitest はある程度丸投げできる。

hajimismhajimism

このとき、 tests や test/** ではなく、実装したいディレクトリで実装してしまってよい。

異論はあると思うが、テストが見えないディレクトリに隠蔽されていることで、見た目上きれいになっても、維持する対象という意識が薄れて、テスト意識が低い人に放置される害のが大きいと感じている。テストは消極的な存在ではなく、積極的に混ぜていったほうが逆にいいと考えている。

自分もこれだな
ことReactコンポーネントのテストに関してはscaffdogで雛形を自動生成して云々カンヌン

hajimismhajimism

このとき、describe や context を積極的には使わない。ファイル名自体がテストのためのスコープを持つので、基本的にはそれで十分とする。 describe があるときは、専用の beforeEach 等があることを期待する。

なるほど

hajimismhajimism

ここで意識することとして、expect() のアサーションも .toBe() や .equal() のような単純なものしか使わないようにしている。

地味に大事かもしれない

hajimismhajimism

jest/vitest は NODE_ENV に test が入っているので、ランタイムで実行されないように分岐を入れてテストを書く。コード内にプライベートな関数やモジュールが多いとき、一旦こう書いてしまう。

なるほど

hajimismhajimism
hajimismhajimism

うーん、シンプルに「満たすべき仕様(ユースケース)をテストとして記述し、最終的にはそれがpassされていることを目指す」「最初にテストを書くことで明確にゴールが意識されていて生産性を保ちやすい」みたいな話じゃないのかな

hajimismhajimism

これができるには「実装のスコープがはっきりしていて、かつそんなに大きい粒度でない」条件が必要な気がした。例えばプロジェクトの最初からTDDやるってなったときに、下記の迷いが生じそう

  • 最初からE2E書く?
  • どこまで再利用されるかわかんないのにコンポーネント切り出す?
  • 振る舞いがある程度自明なものもテスト書いてから?

その点、今回の例で使われているフォームはうってつけだ

hajimismhajimism

ベテランと新人が一緒に改修に取り組むときに、ベテランが「下記のテストをクリアするように〇〇コンポーネントを変更してください」的なdraft PRをCI付きで出して、新人がそれを満たせるように実装する

みたいな教育的な用途で威力を発揮しそう

hajimismhajimism
hajimismhajimism

region ロールをアクセシブルネーム(タイトルそのもの)で絞り込み、含まれる link を検証しています。

const href = "https://testing.example.com/articles/2";
const title = "React18 の useId で a11y対応する";
const region = screen.getByRole("region", { name: title });
expect(within(region).getByRole("link")).toHaveAttribute("href", href);

前者・後者を見比べても、どういった行動をしているのか一目瞭然のテストとなり、a11y とテストが同時に改善できました。

コミットされたテストファイルを見れば、a11y を考慮しているコンポーネントなのか否か、これもまた一目瞭然です。コンポーネントのテストファイルを今一度確認し、a11y を考慮しているか見直してみましょう。

hajimismhajimism

a11yの改善はdocumentに意味を付与することでマシンリーダブル(あるいはヒューマンリーダブル via マシン)にすることなんだな。
そして、付与された意味を用いて記述することでデベロッパーリーダブルにもなるという。目からウロコだわ。

このスクラップは2023/02/02にクローズされました