はじめに
現場がユニットテストにフォーカスし始めたので、目検が多かったテスト文化をユニットテストに向けて深化させる(現場はphpだけど)。でもたしかに自動テスト文化になったほうが説得力が違う
参考
大事なこと
- プロダクションコード(実際のコード)がない状態でテストを作る
実際のコードを書く前に絶対に失敗するテストを書く(仮説を立てる)ことが大事
ディレクトリの状態
アプリケーションは複数ある
- gmarker
- kanban
- linebot
- register
- shopping
- vietnam_research
D:.
├─.idea
├─docs
├─favicon
├─import
├─mysite
│ ├─gmarker
│ │ └─tests.py
│ ├─kanban
│ │ └─tests.py
│ ├─linebot
│ │ └─tests.py
│ ├─mysite
│ │ └─tests.py
│ ├─register
│ │ └─tests.py
│ ├─shopping
│ │ └─tests.py
│ │─vietnam_research
│ │ └─tests.py
│ └─manage.py
└─venv
ユニットテストの書き方と実行
vietnam_research/tests.py
にテストを記述して python manage.py test
を実行。するとテストは失敗する。
例えば mixedproject
というリポジトリがあってそのフォルダがルート(.git
がある)状態になっていて、いろんなスクリプトフォルダがあるなかのひとつに hogeproject
があってそれがdjangoプロジェクトの起点だった場合、そもそもプロジェクトを分離して hogeproject
でリポジトリを作るべきである
なぜかテストが実行できなくて地獄を見るぞ。泣く。
自分のプロジェクトだとこんなことしないんだけど、人のリポジトリを間借りしてやってるときなんかはちょっと遠慮しちゃって ココニツクルネ なんてやりはじめるとこんなことになる
import文が hogeproject
から始まる感じになってたらあやしい
ただ、一応回避方法は発見した。
それは hogeproject
を起点としてIDEに読み込ませるという方法
from django.test import TestCase
class SmokeTest(TestCase):
def test_bad_maths(self):
self.assertEqual(1 + 1, 3) # 失敗
PyCharmのUIでテスト実行
コマンドラインからのテスト実行
現場でVSCodeしか使えないような場合の実行の仕方
Portfolio> cd mysite
Portfolio\mysite> python manage.py test
テスト中のデータベースのライフサイクル
さりげなく database を作って、最後に消していることがわかる。
テストデータベースのライフサイクルはテストコマンドの開始と終了で、 def
ごとに毎回 setup()
を実行してくれてそのたびにテーブルはロールバックされるが、pkは増え続けるので pk=1
みたいなテストを実行すると存在しないと言われてしまう。テーブルから pk
でひいてはいけない!
settings.py で指定したdatabaseログイン権限に データベース作成 権限がないとだめ。
ここまで聞くと簡単そうなんだけど、現場入りするとそもそもテストが走らないみたいな環境要因もあったりするよね。カオス。でも、環境要因が解決してから「どんなテストをすればいいか」というのが思いつかないともっとカオスだからこうして轍(わだち)を残すことが大事。テストっていったらスクリーンショットでしょって文化が僕にもあるけど、そんなものは変えていかないと。。。
テーブルにコンタクトできるか
No.1 テーブルは0件
from django.test import TestCase
from .models import Industry
class IndustryModelTests(TestCase):
def test_is_empty(self):
""" test No.1: テーブルは0件です"""
saved_industry = Industry.objects.all()
self.assertEqual(saved_industry.count(), 0)
OK(そらそうよ)
PS D:\OneDrive\dev\Portfolio\mysite> python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.007s
OK
Destroying test database for alias 'default'...
No.2 テーブルにレコードを1件増やす
from django.test import TestCase
from .models import Industry
class IndustryModelTests(TestCase):
def test_is_empty(self):
""" test No.1: テーブルは0件です"""
saved_industry = Industry.objects.all()
self.assertEqual(saved_industry.count(), 0)
def test_is_not_empty(self):
"""test No.2: 1つ登録すれば保存されたレコード数は1"""
industry = Industry()
industry.save()
saved_industries = Industry.objects.all()
self.assertEqual(saved_industries.count(), 1)
NG(cannot be null)
modelに NOT NULL 制約があるから pub_date
になんか日付入れないとだめだよ
PS D:\OneDrive\dev\Portfolio\mysite> python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.E
======================================================================
ERROR: test_is_not_empty (vietnam_research.tests.IndustryModelTests)
test No.2: 1つ登録すれば保存されたレコード数は1
----------------------------------------------------------------------
Traceback (most recent call last):
MySQLdb._exceptions.OperationalError: (1048, "Column 'pub_date' cannot be null")
django.db.utils.IntegrityError: (1048, "Column 'pub_date' cannot be null")
----------------------------------------------------------------------
Ran 2 tests in 0.189s
FAILED (errors=1)
Destroying test database for alias 'default'...
テストコード修正
from django.test import TestCase
+ from django.utils.timezone import now
from .models import Industry
class IndustryModelTests(TestCase):
def test_is_empty(self):
""" test No.1: テーブルは0件です"""
saved_industry = Industry.objects.all()
self.assertEqual(saved_industry.count(), 0)
def test_is_not_empty(self):
"""test No.2: 1つ登録すれば保存されたレコード数は1"""
industry = Industry()
+ industry.pub_date = now()
industry.save()
saved_industries = Industry.objects.all()
self.assertEqual(saved_industries.count(), 1)
OK
たった2文字なのにこの湧き上がる安心感よ。
PS D:\OneDrive\dev\Portfolio\mysite> python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 2.859s
OK
Destroying test database for alias 'default'...
No.3: 入れる前のデータと入れたあとのデータは等しい
from django.test import TestCase
from django.utils.timezone import now
from .models import Industry
class IndustryModelTests(TestCase):
def test_is_empty(self):
""" test No.1: テーブルは0件です"""
saved_industry = Industry.objects.all()
self.assertEqual(saved_industry.count(), 0)
def test_is_not_empty(self):
"""test No.2: 1つ登録すれば保存されたレコード数は1"""
industry = Industry()
industry.pub_date = now()
industry.save()
saved_industries = Industry.objects.all()
self.assertEqual(saved_industries.count(), 1)
+ def test_saving_and_get_industry(self):
+ """test No.3: 入れる前のデータと入れたあとのデータは等しい"""
+ first_industry = Industry()
+ market_code, symbol, company_name = "HOSE", "AAA", "アンファット・バイオプラスチック"
+ first_industry.name = market_code
+ first_industry.page = symbol
+ first_industry.publisher = company_name
+ first_industry.save()
+ saved_industries = Industry.objects.all()
+ actual_industry = saved_industries[0]
+ self.assertEqual(actual_industry.market_code, market_code)
+ self.assertEqual(actual_industry.symbol, symbol)
+ self.assertEqual(actual_industry.company_name, company_name)
NG
あれ?予想外のエラーだな。テーブルにデータが入ってない?
PS D:\OneDrive\dev\Portfolio\mysite> python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..F
======================================================================
FAIL: test_saving_and_get_industry (vietnam_research.tests.IndustryModelTests)
test No.3: 入れる前のデータと入れたあとのデータは等しい
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\OneDrive\dev\Portfolio\mysite\vietnam_research\tests.py", line 32, in test_saving_and_get_industry
self.assertEqual(actual_industry.market_code, market_code)
AssertionError: '' != 'HOSE'
+ HOSE
----------------------------------------------------------------------
Ran 3 tests in 0.065s
FAILED (failures=1)
Destroying test database for alias 'default'...
とりあえず print
を入れてみて...
from django.test import TestCase
from django.utils.timezone import now
from .models import Industry
class IndustryModelTests(TestCase):
def test_is_empty(self):
""" test No.1: テーブルは0件です"""
saved_industry = Industry.objects.all()
self.assertEqual(saved_industry.count(), 0)
def test_is_not_empty(self):
"""test No.2: 1つ登録すれば保存されたレコード数は1"""
industry = Industry()
industry.pub_date = now()
industry.save()
saved_industries = Industry.objects.all()
self.assertEqual(saved_industries.count(), 1)
def test_saving_and_get_industry(self):
"""test No.3: 入れる前のデータと入れたあとのデータは等しい"""
first_industry = Industry()
market_code, symbol, company_name = "HOSE", "AAA", "アンファット・バイオプラスチック"
first_industry.name = market_code
first_industry.page = symbol
first_industry.publisher = company_name
first_industry.pub_date = now()
first_industry.save()
saved_industries = Industry.objects.all()
actual_industry = saved_industries[0]
+ print("actual_industry.market_code: ", actual_industry.market_code)
self.assertEqual(actual_industry.market_code, market_code)
self.assertEqual(actual_industry.symbol, symbol)
self.assertEqual(actual_industry.company_name, company_name)
検証
def test_saving_and_get_industry(self):
"""test No.3: 入れる前のデータと入れたあとのデータは等しい"""
first_industry = Industry()
market_code, symbol, company_name = "HOSE", "AAA", "アンファット・バイオプラスチック"
first_industry.name = market_code
first_industry.page = symbol
first_industry.publisher = company_name
first_industry.pub_date = now()
first_industry.save()
saved_industries = Industry.objects.all()
self.assertEqual(saved_industries.count(), 1)
# actual_industry = saved_industries[0]
# self.assertEqual(actual_industry.market_code, market_code)
# self.assertEqual(actual_industry.symbol, symbol)
# self.assertEqual(actual_industry.company_name, company_name)
この時点ではたしかに入っているな。
PS D:\OneDrive\dev\Portfolio\mysite> python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
...
----------------------------------------------------------------------
Ran 3 tests in 0.066s
OK
Destroying test database for alias 'default'...
def test_saving_and_get_industry(self):
"""test No.3: 入れる前のデータと入れたあとのデータは等しい"""
first_industry = Industry()
market_code, symbol, company_name = "HOSE", "AAA", "アンファット・バイオプラスチック"
first_industry.name = market_code
first_industry.page = symbol
first_industry.publisher = company_name
first_industry.pub_date = now()
first_industry.save()
saved_industries = Industry.objects.all()
+ print(Industry.objects.all())
# actual_industry = saved_industries[0]
# self.assertEqual(actual_industry.market_code, market_code)
# self.assertEqual(actual_industry.symbol, symbol)
# self.assertEqual(actual_industry.company_name, company_name)
入ってる... もしかしてもとにした記事が 2014年
だってことが影響してる??(書き方が違う?自分のソースコード確認するのが一番早いか...ゴソゴソ)
def test_saving_and_get_industry(self):
"""test No.3: 入れる前のデータと入れたあとのデータは等しい"""
first_industry = Industry()
market_code, symbol, company_name = "HOSE", "AAA", "アンファット・バイオプラスチック"
- first_industry.name = market_code
+ first_industry.market_code = market_code
- first_industry.page = symbol
+ first_industry.symbol = symbol
- first_industry.publisher = company_name
+ first_industry.company_name = company_name
first_industry.pub_date = now()
first_industry.save()
saved_industries = Industry.objects.all()
actual_industry = saved_industries[0]
self.assertEqual(actual_industry.market_code, market_code)
self.assertEqual(actual_industry.symbol, symbol)
self.assertEqual(actual_industry.company_name, company_name)
OK
いい検証になったわ...
PS D:\OneDrive\dev\Portfolio\mysite> python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
...
----------------------------------------------------------------------
Ran 3 tests in 0.065s
OK
Destroying test database for alias 'default'...
URL解決のテスト
vietnam_research
は index.html
しかないんでね
class UrlResolveTests(TestCase):
def test_url_resolves_to_book_list_view(self):
"""test No.4: /では、indexが呼び出される事を検証"""
found = resolve('/')
self.assertEqual(found.func, index)
OK
PS D:\OneDrive\dev\Portfolio\mysite> python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
....
----------------------------------------------------------------------
Ran 4 tests in 0.069s
OK
Destroying test database for alias 'default'...
フォームのテスト(正しいHTMLが返されているか)
def test_exchange_calc(self):
"""test No.8: 残高4,029,139VND, 単価50,000, 口数200のときに足りない額は7,290,861"""
update_url = '/'
# GET the form
r = self.client.get(update_url)
# retrieve form data as dict
form = r.context['exchange_form']
# manipulate some data
data = form.initial # form is unbound but contains data
data['current_balance'] = 4029139
data['unit_price'] = 50000
data['quantity'] = 200
# POST to the form
r = self.client.post(update_url, data)
self.assertContains(r, 'q=-7290861vnd')