【初心者仕様】GitHubActionsでCI/CDを試してみた話

## はじめに
この記事はGitHubActionsについて、名前は知っているけどその実態は何も知らない成人男性がGitHubActionsでコードをテスト～デプロイを行うワークフローを作ってみた話です。

## GitHubActionsってなんだ
GitHubActionsはGitHubの提供するCI/CDプラットフォームです。
プルリクエストが作成されたタイミングでテストコードを実行したり、mainブランチへマージされたタイミングでビルドやデプロイを行うワークフローを実行できるほか、イシューを作成したタイミングでラベルを付けるといったこともできるそうです。

> GitHub Actions は、DevOps であるだけでなく、リポジトリで他のイベントが発生したときにワークフローを実行できます。 たとえば、リポジトリで新しい issue が作成されるたびに、適切なラベルを自動的に追加するワークフローを実行できます。

### GitHubActions Words
出てくるワードが分からんのでまとめてみました。

#### ワークフロー
1つ以上のジョブを実行する自動化プロセス。
.github/workflows/配下のYAMLファイルで定義する。

#### ジョブ
ワークフローを構成する各スクリプトやアクション。

#### イベント
プッシュやフォーク、イシューの作成といったGitHub上のアクションのことで、イベントをトリガーにワークフローが実行される。

## 使ってみよう
なんとな～く概要を知ったつもりになったところで今回は以下の2つのワークフローを作ってみます。

* プルリクエストが作成されたらテストコードを実行する
* mainブランチへマージされたらビルドしてGitHubPagesに静的サイトをデプロイする

### プロジェクトの準備
viteでReactプロジェクトを作成して必要なパッケージをインストールします。

```sh
npm create vite@latest

npm install -D vitest @vitest/coverage-v8
```

適当にvitestの設定をします。
```vitest.config.ts
const defaultExclude = ["coverage/**",
    "dist/**",
    "**\/[.]**",
    "packages/*\/test?(s)/**",
    "**\/*.d.ts",
    "**\/virtual:*",
    "**\/__x00__*",
    "**\/\x00*",
    "cypress/**",
    "test?(s)/**",
    "test?(-*).?(c|m)[jt]s?(x)",
    "**\/*{.,-}{test,spec}?(-d).?(c|m)[jt]s?(x)",
    "**\/__tests__/**",
    "**\/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*",
    "**\/vitest.{workspace,projects}.[jt]s?(on)",
    "**\/.{eslint,mocha,prettier}rc.{?(c|m)js,yml}"
];

export default defineConfig({
    plugins: [react()],
    test: {
        globals: true,
        environment: "jsdom",
        include: ["test/**/*.test.{js,ts,jsx,tsx}"],
        coverage: {
            provider: "v8",
            exclude: [...defaultExclude, "src/main.tsx"],
            thresholds: {
                statements: 100,
                branches: 100,
                functions: 100,
                lines: 100
            }
        },
        alias: { "@": path.resolve(__dirname, "./test"), }
    }
});
```

適当にテストコードを書きます。
```test/App.test.tsx
import { describe, test, expect } from "vitest";
import React from "react";
import { render, screen } from "@testing-library/react";
import App from "../src/App";

describe("App Test", () => {

    test("render check", () => {
        render(<App />);
        expect(screen.getByText("Vite + React")).toBeTruthy();
    });
});
```

### プルリクエストの作成時にテストコードを実行するワークフロー

.github/workflows/配下にワークフローの設定ファイルを作成します。
```.github/workflows/test.yml
name: Run Vitest And Check Coverage
run-name: test coverage

# プルリクエストの作成時に実行する
on: [pull_request]

# ワークフローで実行するジョブ定義
jobs:
  test:
    # ジョブを実行するOS
    runs-on: ubuntu-latest

    steps:
      # リポジトリをチェックアウトする
      - name: Checkout repository
        uses: actions/checkout@v4

      # Node.jsを使用する
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20.x'

      # 依存関係をインストール
      - name: Install dependencies
        run: npm install

      # Vitestを実行
      - name: Run vitest
        run: npm run test
```

#### on
ワークフローを実行するイベントを記述します。
プルリクエストの作成時(pull_request)やプッシュ時(push)のほかにも指定できるイベントはかなり多いです。
以下の形式で特定のブランチのみワークフローを実行することも可能です。

