レアゾン・ホールディングス 2024新卒エンジニアユニットテスト研修 -テストをしっかり書けるエンジニアになろう-

プロモーション本部　エンジニアの石黒です。

高等専門学校で5年間電子工学および情報・通信工学について学んでいました。
といっても興味のあることしか熱心にやってこなかった身なので、エンジニアとしてはまだまだ駆け出しの自分ですが新しいことに挑戦し続けられるレアゾン・ホールディングスの社風は自分を大きく成長させてくれる環境だと感じています。
約3ヶ月にわたる新卒研修を経て現在は、ブロックチェーンを活用したAd Networkの開発に携わっています。

今回は2024年新卒研修のなかから、 ユニットテスト研修について紹介したいと思います。

## 研修の概要
研修の概要はオウンドメディアの記事で詳しく紹介していますので、こちらからご覧ください。
https://media.reazon.jp/articles/NewGraduate_engineer

# ユニットテスト研修の概要
### ユニットテスト研修のゴール
 →フロントエンド・バックエンド研修で作ったECサイトのテストを書いてみる

### ユニットテスト研修の全体像について
 1日目：テストの必要性について & テストの書き方
 2日目：ECサイトのテストを書いてみよう

# テーマの詳細

### そもそもなんでテストが必要なんだろうか？
コードに変更を加えたときに、システムがデグレードしていないか確認する必要があります。この作業は手動で行うことも可能ですが、機能を増やしていった時に手動で全ての動作を確認しようと思うと大変な労力になってしまいます。

そこで登場するのがテストです！

予めその機能で期待する処理をシミュレーションしておくことで、デグレやバグ・仕様変更による不具合が発生した場合に、テストが失敗してくれるので、開発段階での早期発見・修正が可能となります。

<img width="" height="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4033401/1fde6111-46b0-4ace-8a85-ef440e444647.png" alt="すべてのテストが問題なく実行された画像">

実際のテストの例です。
Testsの項目を見てみると34個あるテストが全て成功していることがわかりますね。

<br/>

<img width="" height="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4033401/b797545d-61af-49e8-9979-32ec68101836.png" alt="テストに失敗した際のエラーメッセージを表示した画像">

ちなみにテストに失敗した場合は、このようにどのテストのどの部分で失敗したのかを明示してくれます。

## テストの種類
### 単体テスト
1つの機能単体で行うテストで、バックエンドからのAPIの代わりにMockと呼ばれるコンポーネントを使用してテストデータを返すので短時間でテストすることができます。

### 結合テスト
いくつかのコンポーネントを結合して行うテストで、単体テストでは行えない処理の確認を行います。

### E2Eテスト(End To End Test)
システムを本番環境に近い状態で行うテストで、Mockを使用せずにシステムの一連の流れを検証するため、処理にかかる負担も、メンテナンスのコストも高くなります。
<br/>
そこからさらに、正しい処理をテストする正常系、エラー時に正しくエラーが処理されるかテストする準正常系、予期せぬエラーなどの異常を正しく処理できるかテストする異常系に分かれます。

# 技術的なお話
今回のハンズオンでは、vitestとReact Testing Libraryを使用し、フロントエンド・バックエンド研修で制作したECサイトのログイン機能の単体テストを書いてみました。

まずvitestとtesting libraryを導入します。
```shell
$ npm install --save-dev vitest
```
```shell
$ npm install --save-dev @testing-library/react @testing-library/jest-dom
```
vitestの設定ファイルであるvitest.config.tsを用意します。
```typescript
export default defineConfig((configEnv) =>
 mergeConfig(
   viteConfig(configEnv),
   defineConfig({
     test: {
       globals: true,
       include: ["src/**/*.test.{js,ts,jsx,tsx}"], // includeに書かれているパスにテストファイルを置く
       environment: "jsdom",
     },
   })
 )
);
```
これでテストを書く準備ができました。

「準備（Arrange）」「実行（Act）」「確認（Assert）」の順番に記述していくAAAパターンと呼ばれる書き方で記述することで、可読性の高いコードを書くことを意識して境界値・代表値分割を用いたテストケースを作成しました。

