もがき系プログラマの日常

もがき系エンジニアの勉強したこと、日常のこと、気になっている技術、備忘録などを紹介するブログです。

vueでテストをやってみた

はじめに

こんばんは。

皆さんjsのテストって書いてますでしょうか?

僕はほぼほぼか書かないです!!

2〜3個前の案件で jestで書いて以来、そこから全く触っていません。

これはそろそろ...

と思い、重い腰を上げてやってみました。

今回は、今の案件でも使用している vueのテストをjestでやってみました。

多分新規プロジェクトとかだと、インストールツールでインタラクティブインストール時にテストツールとか選択できてたと思うのですが、テストだけ導入するにはどする?ってところでやってみました。

参考はこちらのサイト

本題

1. セットアップ

1. テストツールのinstall

現在の自分のプロジェクトでは、以下のライブラリをインストールすることになりました。

$ npm install --save-dev jest vue-jest babel-jest @vue/test-utils babel-core@bridge babel-preset-env

2. package.jsonの修正

  "scripts": {
    "test": "jest"
  },
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "vue"
    ],
    "transform": {
      ".*\\.(vue)$": "<rootDir>/node_modules/vue-jest",
      "^.+\\.js$": "<rootDir>/node_modules/babel-jest"
    }
  }

3. .babelrcの修正

{
  "presets": [
    [ "env", { "modules": false } ]
  ],
  "env": {
    "test": {
      "presets": [
        [ "env", { "targets": { "node": "current" } } ]
      ]
    }
  }
}

テスト

テストで使うvueは以下

特定のステータスをラジオボタンで変更するコンポーネントなイメージです。

<template>
  <label :class="{ 'status-checked': checked }">
    <input
      v-model="innerChoiceStatusId"
      type="radio"
      name="status[]"
      :value="statusId"
    >
    {{ label }}
  </label>
</template>

<script>
export default {
  name: "StatusRadioButton",
  props: {
    statusId: {
      type: Number,
      default: null
    },
    label: {
      type: String,
      default: ''
    },
    choiceStatusId: {
      type: Number,
      default: null
    }
  },
  computed: {
    innerChoiceStatusId: {
      get() {
        return this.choiceStatusId
      },
      set(choiceStatusId) {
        if (this.choiceStatusId !== choiceStatusId) {
          this.$emit('update:choiceStatusId', choiceStatusId)
        }
      }
    },
    checked() {
      return this.statusId === this.innerChoiceStatusId
    }
  }
}
</script>

<style scoped>

</style>

1. 参考サイトにあったやつのテスト

import { mount } from '@vue/test-utils'
import StatusRadioButton from '@js/StatusRadioButton'

describe('Component', () => {
  test('is a Vue instance', () => {
    const wrapper = mount(StatusRadioButton)
    expect(wrapper.vm).toBeTruthy()
  })
})
実行
 npm test

