【Django】DjangoによるWeb開発プロジェクトPart6.5 - CIによるコードスタイルチェックと自動テスト -

## はじめに

FreeBSDにおける準仮想化機構`jail`をWeb上で構築できるプラットフォーム「jaisting」をパブリックリリースする運びとなりました。
概要についてはこちら→https://note.mu/himrock922/n/n8e35b1d70e19
リポジトリはこちら→https://github.com/himrock922/jaisting

本格的にリリースする運びとなりましたので、そろそろテストをゴリゴリ書いていこうと思います。今回はテストをコーディングするまでに必要な環境を構築するまでの手順を執筆したいと思います。

## Cirrus-CIによるFreeBSDマシンの構築

今回のプロジェクトでは、FreeBSD特有の機能をフルに利用したWebアプリケーションとなります。そのため、CIでテスト駆動するとは言え、前提としてFreeBSDをサポートしているCIサービスを利用しなければなりません。そのため、まず初めにFreeBSDをサポートしているCIサービスがそもそもあるのか調べる事が先決となりました。

CIサービスでは、今の所`cirrus-ci`がFreeBSDをサポートしている模様なので、これを使いたいと思います。cirrus-ciはGitHub MarketPlaceでも提供されており、リポジトリ単位でcirrus-ciを使うかどうかフラットに決められるます。
構築手順のドキュメントはこちら→* [https://cirrus-ci.org/guide/FreeBSD/](https://cirrus-ci.org/guide/FreeBSD/)

```yaml:.cirrus.yml
freebsd_instance:
  image: freebsd-12-0-release-amd64

task:
  install_script:
    - pkg update
    - pkg install -y python37 py36-psycopg2 py36-ucl rsync git
  check_script:
    - mount
  script:
    - pkg install -y postgresql11-server yarn
    - sysrc postgresql_enable=YES
    - service postgresql initdb
    - service postgresql start
    - sudo -u postgres createuser jaisting
    - sudo -u postgres psql -c "alter user jaisting with encrypted password 'jaisting'"
    - sudo -u postgres psql -c "alter role jaisting with createdb"
    - python3.7 -m ensurepip
    - python3.7 -m pip install Cython==0.29.5
    - python3.7 -m pip install -r packages.txt
    - yarn install
    - ./node_modules/.bin/webpack --config webpack.config.js --mode=development
    - flake8 ./**/*.py
    - python3.7 manage.py test
```

CIで行う事は大まかに分けて二つのみです。

1. コードスタイルチェック
2. テスト実行

なお、テスト実行については今回のプラットフォームが、ZFSでホストOSのファイルシステムを構築する事が必須となりますので、後々ZFSによるストレージプールをCIでも構築できるようにしなければなりません(今回の開発に限らず、FreeBSDで新規に何か開発しようとするとZFSによるファイルシステム構築が必須になりかけている)。
とりあえずは、一旦現状の設定でできるところまでテストを書いていきます。

## コードスタイルチェック

今回のPythonファイルのコードスタイルのチェックとして、スタイルガイドについての文書である[PEP8](https://www.python.org/dev/peps/pep-0008/)と、それ以外の論理的なエラーをチェックするツールである[pyflakes](https://github.com/PyCQA/pyflakes#design-principles)と循環的複雑度をチェックできるラッパーである[flake8](https://bitbucket.org/tarek/flake8/overview)を採用しました。
また、プロジェクト単位でチェックする設定を決めるには`setup.cfg`に記載した設定があると自動的に読み込まれるようになります。

```setup.cfg
[flake8]
ignore = D100, D101, D102, D103, F401
max-line-length = 150
exclude = **/migrations/*, **/__init__.py
```

とりあえず、今回のプロジェクトではGitHubでソースコードを見る事が多いと思うので行の最大長だけギリギリスクロールが発生しない程度まで制限を緩和しておきます。

## テスト実行

実際にテストを書いていきましょう。初めは単純にログイン認証が必要な画面でログインしていない場合にアクセスすると、ログイン画面にリダイレクトするか検証するテストを書いて実行します。

```
% python3.7 manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).

----------------------------------------------------------------------
Ran 0 tests in 0.000s
```

test.pyに書いたテストケースが実行されていませんね。調べてみると、[どうもメソッド名に`test`のプレフィックスをつけないと実行されない模様です](https://stackoverflow.com/questions/2037364/django-test-runner-not-finding-tests)(これ、実はあんまり納得してないです。メソッド名に制約をつけてしまうと汎用性なくなるんじゃないかなと、ちょっと考えすぎな気もしますが)。

* 修正前

```python:jails/test.py
class JailsViewTest(TestCase):
    def login_require_jails(self):
        response = self.client.get('/jails/')
        self.assertRedirects(response, '/accounts/login/?next=/jails/')
```

* 修正後

```python:jails/test.py
% python3.7 manage.py test 
class JailsViewTest(TestCase):
    def test_login_require_jails(self):
        response = self.client.get('/jails/')
        self.assertRedirects(response, '/accounts/login/?next=/jails/')
```

```
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.081s

OK
Destroying test database for alias 'default'...
```

```python:jails/tests.py
from django.test import TestCase, Client
from django.contrib.auth import get_user_model


class TemplateTest(TestCase):
    def test_login_require_jails(self):
        response = self.client.get('/jails/')
        self.assertRedirects(response, '/accounts/login/?next=/jails/')

    def test_login_require_jail_new(self):
        response = self.client.get('/jails/new')
        self.assertRedirects(response, '/accounts/login/?next=/jails/new')

    def test_access_jails(self):
        User = get_user_model()
        self.client = Client()
        self.client.force_login(User.objects.create_user('user'))
        response = self.client.get('/jails/')
        self.assertTemplateUsed(response, 'jails/index.html')
        self.client.logout()

    def test_access_jail_new(self):
        User = get_user_model()
        self.client = Client()
        self.client.force_login(User.objects.create_user('user'))
        response = self.client.get('/jails/new')
        self.assertTemplateUsed(response, 'jails/new.html')
        self.client.logout()
```

```python:networks/tests.py
from django.test import TestCase


class TemplateTest(TestCase):
    def test_login_require_networks_new(self):
        response = self.client.get('/networks/new')
        self.assertRedirects(response, '/accounts/login/?next=/networks/new')
```

## まとめ

VMでCIテスト駆動やるの結構辛いです。今回のcirrus-ciのプランでは[パブリックリポジトリの場合、無料で行えるため](https://cirrus-ci.org/pricing/)文句言えないですが、、、
むしろ、今回のプラットフォームでCI用のコンテナ作れると嬉しいかもしれませんね。それをするにはやはりスケーリング機能も重要になってきますが。
まだまだ、やる事いっぱいあるので、今回はこの辺で

## シリーズ

[Part6](https://qiita.com/himrock922/items/5ea559e8d006449f6145) ← [Part6.5](https://qiita.com/himrock922/items/5b8f39a61de9b640d840) → [Part7](https://qiita.com/himrock922/items/28dc4d0178fc0bba81a9)

## 参考文献

* [https://cirrus-ci.org/guide/FreeBSD/](https://cirrus-ci.org/guide/FreeBSD/)
* [https://blog-ja.sideci.com/entry/python-lint-pickup-5tools](https://blog-ja.sideci.com/entry/python-lint-pickup-5tools)
* [https://qiita.com/zaki-yama/items/d05adce9d23d67144fbf](https://qiita.com/zaki-yama/items/d05adce9d23d67144fbf)
* [https://stackoverflow.com/questions/2037364/django-test-runner-not-finding-tests](https://stackoverflow.com/questions/2037364/django-test-runner-not-finding-tests)