Laravel+Nuxt.jsで作っているプロジェクトにCircleCIを導入した。

こんにちは！
今作っているアプリにCircleCIを導入して、文法チェックツールやテストの実行を自動化しました。
メモ用にやったことを書き残しておきたいと思います。どなたかの参考になると嬉しいです。



## 環境
- Laravel v6.10.1
- Nuxt.js v2.11.0
- CircleCI v2.1

## ディレクトリ構成
1つのリポジトリでBackend(Laravelアプリ)とFrontend(Nuxtアプリ)を管理しています。

```
project
  |- .circleci
  |    └- config.yml 
  |- docker
  |- docker-compose.yml
  |- backend (Laravelアプリ)
  └- frontend (Nuxtアプリ)
```
## CircleCIで行っていること
- phpファイルのコーディング規約チェック
- phpファイルを静的解析して、バグになりそうなところをチェック
- jsファイルも同様にコーディング規約チェックとバグになりそうなところチェック
- PHPUnitとJestの実行
- テストカバレッジのアップロード

## 導入したツール
- [CircleCI](https://circleci.com/)
- [Codecov](https://codecov.io/)

## 導入したライブラリ
#### PHPUnit
PHP用のテストフレームワーク。
Laravelアプリのテストを実行する時に使う。
標準でインストールされているはず。

#### PHP_CodeSniffer
コーディング規約チェックライブラリ。
Laravelアプリで書いたコードがPHPのコーディング規約にあっているかをチェックする。

#### Larastan
PHPの静的解析を行うライブラリであるPHPStanのLaravelバージョン。
PHPのソースコードの静的解析を行い、バグがありそうなコードを発見する。

#### Lint&Prettier
JavaScriptファイルの構文チェックとコーディング規約のチェックを行うライブラリ。
JSのソースコードの静的解析を行い、バグがありそうなコードの発見とコーディング規約にあっているかをチェックする。

#### Jest
JavaScript用のテストフレームワーク。
Nuxtアプリのテストを実行する時に使う。

## 手順
### CircleCIの設定
まずはProjectのRootディレクトリ直下に`.circleci`というフォルダを作成し、設定ファイルである`config.yml`を作成します。

```bash
mkdir .circleci && touch .circleci/config.yml
```

設定はYAML形式で行います。YAMLについて詳しくない方は[こちら](https://magazine.rubyist.net/articles/0009/0009-YAML.html)を見ながら、実際に[ここで](http://yaml-online-parser.appspot.com/)試してみるといいかもしれません。


`config.yml`を次のように設定してください。

```yml
version: 2.1

# ここでは、繰り返し使う値を登録しておきます。管理するjobなどが多くなった時に管理がしやすいです。
# ymlでは、データの前に&でアンカーをつけることができます。定数を宣言するようなイメージです。データを参照する時に、データの前に*をつけます。
# 例) &name: ”ドラえもん",myname: *name => ドラえもん
aliases:
  - &composer_key_base composer-v3-
  - &composer_key composer-v3-{{ checksum "./backend/composer.lock" }}
  - &node_key_base node-v4-
  - &node_key node-v4-{{ checksum "./frontend/package.json" }}
  - &root ~/Your_APP_NAME_OR_SOMETHING

  - &defaults
    working_directory: *root
  - &package-install
    run:
      name: npm install
      command: npm i
      working_directory: frontend
  - &restore_composer
    restore_cache:
      keys:
        - *composer_key
        - *composer_key_base
  - &restore_node
    restore_cache:
      keys:
        - *node_key
        - *node_key_base
  - &prepare-db
    run:
      name: Prepare database
      command: |
        dockerize -wait tcp://127.0.0.1:3306 -timeout 60s

# Codecovにテストカバレッジをアップロードするときに使用します。
orbs:
  codecov: codecov/codecov@1.0.5

## ここでjobを設定し、実行します。
jobs:
  build:
    <<: *defaults
    docker:
      - image: monicahq/circleci-docker-centralperk:latest
      - image: circleci/mysql:5.7
        command: mysqld --default-authentication-plugin=mysql_native_password
        environment:
          MYSQL_DATABASE: homestead
          MYSQL_USER: homestead
          MYSQL_PASSWORD: secret
          MYSQL_ROOT_PASSWORD: password

    steps:
      - checkout

      - run:
          name: Prepare environment
          command: |
            echo $(ls -al)
            cp .env.circleci .env
            sudo composer self-update
          working_directory: backend

      ## Laravel
      - *restore_composer
      - run:
          command: |
            composer global require hirak/prestissimo
            composer install --no-interaction --no-suggest --ignore-platform-reqs
          working_directory: backend

      - save_cache:
          key: *composer_key
          paths: ~/.composer/cache/

      - run:
          name: RUN KEY:GENERATE
          command: |
            php artisan key:generate
          working_directory: backend

      - *prepare-db

      - run:
          name: RUN DB MIGRATE
          command: |
            php artisan migrate
          working_directory: backend

      - run:
          name: RUN Larastan
          command: ./vendor/bin/phpstan analyse
          working_directory: backend

      - run:
          name: RUN PHP Code_Sniffer
          command:  ./vendor/bin/phpcs
          working_directory: backend

      - run:
          name: RUN PHPUnit
          command: phpdbg -qrr vendor/bin/phpunit --coverage-clover ./tests/report
          working_directory: backend

      ### Codecovにテストカバレッジをアップロードします
      - codecov/upload:
          file: ./backend/tests/report
          flags: backend


      ## Nuxt.JS
      - *restore_node

      - *package-install

      - save_cache:
          key: *node_key
          paths: ~/.cache

      - run:
          name: Check js and vue lint
          command: npm run lint
          working_directory: frontend

      - run:
          name: RUN JUnit
          command: npm run test-coverage
          working_directory: frontend

      ### Codecovにテストカバレッジをアップロードします
      - codecov/upload:
          file: ./frontend/coverage/clover.xml
          flags: frontend
```

CircleCIで使用するDockerイメージは、OSSの1つである[monica](https://github.com/monicahq/monica)が開発している[monicahq/circleci-docker-centralperk](https://github.com/monicahq/centralperk/blob/master/Dockerfile)を使用しています。

#### Databaseの設定
CircleCI内でDatabaseを用いて、Laravelアプリのテストを行うことができるように、Databaseの設定を行います。

まずはLaravelアプリのプロジェクトにある`config/database.yml`に以下のものを追加します。

```yml
    'connections' => [
        'circleci_test' => [
            'driver' => 'mysql',
            'host' => env('DB_HOST'),
            'port' => '3306',
            'database' => env('DB_DATABASE'),
            'username' => env('DB_USERNAME'),
            'password' => env('DB_PASSWORD'),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'strict' => true,
            'engine' => null,
        ],
        中略
    ]

```

同じくLaravelアプリのプロジェクトに`.env.circleciファイル`を作成して、以下のものを貼り付けます。

```
APP_NAME=Laravel
APP_ENV=circleci_test
APP_KEY=YOUR_APP_KEY
APP_DEBUG=true
APP_URL=http://localhost

LOG_CHANNEL=stack

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
```

これでCircleCIの中でDatabaseを用いたテストを実行することができると思います。

### PHP_CodeSnifferのインストール&設定
PHP_CodeSnifferは、PHPのソースコードがコーディング規約に合っているかどうかをチェックするライブラリです。PHPのコーディング規約はいくつかあるのですが、ここでは`PSR-2`を使います。
詳しく知りたい方は[こちらの記事](https://blog-ja.sideci.com/entry/PHP-codingstyles)がわかりやすいと思います。

では、次のコマンドを実行して、LaravelアプリのプロジェクトにPHP_CodeSnifferをインストールします。

```bash
composer require --dev "squizlabs/php_codesniffer=*"
```

次にLaravelアプリのプロジェクト直下に`phpcs.xml`を作成します。

```bash
touch phpcs.xml
```

作成した`phpcs.xml`に次の値を設定します。

```xml
<?xml version="1.0"?>
<ruleset name="Laravel Standards">
    <description>The Laravel Coding Standards</description>

    <!-- 対象フォルダ -->
    <file>./app</file>
    <file>./config</file>
    <file>./resources</file>
    <file>./routes</file>
    <file>./tests</file>

    <!-- 除外したいファイル、ディレクトリ -->
    <exclude-pattern>*/database/*</exclude-pattern>
    <exclude-pattern>*/cache/*</exclude-pattern>
    <exclude-pattern>*/*.js</exclude-pattern>
    <exclude-pattern>*/*.css</exclude-pattern>
    <exclude-pattern>*/*.xml</exclude-pattern>
    <exclude-pattern>*/*.blade.php</exclude-pattern>
    <exclude-pattern>*/autoload.php</exclude-pattern>
    <exclude-pattern>*/storage/*</exclude-pattern>
    <exclude-pattern>*/docs/*</exclude-pattern>
    <exclude-pattern>*/vendor/*</exclude-pattern>
    <exclude-pattern>*/migrations/*</exclude-pattern>

    <!-- PSR2をベースとする -->
    <rule ref="PSR2">
        <!-- 除外したい項目 -->
        <exclude name="Generic.Files.LineLength.TooLong"/>
    </rule>

    <arg name="colors"/>
    <arg value="p"/>

    <ini name="memory_limit" value="128M"/>
    <rule ref="PSR2"/>
</ruleset>
```

下記のコマンドを実行して、PHP_CodeSnifferを実行してください。コーディング規約に合っていないコードがある場合エラーが出力されます。

```bash
./vendor/bin/phpcs
```

ここで出たエラーを自動で整形したい場合は、以下のコマンドを使います。

```bash
./vendor/bin/phpcbr
```

### Larastanのインストール&設定
LarastanはPHPのソースコードにバグらしいコードがないかをチェックするライブラリです。

まず次のコマンドを実行して、LaravelアプリのプロジェクトにLarastanをインストールします。

```bash
composer require --dev nunomaduro/larastan
```

次にLaravelアプリのプロジェクト直下に`phpstan.neon`を作成します。

```bash
touch phpstan.neon
```

作成した`phpcs.xml`に次の値を設定します。

```
includes:
    - ./vendor/nunomaduro/larastan/extension.neon

parameters:

    paths:
        - app
        - resources
        - tests

    # The level 8 is the highest level
    level: 5

    ignoreErrors:
        - '#Unsafe usage of new static#'

    excludes_analyse:
        - ./*/*/FileToBeExcluded.php

    checkMissingIterableValueType: false
```


次のコマンドで解析を行います。バグがありそうなコードが存在する場合、そのコードが出力されると思います。

```bash
./vendor/bin/phpstan analyse
```

また解析のレベルを0-7の間で調整することができます。0に近いほど緩く解析、7に近いほど厳しく解析になります。

```bash
./vendor/bin/phpstan analyse --level=4
```

### ESLint & Prettierの設定
ESLintとPrettierは、JavaScriptのソースコードを解析して、バグらしきコードがないかやコーディング規約にあっているかをチェックするライブラリです。

次のコマンドを実行して、Nuxtアプリのプロジェクトに、ESLintとPrettierに必要なパッケージをインストールします。

```bash
npm install --save-dev babel-eslint eslint eslint-config-prettier eslint-loader eslint-plugin-vue eslint-plugin-prettier prettier
```

次にNuxtアプリのプロジェクト直下に`.eslintrc.js`を作成します。

```bash
touch .eslintrc.js
```

作成した`.eslintrc.js`に次の値を設定します。

```js
module.exports = {
  root: true,
  env: {
    browser: true,
    node: true
  },
  "parser": "vue-eslint-parser",
  parserOptions: {
    parser: 'babel-eslint'
  },
  extends: [
    "prettier",
    "plugin:prettier/recommended",
    "plugin:vue/recommended",
    "@vue/prettier",
    // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
    // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
    // 'plugin:vue/essential'
  ],
  // required to lint *.vue files
  plugins: [
    'vue'
  ],
  // add your custom rules here
  rules: {
    "semi": [2, "never"],
    "no-unused-vars": ["error", { "args": "none" }],
    "prettier/prettier": [
      "error",
      {
        "singleQuote": true,
        "semi": false
      }
    ]
  }
}
```

`package.json`に`lint`と`lint-fix`コマンドを追加します。

```json
  "scripts": {
    "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
    "lint-fix": "eslint --ext .js,.vue --ignore-path .gitignore . --fix",
  },
```

次のコマンドで解析を実行します。コーディング規約に合っていないおよびバグがありそうなコードが存在する場合、エラーが出力されます。

```bash
npm run lint
```


エラーを自動で修正したい場合はこちらのコマンドを実行します。

```bash
npm run lint-fix
```


### CircleCIとCodecov連携
ここでは、CircleCIから実行したPHPUnitとJestのテストカバレッジをCodecovで確認する方法を記載します。
登録からCodecovの設定方法を書くのが大変なので、以下のサイト様を参考にしてやってみてください...

[CircleCIとCodecovでGitHubにカバレッジバッジをつけよう！](https://medium.com/veltra-engineering/how-to-add-coverage-badge-to-github-repository-using-circleci-and-codecov-663dd412d95f)

流れとしては、Codecovに登録 -> CodecovからGitHubリポジトリの選択 -> CircleCIにCodecovのトークンを登録 -> ローカルからCircleCIにpush -> CircleCIからCodecovにカバレッジをアップロードという流れになるかと思います。


### PHPUnitのテストカバレッジの結果をCodecovにアップロード
次にLaravelアプリのプロジェクトにある`phpunit.xml`に次の設定を追加します。

```xml
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./app/Http/Controllers</directory>
            <directory suffix=".php">./app/Models</directory>
        </whitelist>
    </filter>
```

ここでテストカバレッジを測定したいフォルダやファイルを設定します。

テストカバレッジを出力するときは次のコマンドを実行してください。

```bash
phpdbg -qrr vendor/bin/phpunit --coverage-clover ./tests/report
```

`tests`ディレクトリの下に`report`というファイルが出来ていると思います。
こちらをCircleCIからCodecovにアップロードします。

実際には`.circleci/config.yml`の以下の部分でテストの実行とアップロードを行っています。

```yml
      - run:
          name: RUN PHPUnit
          command: phpdbg -qrr vendor/bin/phpunit --coverage-clover ./tests/report
          working_directory: backend

      ### Codecovにテストカバレッジをアップロードします
      - codecov/upload:
          file: ./backend/tests/report
          flags: backend
```


ちなみに`phpdbg`を使うとテストカバレッジの出力が高速になるみたいです。
参考: [PHP でカバレッジを出すなら phpdbg](https://qiita.com/nanasess/items/f5f8483e92c76ef408ff)



### Jestのテストカバレッジの結果をCodecovにアップロード
JestはJavaScriptのテストを実行するためのフレームワークです。

次のコマンドを実行して、NuxtアプリにJestをインストールします。

```bash
npm install --save-dev jest
```

次にNuxtアプリのプロジェクト直下に`jest.cinfig.js`を作成します。

```bash
touch jest.config.js
```

作成した`jest.config.js`に次の値を設定します。

```js
module.exports = {
  transform: {
    '^.+\\.js$': 'babel-jest',
    '.*\\.(vue)$': 'vue-jest'
  },
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/$1',
    '^~/(.*)$': '<rootDir>/$1'
  },
  moduleFileExtensions: ['js', 'json', 'vue']
}
```

次にtestsフォルダを作成します。

```bash
mkdir tests
```

`package.json`に次のコマンドを追加します。

```json
  "scripts": {
    "test": "jest --verbose ./tests",
    "test-coverage": "jest --verbose ./tests --coverage"
  },
```

次のコマンドを実行してテストを実行しましょう。

```bash
npm run test-coverage
```

Nuxtアプリのディレクトリ に`coverage`というフォルダが出来ていると思います。このフォルダ内の`clover.xml`をCircleCIからCodecovにアップロードします。

実際には`.circleci/config.yml`の以下の部分でテストの実行とアップロードを行っています。

```yml
      - run:
          name: RUN JUnit
          command: npm run test-coverage
          working_directory: frontend

      ### Codecovにテストカバレッジをアップロードします
      - codecov/upload:
          file: ./frontend/coverage/clover.xml
          flags: frontend
```

CircleCIにpushしてみて、Codecovにテストカバレッジが表示されていれば成功です。

何か間違いやこうした方がいいよ！などがございましたら、ご指摘していただけると嬉しいです！
ありがとうございました！

## 参考にさせていただきました
- [PHP_CodeSniffer で PHP のコーディングスタイルを整える (Visual Studio Code)](https://mseeeen.msen.jp/php-codesniffer-with-vscode/)
- [Nuxt.js で ESLint ＆ Prettier を導入する](https://qiita.com/shosho/items/742723142980092b61ef)
- [CircleCIとCodecovでGitHubにカバレッジバッジをつけよう！](https://medium.com/veltra-engineering/how-to-add-coverage-badge-to-github-repository-using-circleci-and-codecov-663dd412d95f)
- [PHPのコーディング規約まとめ。PSR-2, CakePHP, Symfony, WordPress, FuelPHPなどの5つの規約の概要と特徴的なルール](https://blog-ja.sideci.com/entry/PHP-codingstyles)