[Django] 自動テストについてのまとめ

## この記事について

PythonのWebアプリケーションフレームワーク「Django」の自動テスト機能についてまとめてみました。


## 公式サイトのテスト関連ページ

- [テストチュートリアル](https://docs.djangoproject.com/ja/2.1/intro/tutorial05/)
- [Django におけるテスト](https://docs.djangoproject.com/ja/2.1/topics/testing/)
 - [テストを書いて実行する](https://docs.djangoproject.com/ja/2.1/topics/testing/overview/)
 - [テストツールリファレンス](https://docs.djangoproject.com/ja/2.1/topics/testing/tools/)
 - [テストに関する応用的なトピック](https://docs.djangoproject.com/ja/2.1/topics/testing/advanced/)


## テストを書く場所

`manage.py startapp`実行時に作成される`tests.py`に記述します。本格的なプロジェクトでは`tests`をパッケージ化し、その下にtest_XXX.pyという名前でテストを分割するのが一般的です。`unittest`の規約にのっとり、プロジェクト内のファイル名が`test`で始まるファイルが自動テストの対象となります。

## テストの実行方法

Djangoでは管理コマンド`manage.py`より自動テストが実行できる。

コマンド

```
manage.py test
```

ヘルプ表示

```
manage.py help test

usage: manage.py test [-h] [--version] [-v {0,1,2,3}] [--settings SETTINGS]
                      [--pythonpath PYTHONPATH] [--traceback] [--no-color]
                      [--noinput] [--failfast] [--testrunner TESTRUNNER]
                      [-t TOP_LEVEL] [-p PATTERN] [-k] [-r] [--debug-mode]
                      [-d] [--parallel [N]] [--tag TAGS]
                      [--exclude-tag EXCLUDE_TAGS]
                      [test_label [test_label ...]]
```

### コマンドのオプション

- **-v {0,1,2,3} --verbosity {0,1,2,3}**
実行ログの詳細レベル。レベル３にするとデータベースの変更タイミング等がわかるので、最初の内はこのオプションを使うと理解が早い。

- **-k --keepdb**
**自動テストのデフォルト動作では、毎回データベースを再作成する**。このオプションを使うと再作成を回避してデータベースを削除せずに残し、次回実行時に再利用する。

- **--settings SETTINGS**
単一の設定ファイル(settings.py)のままだと本番環境、開発環境、自動テスト環境を個別に設定することが難しいので、自動テスト用に個別に設定ファイルを作成するのがベストプラクティスとされている。設定ファイルの分割方法については [こちら](https://qiita.com/okoppe8/items/e60d35f55188c0ab9ecc) を参照。データベースの設定方法は後述。

### 実行範囲の指定

自動テストでは実行単位を設定できる。

```
# アプリケーション単位
$ ./manage.py test app

# ファイル単位
$ ./manage.py test app.tests

# テストクラス単位
$ ./manage.py test animals.tests.testcase

# メソッド単位
$ ./manage.py test animals.tests.AnimalTestCase.test_can_something
```

### テストのデバッグ方法

pycharmなら自動テストをデバッグモードで実行できる。
参考：[Creating Run/Debug Configuration for Tests](https://www.jetbrains.com/help/pycharm/creating-run-debug-configuration-for-tests.html)

## テストの動作

### 処理の流れ

![image.png](https://qiita-image-store.s3.amazonaws.com/0/217094/91dd3f78-30aa-86a1-1961-96f43eb9efe2.png)


### テストケースクラス

Djangoの自動テストはPython標準のテストモジュール`unittest`機能を拡張したものである。テストケースは`unittest.TestCase`の継承クラスである`django.test.TestCase`を継承して記述する。`unittest.TestCase`との違いは以下の通り。

- テストデータの準備にフィクスチャファイルとデータ準備メソッド（setUpTestData）の２つが使える。各処理はテストケースの実行前に一度だけ実行される。

- 各テストケースはトランザクションで実行され最終的にロールバックされる。また、各テストメソッドも前述のトランザクション内の子トランザクションとして実行され、メソッドごとにロールバックされる。これによりテストケース、テストメソッド毎の独立性が担保される。

### データベースの設定

settings.py上での自動テストのデータベース設定です。開発環境と同じデータベースサーバに別のデータベースを作成することを前提としています。テスト用の設定ファイルを単独で用意する場合は若干違和感のある設定となります。

参考：[公式:test データベース](https://docs.djangoproject.com/ja/2.1/topics/testing/overview/#the-test-database)

```python:settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': '',  
        'USER': 'username',
        'PASSWORD': 'password',
        'HOST': 'localhost',
        'PORT': '',
        'TEST': {
            'NAME': 'test_database_name',
        }
    }
}
```

なお、TEST.NAMEでテスト用データベース名を指定しなかった場合のデフォルトは以下の通りです。

SQLite3の場合　　：インメモリデータベースを作成（ファイルは作成されない）
SQLite3以外の場合：メインのデータベース名に「test_」をプレフィックスした名前


**注意：デフォルトの動作ではテスト実行の度にデータベースの作成と削除を行います。よって接続ユーザーにデータベースの作成・削除権限が必要です。--keepdbオプションを使って運用するなら読み取り権限だけでOKです。**

### 参考：テストデータをコミットさせる方法

自動テストの開発時やマルチスレッド処理などの特殊なテストを行う際に、データベースをロールバックさせずにコミットさせたい場合があります。そのような時は、「django.test.TestCase」ではなく「unittest.TestCase」を使えばOKです。

Djangoの自動テストの実行対象は「unittest.TestCase」を継承した全てのクラスなので、混在していても大丈夫です。「django.test.TestCase」の継承クラスを実行したあとで、「unittest.TestCase」の継承クラスがまとめて実行されるように順番が制御されています。

## テストの書き方・ライブラリの紹介

### テストデータの作成

テストデータにはフィクスチャファイルが利用できますが、以下の欠点があります。

- データの場所がテストコードと違うファイルになり確認しずらい。
- テキストデータはIDEのサポートが得られにくく管理が大変。

というわけで、テストデータは`factory-boy`というライブラリを使い、テストコード上で定義したほうが合理的とされています。`factory-boy`はテストデータの値を具体値ではなくロジックとして記述することもできるので、複雑な値や構造を持つデータもフィクスチャよりも簡単に作成できます。

参考リンク：

- [Github factory_boy](https://github.com/FactoryBoy/factory_boy)
- [くろねこ日記 テストデータ作成に便利なFactoryBoyをつかってみた](http://kuroneko0208.hatenablog.com/entry/2015/01/25/031727)
- [Python: factory-boy でテストに使うオブジェクトを作る](http://momijiame.tumblr.com/post/108317509611/python-factory-boy-%E3%81%A7%E3%83%86%E3%82%B9%E3%83%88%E3%81%AB%E4%BD%BF%E3%81%86%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E4%BD%9C%E3%82%8B)
- [factory-boyとDjangoでテストを書くときにファイルをうまく使う](https://blog.hirokiky.org/entry/2018/07/12/120514)
 - FileFieldにダミーデータを設定する方法を紹介

### カバレッジの計測

カバレッジ（テストのコード網羅率）の計測には「Coverage.py」を使います。
以前は`django-nose`、`django-coverage`といったパッケージが利用されましたが、現在は公式でも「Coverage.py」を推奨しています。

- [Github Coverage.py] (https://github.com/nedbat/coveragepy/blob/coverage-4.5.1a/doc/index.rst)
- [Django公式: Integration with coverage.py](https://docs.djangoproject.com/ja/2.1/topics/testing/advanced/#integration-with-coverage-py)
- [もた日記 Djangoメモ(26) : coverage.pyでカバレッジ（網羅率）を計測](https://wonderwall.hatenablog.com/entry/2018/03/26/003000)
- [Qiita:Coverage.pyでDjangoアプリケーションのカバレッジを計測する](https://qiita.com/csakatoku/items/32bbfacbf85e43f1950d)

使い方は簡単で、`pip install coverage`でインストール後、Coverage.py経由でテストを実行して結果ファイルを作成し、レポートをコンソールで表示したり、HTMLで出力させたりします。除外対象の設定等はリンク先を参照してください。

```shell
# テスト実行
coverage run --source='.' manage.py test myapp
# レポート出力
coverage report
# HTML出力
coverage html
```

### ユニットテスト

ユニットテストは python の標準的な unittest の書き方を参考にしてください。
とりあえず以下の記事がよく整理されています。

- [Pythonのデバッグとテストモジュール](https://qiita.com/0xfffffff7/items/b0c3a747522943df434f)
- [Python Django チュートリアル(7)](https://qiita.com/maisuto/items/cce169a2455b116e2f82)

以下の記事も参考になりました。

- [unittest 単体テスト入門 その1 基本的な使い方](https://www.python.ambitious-engineer.com/archives/828)
- [python標準のunittestの使い方メモ](https://qiita.com/aomidro/items/3e3449fde924893f18ca)
- [Djangoのテストの書き方について勉強したのでまとめる](https://nwpct1.hatenablog.com/entry/how-to-write-unittest-on-django)


### 統合テスト

Djangoのテストフレームワークには`django.test.Client`というモジュールが用意されています。これを使うとDjangoアプリケーションに対して仮想的なHTTPリクエストを送信し、レスポンスを受け取るとることができます。Webサーバを起動せずにテストできるので、実際のブラウザを使ったUIテストより低コストです（ただしJavaScriptの実行は不可）。

- [Django公式：テストツール：テストクライアント](https://docs.djangoproject.com/ja/2.1/topics/testing/tools/#the-test-client)

しかし`Ruby On Rails`で同様の役割を担う`Capybara`では、Djangoのテストクライアントにはない以下の機能があり、テストの自由度に不満がでます。

- CSSセレクタを使って特定のDOMオブジェクトを取得する。
- aタグをクリックしてハイパーリンク先に遷移する。

そこでお薦めするのがライブラリ`django-webtest`です。
pythonのライブラリ`webtest`をDjangoで使えるようにしたものです。

これを使うとレスポンスのHTMLページを、スクレイピングで利用されている`Beautiful Soup`のオブジェクトとして扱えるので、CSSセレクタやaタグのハイパーリンク先への遷移が簡単にできるようになります。

参考：

- [Github：django-webtest](https://github.com/django-webtest/django-webtest)
- [webtest：Testing Applications with WebTest](https://docs.pylonsproject.org/projects/webtest/en/latest/testresponse.html)
- [メモ的な思考的な：Pythonで、WebTestを使って、WSGIサーバを起動せずにWSGIアプリのテストをする](http://thinkami.hatenablog.com/entry/2017/03/27/062033)
- [Qiita：PythonとBeautiful Soupでスクレイピング](https://qiita.com/itkr/items/513318a9b5b92bd56185)

### UIテスト

DjangoでもSeleniumを使ったUIテストが可能です。DjangoではSeleniumを使ったテスト用のモジュールを用意しています。公式サイトを参考にテストケースを作成してください。

- [Django公式：LiveServerTestCase](https://docs.djangoproject.com/ja/2.1/topics/testing/tools/#liveservertestcase)

以上です。