```yml
# mainブランチへプッシュされたときにワークフローを実行する
on: 
  push:
    branches:
      - "main"
```

#### jobs
ワークフローで実施するジョブを記述します。
ジョブ自体は並列で実行されますが、`needs`を指定することで特定のジョブが完了してから実行する、といった制御も可能です。

#### runs-on
ジョブを実行するOS（ランナー）を設定します。
GitHubホステッドランナー（Ubuntu、Windows、macOS）のほか、独自で設定するセルフホステッドランナーを設定できます。

#### uses
GitHubやユーザの作成したアクションをワークフロー内から実行することが出来ます。
上記コードで使用している`actions/checkout@v4`や`actions/setup-node@v4`はGitHubが作成して公開しているアクションです。

### テストコードの自動実行

ワークフローの設定ができたところで、適当にブランチを切ってmainブランチへのプルリクエストを作成します。
その際、vitestのコードカバレッジが100%にならないように変更を加えておきます。

プルリクエストを作ったらGithubActionsが実行されています。
このまま実行が完了するまで待ってみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125818/43adaa47-a418-355f-cf83-d92c229422d3.png)

カバレッジが100%未満なのでエラーが出ていますが、
このままでもマージはできてしまうので、リポジトリの設定を弄ってテストでエラーが出た場合はマージできないようにしてみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125818/9e6aaf9b-8afe-131a-ed9a-ff1250089fba.png)

リポジトリのルールセットから、mainブランチに対するルール設定を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125818/d91edb05-f18f-92e9-07c1-07253d0894f6.png)

今回は`Require status checks to pass before merging`を有効化することでマージ前のステータスチェック通過を必須条件とし、必要なステータスチェックとしてGitHubActionsのtestワークフローを追加しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125818/35fd465a-c709-921a-2834-fb14be476a74.png)

これでテストコードのカバレッジを100%にしないとマージできないようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125818/32ec4b7c-1193-3664-cd18-a5240efc8f43.png)

コードのカバレッジが100%になるよう修正してプッシュしてみると
ステータスチェックが正常に完了し、マージできるようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125818/565a2e98-35c7-d3f0-98f1-7db5ef1d79d3.png)


### mainブランチへマージされたらGitHubPagesにデプロイするワークフロー

.github/workflows/配下にtestとは別のワークフローファイルを作成します。
```.github/workflows/build-deploy.yml
name: Build Application
run-name: build and deploy
on: 
  push:
    branches:
      - "main"

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      # リポジトリをチェックアウトする
      - name: Checkout repository
        uses: actions/checkout@v4

      # Node.jsを使用する
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20.x'

      # 依存関係をインストール
      - name: Install dependencies
        run: npm install

      # ビルドを実行
      - name: Run build
        run: npm run build

      # ビルドしたアーティファクトをアップロード
      - name: Upload artifact files
        uses: actions/upload-pages-artifact@v2
        with:
          path: ./dist

  deploy:
    runs-on: ubuntu-latest

    # GitHub Pagesのデプロイ権限を設定
    permissions:
      contents: read
      pages: write
      id-token: write

    # buildが完了してから実行する
    needs: build

    steps:
      # GitHub Pagesにデプロイ
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v3
```

ワークフローを作成したところで適当なブランチへプッシュしてmainブランチへマージします。
マージのタイミングでデプロイ用ワークフローが動いていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125818/5eb3dd41-ace8-4a4b-b759-58142a00009f.png)

ワークフローがちゃんと完了し、GitHubPagesに静的サイトがデプロイされていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125818/38242703-6fb5-8194-2d81-f4bf4664ca8f.png)

## おわりに
GithubActionsを使って簡単な仕様のCI/CDを試してみましたが、かなり簡単に実装できるものでしたね。
GitHubは個人で適当に遊ぶ用のリポジトリを用意する程度にしか使ってきませんでしたが、もう少しGitHubの可能性に目を向けるべきだったのかもしれません。

気が向いたらもう少し凝ったワークフローを作って記事を書いてみたいですね。

## 参考記事
https://docs.github.com/ja/actions

https://qiita.com/shun198/items/14cdba2d8e58ab96cf95

https://qiita.com/tat_mae084/items/745761eee6cd1d42949d