実際のユーザーの動きを想定したシミュレーションができるため、想像していたより簡単に実装できました。　
![20241015140114.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4033401/38aaae64-c93e-40df-ad3a-17e24d97a4e9.png)

このログインページを例にすると、ログインまでのユーザーの動きは...

①Emailの項目をクリックしてメールアドレスを入力

②Passwordの項目をクリックしてパスワードを入力

③LOGINをクリック

という流れになります。

この動きをシミュレーションしたイメージはこんな感じです。
```typescript
it("should pass login form with correct email and password", async () => {
   //Arrange（準備）
   render(<LoginPage />);

   //Act（実行）
   await userEvent.click(screen.getByText("Email"));
   await userEvent.keyboard("user1@reazon.jp");
   await userEvent.click(screen.getByText("Password"));
   await userEvent.keyboard("12345678");
   await userEvent.click(screen.getByRole("button", { name: "Login" }));
   await screen.findByText("USER ID:");

   //Assert（確認）
   expect(screen.getByText("USER ID: 1")).toBeInTheDocument();
});


 it("should show error message when login failed", async () => {
   //Arrange（準備）
   render(<LoginPage />);

   //Act（実行）
   await userEvent.click(screen.getByText("Email"));
   await userEvent.keyboard("user1@reazon.jp");
   await userEvent.click(screen.getByText("Password"));
   await userEvent.keyboard("11111111");

   //Assert（確認）
   expect(screen.getByLabelText("Email")).toHaveValue("user1@reazon.jp");
   expect(screen.getByLabelText("Password")).toHaveValue("11111111");
   expect(
     screen.getByText("Invalid email or password. Please try again."),
   ).toBeInTheDocument();
   expect(screen.getByRole("button", { name: "Login" })).toBeDisabled();
 });
```

また、自分は研修中には辿り着けませんでしたが、バックエンドのログイン機能のAPIテストを書いてみるハンズオンもありました。こちらはバックエンドの実装にExpressを使用したため、テストライブラリにはsupertestを使用してテストを作成しました。

supertestをインストールします。
```shell
$ npm install supertest --save-dev
```
ログイン機能でテストするのは...

①正しいリクエストを送った時にステータスコード200が返ってくるか

②正しくないリクエストを送った時にステータスコード401が返ってくるか

③ログインした状態で再びログインしようとした時にステータスコード400が返ってくるか

の3つです。

これらをシミュレーションするテストはこんな感じです。
```typescript
describe("POST /login", () => {
 it("should return 200 OK", (done) => {
   request(app)
     .post("/login")
     .send({ email: "user1@reazon.jp", password: "12345678" })
     .expect(200, done);
 });


 it("should return 401 Invalid email or password", (done) => {
   request(app)
     .post("/login")
     .send({ email: "user1@reazon.jp", password: "11111111" })
     .expect(401, done)
     .expect("Invalid email or password");
 });


 it("should return 400 Bad Request", (done) => {
   request(app)
     .post("/login")
     .send({ email: "user1@reazon.jp", password: "12345678" })
     .expect(200)
     .then(() => {
       return request(app)
         .post("/login")
         .send({ email: "user1@reazon.jp", password: "12345678" })
         .expect(400)
         .expect("Already logged in");
     })
     .then(() => done())
     .catch((err) => done(err));
 });
});
```

# 最後に
いかがでしたか？

他の研修と比べてテストはなんだか地味なイメージを持っていたのですが、自分の配属されたチームでは頻繁にテストを書く機会があり、如何にテストが重要であるかを日々感じています。

特に、自分のタスクでバグが発生してしまった時には、どこが悪さをしているかが見つけやすいのですぐに修正ができています。テストさまさまです。

### **▼採用情報**
レアゾン・ホールディングスは、「世界一の企業へ」というビジョンを掲げ、「新しい"当たり前"を作り続ける」というミッションを推進しています。

現在、エンジニア採用を積極的に行っておりますので、ご興味をお持ちいただけましたら、ぜひ下記リンクからご応募ください。

https://recruit.reazon.jp/