Django で unittest を高速化する(主にDBの話し)
yuheiさんが書き残してくれたありがたい記事を参考にゴニョゴニョしてて気になって事があったので、調べてみた時のメモ
この記事には以下の対応が書かれていました。
- 対応2 sqlite3のin-memory databaseにしてみる
- 対応3 southでテスト時にmigrateを使わず、syncdbでテーブル作成を行わせる
この記事の結果を見た時に、あれ?これってそんなに効果ないんだっけ?て気になったのでちょっと調べてみた次第。
前提&環境
やりたい事
- ローカルでテスト全体を走らせた時に速く終らせたい(コミット&プッシュ前とかに良くするので)
- 主に利用するDBを変えてみる事でどれくらい実行時間が変わるか知りたい
- Southのマイグレーションをテスト時にオフする事で実行時間が短縮されるか知りたい
1. MySQL(Migration有り)
- まずは素のMySQLでテストを走らせる
- manage.py testで出力されるテスト実行時間と体感時間にかなり差があるのでtimeコマンド付きで実行する。
$ time python manage.py test --settings=hoge.settings.test
結果
# manage.py testが吐き出す結果 Ran 265 tests in 29.224s # timeコマンドで計測した結果 real 3m10.899s user 0m17.511s sys 0m1.450s
- テスト実行は29秒で終ってんのに、実際は3分もかかってやがる。
- Djangoはテスト時にtest_で始まるテスト用のDB作成/破棄してる。多分その時間はmanage.py testで吐き出される時間にはインクルードされてない。
2. MySQL(Migration無し)
- 次にSouthのマイグレーションをOFFにしてやってみる
- 設定ファイル(settings/test.py)に以下の設定を書くだけ
SOUTH_TESTS_MIGRATE=False
結果
# manage.py test Ran 265 tests in 18.015s # time real 1m57.275s user 0m10.954s sys 0m1.049s
- テストの実行自体も早くなったが、コマンドの実行時間自体も1分以上早くなった。体感的に全然ちがう。
3. MySQL on ramdisk (Migration有り)
- Macでtmpfs的な事をやる時はhdidとかいうコマンドでramdiskを作れる
- OSX で tmpfs 的なことをする方法 - unknownplace.org
- そこでMySQLを動かせばテストも速くなるのではないか。
$ hdid -nomount ram://10670000 $ newfs_hfs /dev/disk2 # -> 大体6GBのヤツできる $ mkdir /tmp/mnt $ mount -t hfs /dev/disk2 /tmp/mnt $ cp -pr /usr/local/var/mysql /tmp/mnt/ $ mysql.server stop $ mysql.server start --datadir=/tmp/mnt/mysql
結果
# manage.py test Ran 265 tests in 17.435s # time real 0m34.779s user 0m19.037s sys 0m1.342s
- やだー速いじゃないですかーやだーもー。
- テスト実行自体は大して速くなってないけど、コマンドの実行時間は大分短くなってストレスなくなってきた。
4. MySQL on ramdisk (Migration無し)
- さっきと同じようにMigrationをオフってみる
結果
# manage.py test Ran 265 tests in 16.330s # time real 0m28.271s user 0m13.639s sys 0m1.067s
- 僅かではあるがMigration有りの時より速くなってる。
5. SQLite(Migration有り)
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': 'unittest.db', }, }
結果
# manage.py test Ran 265 tests in 12.166s # time real 0m22.653s user 0m14.788s sys 0m0.810s
- MigrationがあってもMySQLより速くなった
6. SQLite(Migration無し)
- 例によってMigrationをOFFる
結果
# manage.py test Ran 265 tests in 11.209s # time real 0m17.247s user 0m14.788s sys 0m0.810s
- 今までと同様にMigration無い方が実行時間は短くなった
7. SQLite in-memory (Migration有り)
- 最終形態。SQLiteをin-memoryで利用する
- さっきのDATABASE設定のNAMEを":memory:"にするだけ
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:', }, }
結果
# manage.py test Ran 265 tests in 10.511s # time real 0m16.613s user 0m14.579s sys 0m0.647s
- SQLiteのMigration無しと大差ない結果だった。
8. SQLite in-memory (Migration無し)
- 最後にMigration無しの試す
結果
# manage.py test Ran 265 tests in 10.208s # time real 0m11.963s user 0m10.202s sys 0m0.456s
- テスト実行時間自体は大して変わらんけど、コマンド実行時間は10秒前後にまで縮まって、ほぼテスト実行時間とイコールになった。
まとめ
8パターンの計測を行った、最後にそれぞれのパターンを複数回(10回程度)実行して、その中央値を表にしてみた。
- mange.py test が出してるテスト実行時間には、テストDBの作成/破棄の時間は含まれない
- 同様にMigrationしてる間の時間も含まれないのでMigrationをOFFにした方が実際の時間は短縮される
- テーブル数、Migration数が多いほど、SQLite in-memoryでテスト実行時間が短くなってるのが実感できるのでオヌヌメ
- ストレスフリーになってよかったな > 俺