> jest

 PASS  tests/example.spec.js
  Component
    ✓ is a Vue instance (15 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        3.396 s
Ran all test suites.

参考サイトにあった expect(wrapper.isVueInstance()).toBeTruthy() を行うと、エラーが出まして、次期バージョンでは削除されるとのことでした。

なので、こちらの方法で試すことにしました。

2. emitされてるか

describe('Component', () => {
  test('チェックされたら値が更新される', async () => {
    const wrapper = mount(StatusRadioButton, {
      propsData: {
        statusId: 100,
        label: 'タイプ100',
        choiceStatusId: null,
      }
    })
    const radioInput = wrapper.find('input[type="radio"]')
    await radioInput.setChecked()
    expect(wrapper.emitted('update:choiceStatusId')).toBeTruthy()
    expect(wrapper.emitted('update:choiceStatusId')[0][0]).toBe(100)
  })
})
実行
$ npm test
> jest

 PASS  tests/Spec/example.spec.js
  Component
    ✓ is a Vue instance (16 ms)
    ✓ チェックされたら値が更新される (10 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        6.856 s
Ran all test suites.

propsDataをセットして、 emitされたかどうかのチェックは emitted でみるみたいです。

3. クラスが変更されているか

  test('チェックされたらlabelにstatus-checkedクラスが付与される', async () => {
    const wrapper = mount(StatusRadioButton, {
      propsData: {
        statusId: 100,
        label: 'タイプ100',
        choiceStatusId: null,
      }
    })
    const radioInput = wrapper.find('input[type="radio"]')
    await radioInput.setChecked()
    await wrapper.setProps({ choiceStatusId: wrapper.emitted('update:choiceStatusId')[0][0]})
    expect(wrapper.vm.checked).toEqual(true)
  })
実行
$ npm test

> jest

 PASS  tests/Spec/example.spec.js
  Component
    ✓ is a Vue instance (15 ms)
    ✓ チェックされたら値が更新される (10 ms)
    ✓ チェックされたらlabelにstatus-checkedクラスが付与される (5 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        3.39 s
Ran all test suites.

syncでpropsを指定している際、update emit後本来ならば値が変更されているんですが、テストではどう書くかわからなかったので、2番のテストと似たような感じになりました...

終わりに

テスト各事自体はやりやすいんですが、テスト環境を作ることが大変です...

現場からは以上です。

Github Actionsで並列実行させたphpunitを codecovにあげる

はじめに

こんばんは。

前回の記事の続きみたいな感じです。

kojirooooocks.hatenablog.com

前回の記事から、coverageをとって、codecovへあげるようにしてみました。

本題

前提

  1. codecovのtokenをsecretsに登録する

1. ymlの修正

.github/workflows/unittest.yml

name: unit test
on:
  push:

jobs:
  test:
    runs-on: ubuntu-20.04
    strategy:
      fail-fast: false
      matrix:
        parallelism: [5]
        id: [0,1,2,3,4]

    steps:
      - name: Checkout
        uses: actions/checkout@master

      - name: Install PHP with extensions
        uses: shivammathur/setup-php@v2
        with:
          php-version: 8.0
          coverage: pcov

      - name: Install Dependencies
        run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist

      - name: Execute Test
        run: |
          find src/ -name '*Test.php' | sort | awk "NR % ${{ matrix.parallelism }} == ${{ matrix.id }}" | xargs php ./bin/create_ci_phpunit_xml.php
          XDEBUG_MODE=coverage ./vendor/bin/phpunit --configuration /tmp/ci_phpunit.xml --coverage-php /tmp/phpunit-${{ matrix.id }}.cov

      - name: Archive Artifacts
        uses: actions/upload-artifact@v2
        with:
          name: phpunit-coverage
          path: /tmp/phpunit-${{ matrix.id }}.cov

  coverage:
    runs-on: ubuntu-20.04
    needs: test
    steps:
      - name: Checkout
        uses: actions/checkout@master

      - name: Install PHP with extensions
        uses: shivammathur/setup-php@v2
        with:
          php-version: 8.0
          coverage: pcov

      - name: Install Dependencies
        run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist

      - name: Download Coverage File
        uses: actions/download-artifact@v2
        with:
          name: phpunit-coverage
          path: ./tmp/

      - name: Merge Coverage Files
        run: ./vendor/bin/phpcov merge --clover coverage.xml ./tmp/

      - name: Upload Coverage to Codecov
        uses: codecov/codecov-action@v1
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./coverage.xml
          flags: unittests
          fail_ci_if_error: true
          verbose: true

追加/変更したところ

Install PHP with extensions
      - name: Install PHP with extensions
        uses: shivammathur/setup-php@v2
        with:
          php-version: 8.0
          coverage: pcov

こちらを使わずに coverage を取ろうとすると Warning: xdebug.mode=coverage has to be set in php.ini という警告が出て、正しく取れませんでした。

ローカルではなかったので、うーん。悩んでいたのですが、以下のスクリプトをみて、もしやと思い真似してみたらいけました。

github.com

pcovって知らなかったんですが、coverage を計測するドライバ?だそうです。

デフォルトがxdebug??なのかな?

それを pcovに変更したという感じだと思います。

github.com

Execute Test
        run: |
          find src/ -name '*Test.php' | sort | awk "NR % ${{ matrix.parallelism }} == ${{ matrix.id }}" | xargs php ./bin/create_ci_phpunit_xml.php
          ./vendor/bin/phpunit --configuration /tmp/ci_phpunit.xml --coverage-php /tmp/phpunit-${{ matrix.id }}.cov

例の awk のところは変わらず、最終的にphpunitを実行する箇所で --coverage-php /tmp/phpunit-${{ matrix.id }}.covカバレッジを取るようにしています。

Archive Artifacts
      - name: Archive Artifacts
        uses: actions/upload-artifact@v2
        with:
          name: phpunit-coverage
          path: /tmp/phpunit-${{ matrix.id }}.cov

codecovに上げる際に、各jobで実行してとった計測結果をマージしないといけません。

そのために、各jobでの計測結果を次のステップまでどこかに保存しておきたいわけです。

そのためにCIrcleCIでもある artifactsに保存するようにしました。

CoverageStep
  coverage:
    runs-on: ubuntu-20.04
    needs: test
    steps:
      - name: Checkout
        uses: actions/checkout@master

      - name: Install PHP with extensions
        uses: shivammathur/setup-php@v2
        with:
          php-version: 8.0
          coverage: pcov

      - name: Install Dependencies
        run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist

      - name: Download Coverage File
        uses: actions/download-artifact@v2
        with:
          name: phpunit-coverage
          path: ./tmp/

      - name: Merge Coverage Files
        run: ./vendor/bin/phpcov merge --clover coverage.xml ./tmp/

      - name: Upload Coverage to Codecov
        uses: codecov/codecov-action@v1
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./coverage.xml
          flags: unittests
          fail_ci_if_error: true
          verbose: true

最初の方は一緒ですが、重要なところは、 Download Coverage File Merge Coverage Files Upload Coverage to Codecov になります。

Download Coverage File でartifactにアップロードした各計測結果をダウンロードします。

Merge Coverage Files でダウンロードした各計測結果をマージします。

そして

Upload Coverage to Codecov でマージして生成された coverage.xmlを code covにアップロードします。

2. generateXMLスクリプトの編集

bin/create_ci_phpunit_xml.php

<?php

$baseDir = realpath('./');
$files   = array_slice($argv, 1);
$xmlFileStringData = [];
foreach ($files as $file) {
    $xmlFileStringData[] = "<file>{$baseDir}/{$file}</file>";
}
$testFileString = implode("\n", $xmlFileStringData);
$template = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" colors="true" bootstrap="{$baseDir}/bootstrap.php" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
  <coverage processUncoveredFiles="true">
    <include>
      <directory suffix=".php">{$baseDir}/src/Kojirock5260/*/src</directory>
    </include>
  </coverage>
  <testsuites>
    <testsuite name="Test Case">
      {$testFileString}
    </testsuite>
  </testsuites>
</phpunit>
XML;

file_put_contents("/tmp/ci_phpunit.xml", $template);

こちらはそこまで大幅に変わっていませんが、一点、前のままでやった場合、以下のような警告が出ました。

Warning:       Your XML configuration validates against a deprecated schema.
Suggestion:    Migrate your XML configuration using "--migrate-configuration"!

スキーマが非推奨になってるっぽいです。

実際に、指定されているオプションをつけて実行してみると、上記のようなxmlが生成されました。

結果

f:id:kojirooooocks:20210303045859p:plain

f:id:kojirooooocks:20210303045915p:plain

マージされた xmlで codecovにアップロードされていました!!!!

終わりに

思ったとおりに、出来て安心しました。

一点だけ、ちょっと。ん?と思ったのが、 codecovの xmlを指定する files という部分ですが、どうやら複数指定できるようでした。

もしかしたらマージしなくてもいいのかも?

とか思って、チャレンジしようかな?と思いましたが、ちょっとめんどくさいのでやめました...

現場からは以上です。

GithubActions で phpunit の並列実行

はじめに

こんばんは。

今回もテスト系の備忘録です。

以前 CircleCI でテストの並列実行を行った記事を書きました。

kojirooooocks.hatenablog.com

今回はこれのgithub action版です。

本題

CircleCIでは circleci test globcircleci test split みたいな並列実行を簡単に実装できるような機能が提供されてました。

github actionsでは似たような機能がなかったので自前実装で実装しました。

肝となる部分はこちらのコードを使わせてもらいました。ありがとうございます!!

gist.github.com

github actionのコードはこちら

.github/workflows/unittest.yml

name: unit test
on:
  push:

jobs:
  test:
    runs-on: ubuntu-20.04
    strategy:
      fail-fast: false
      matrix:
        parallelism: [5]
        id: [0,1,2,3,4]

    steps:
      - name: Checkout
        uses: actions/checkout@master

      - name: Install Dependencies
        run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist

      - name: Execute Test
        run: |
          find src/ -name '*Test.php' | sort | awk "NR % ${{ matrix.parallelism }} == ${{ matrix.id }}" | xargs php ./bin/create_ci_phpunit_xml.php
          ./vendor/bin/phpunit --configuration /tmp/ci_phpunit.xml

phpunitのymlを作成するコードはこちら

bin/create_ci_phpunit_xml.php

<?php

$baseDir = realpath('./');
$files   = array_slice($argv, 1);
$xmlFileStringData = [];
foreach ($files as $file) {
    $xmlFileStringData[] = "<file>{$baseDir}/{$file}</file>";
}
$testFileString = implode("\n", $xmlFileStringData);
$template = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" bootstrap="{$baseDir}/bootstrap.php">
    <testsuites>
        <testsuite name="Test Case">
            {$testFileString}
        </testsuite>
    </testsuites>
</phpunit>
XML;

file_put_contents("/tmp/ci_phpunit.xml", $template);

キモとなるのは awkのNR変数です。

NR変数は処理中の行数を取得できるので、現在処理中の行数と、並列max数の余りを求めて、それがそれぞれのjobIDと一致していれば、それぞれのjobに振り分けるという感じ...だと思います。

it-ojisan.tokyo

f:id:kojirooooocks:20210303015323p:plain

parallelism で設定している 数分のjobが実行されています!

f:id:kojirooooocks:20210303015338p:plain

f:id:kojirooooocks:20210303015349p:plain

また、実行しているjobの番号がそれぞれ変わっているのもわかります。

終わりに

circleci test split --timing とかはまたひねりが必要な感じだと思います。

タイミングはgithub actionsではどうすればよいのかな?

現場からは以上です。

phpunit組み込みのmockライブラリprophecyは非推奨になっていた

はじめに

こんばんは。

よく現場でも prophecy つかってたのですが、今の現場では phpunit7とかを触っていまして、最新版のphpunitを触っていませんでした。

今日偶然テスト系の調べ物していて、最新版のphpunitを触って気づきました...

ちなみにバージョンは 以下です。

$ ./vendor/bin/phpunit --version
PHPUnit 9.4.3 by Sebastian Bergmann and contributors.

本題

最新版のphpunitで prophecyを使用しているテストコードを回してみると以下の警告が出ました。

1) ExampleTest::register
PHPUnit\Framework\TestCase::prophesize() is deprecated and will be removed in PHPUnit 10. Please use the trait provided by phpspec/prophecy-phpunit.

v9では非推奨で、かつ、v10では削除されるとのこと...

v10で削除される旨は、phpunitChangeLogにも記載がありました。

github.com

f:id:kojirooooocks:20210227030133p:plain

issueはこちら

github.com

github.com

v9以降も使い続けるには、別途 phpspec/prophecy-phpunit を使用するとのこと。

使用方法は以下

  1. composer require --dev phpspec/prophecy-phpunit でライブラリをインストール
  2. prophesize() を使用しているテストコードで、 ProphecyTrait をuseする

これで、v9以降も通常通り prophecy が使用できます。

終わりに

最新版触ってないとどんどん置いて行かれているなと思い、ゾッとしました...

現場からは以上です。

local-php-security-checkerで使用しているライブラリの脆弱性チェック

はじめに

こんばんは。

今回もちょっと試してみたブログです。

Qiitaで脆弱性チェックのブログを見つけました。

これはいいなと思い、早速使ってみました。

実際先程のブログで紹介されているツールは archive になっていました。

代替ツールはないのかな?と探した所ありました。

github.com

本題

早速インストールして使ってみます。

僕は cloneして buildしました。

そして、試しのプロジェクトとして、cakephpの古めのバージョンを落としてみます。

$ composer create-project --prefer-dist cakephp/app:3.4.1 example341

早速実行してみます。

$ local-php-security-checker
Symfony Security Check Report
=============================

1 package has known vulnerabilities.

cakephp/cakephp (3.4.14)
------------------------

 * [CVE-2019-11458][]: Unsafe deserialization in SmtpTransport

[CVE-2019-11458]: https://bakery.cakephp.org/2019/04/23/cakephp_377_3615_3518_released.html

Note that this checker can only detect vulnerabilities that are referenced in the security advisories database.
Execute this command regularly to check the newly discovered vulnerabilities.

なんかでました!!

vrda.jpcert.or.jp

jvndb.jvn.jp

脆弱性として報告されているものでした。

ツールで表示されているURLを開くと、以下のサイトでした。

このリリースで治ったという感じですね。早速治ったバージョンをインストールしてみます。

bakery.cakephp.org

3.5.18にupdateして 再度チェックしてみます。

$ local-php-security-checker
Symfony Security Check Report
=============================

No packages have known vulnerabilities.

Note that this checker can only detect vulnerabilities that are referenced in the security advisories database.
Execute this command regularly to check the newly discovered vulnerabilities.

検知される脆弱性がなくなりました!

終わりに

かんたんなチェックですが、有名なツールに関してこれでチェックするのはいいかもと思いました。

現場からは以上です。

ngrokでポートが同じな複数のローカル環境を公開する

はじめに

こんばんは。

今回はめっちゃかんたんな ngrok の備忘録です。

本題

ローカルで dockerを使って複数のwebサービスを開発するさいに、よくあるのは、 ポートで振り分けたりするとおもいます。

ポートで振り分けている場合は、 ngrok http 5260ngrok http 5261 などで公開できると思います。

ただ、例えば以下のような場合、

admin => http://admin.kojirock.local:5260
toC   => http://kojirock.local:5260
toB   => http://business.kojirock.local:5260

ローカル用の ServerNameをつけつつ、かつ、80番ポート以外のポートも指定している場合、ちょっと工夫が必要でした。

答えとしては以下

[admin]
$ ngrok http -host-header=rewrite admin.kojirock.local:5260

[toC]
$ ngrok http -host-header=rewrite kojirock.local:5260

[toB]
$ ngrok http -host-header=rewrite business.kojirock.local:5260

ngrokのドキュメントの Rewriting the Host header にも記載がありました。

ngrok.com

Hostヘッダーを admin.kojirock.local とか kojirock.local に書き換えることで、該当のローカルの環境を公開できるようになりました。

終わりに

ngrok未だに便利で使用してるんですが、他の代替品ないかなーと調べたら結構ありました。

以前ブログで書いた Blimp もその仲間ですね。

kojirooooocks.hatenablog.com

ほかのやつも、ちかいうちに調べてみようと思います。

circleciでshallow clone して実行時間短縮

はじめに

こんばんは。

相棒の旅立ちからあまり立ち直っていない僕です。

今回は前回の記事にもあった circle ciの実行時間短縮の方法である shallow clone を試してみました。

本題

コードはすごく簡単で、 circle ciの checkout を使わず commandで git clone を実行します

- run:
  name: Setup Repository
  command: |
    git clone --depth 1 --single-branch --branch ${CIRCLE_BRANCH} "https://${GITHUB_TOKEN}:x-oauth-basic@github.com/owner/repository.git" ${HOME}/repository

もしくは、 こちらの orbsを使う感じかなと思います。

こちらも結局やっているのは上記のコマンドです。

通常の checkout

f:id:kojirooooocks:20210202021145p:plain

shallow clone

f:id:kojirooooocks:20210202021155p:plain

かなりの速度アップになりました!

ちなみに、github actionではどのくらい速度アップするのかな?と思っていたのですが、 github actionはデフォルトで shallow clone でした。

github.com

終わりに

巨大な歴史あるレポジトリだと、かなり効果があるのがわかりました。

あー手が痛い。。。

現場からは以上です。