Faker
POSTの正常系のテストができるようになりましたが、これではまだ1パターンのデータでしかテストができていません。もしかしたら偶然うまくいっているだけで、違うデータをPOSTしたら失敗する可能性も考えられます。固定値を使うのではなく、より現実に近いランダムな値で検査してみましょう。
ここではFakerというサードパーティのパッケージを使って、テストデータの生成を行います。
それではまず、Fakerをインストールしてみましょう。
(env) $ pip install faker (env) $ vi requirements/test.txt (env) $ cat requirements/test.txt -r base.txt faker==0.8.16
requirements/test.txtというファイルを新しく作成して、テスト用のパッケージをここで管理するようにします。
faker.Faker()の引数にlocaleを渡して初期化して、各用途に応じたメソッドを呼び出せばOKです。
>>> from faker import Faker
>>> fake = Faker()
>>> fake.name()
'Rebecca Lopez'
>>> fake.name()
'Penny Obrien'
>>> fake.address()
'9645 Kim Parkways\nLake Scott, UT 34764'
>>> fake = Faker('ja_JP')
>>> fake.name()
'笹田 健一'
>>> fake.name()
'山本 太郎'
>>> fake.address()
'佐賀県四街道市台場7丁目15番10号 池之端アーバン120'
Fakerを使って、さきほどのTicketTestsをランダムなデータによるテストに書き換えます。data変数を以下のように編集しましょう。またstatusの値に関しては、IntEnumであるTicketStatusとrandom.choice()を組み合わせて1〜3の範囲内で値を取得するようにしています。
import json
import random
from django.test import TestCase
from django.urls import reverse
from faker import Faker
from rest_framework import status
from .constants import TicketStatus
from .models import Ticket
class TicketTests(TestCase):
def test_post(self):
fake = Faker()
url = reverse('ticket-list')
data = {
'assignee': None,
'name': fake.word(),
'description': fake.text(),
'status': random.choice([x.value for x in TicketStatus]),
'start': str(fake.date_object()),
'end': None,
}
response = self.client.post(url, json.dumps(data), content_type='application/json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Ticket.objects.count(), 1)
self.assertEqual(Ticket.objects.get().name, 'test ticket')
これを実行してみると、次のようなエラーが返ってきてテストが失敗してしまいます。
(env) $ python manage.py test kanban
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_post (board.tests.TicketTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/massa142/src/github.com/massa142/modern-django/kanban/board/tests.py", line 28, in test_post
self.assertEqual(Ticket.objects.get().name, 'test ticket')
AssertionError: 'worry' != 'test ticket'
- worry
+ test ticket
----------------------------------------------------------------------
Ran 1 test in 0.037s
FAILED (failures=1)
Destroying test database for alias 'default'...
この原因は、作成したチケットのnameの期待値をtest ticketから変更していないためです。28行目を次のように修正して、再びテストが成功することを確認してください。
self.assertEqual(Ticket.objects.get().name, data['name'])
境界値分析
POSTの正常系のテストができたので次は、TicketSerializerで実装した開始日と終了日に関するバリデーションをテストしましょう。
class TicketSerializer(serializers.ModelSerializer):
...
def validate(self, data):
start = data['start']
end = data['end']
if start and end and start > end:
raise serializers.ValidationError('終了日は開始日以降に設定してください')
return data
このバリデーションがきちんと動いているかを確かめるために、妥当ではないdataをPOSTするテストを書きます。
def test_validate_opposite_dates(self):
fake = Faker()
url = reverse('ticket-list')
data = {
'assignee': None,
'name': fake.word(),
'description': fake.text(),
'status': random.choice([x.value for x in TicketStatus]),
'start': str(fake.future_date()),
'end': str(fake.past_date()),
}
response = self.client.post(url, json.dumps(data), content_type='application/json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(Ticket.objects.count(), 0)
self.assertEqual(response.data, {'non_field_errors': ['終了日は開始日以降に設定してください']})
fake.future_date()、fake.past_date()を用いて、開始日に未来の日付を、終了日に過去の日付をセットしています。
先ほどとは異なり、ここでは以下の3つのアサーションを行っています。
- ステータスコードが400かどうか
- 作成されたレコードがないかどうか
- エラーメッセージが正しいかどうか
これでバリデーションが期待した動きになっているかを確かめることができました。しかしこれではまだ安心できません。プログラムを書く上で、バグが発生しやすい箇所の1つが境界値です。バリデーションの中にstart > endというロジックが含まれているので、開始日と終了日が同じになる場合もテストしておきましょう。
def test_validate_same_dates(self):
fake = Faker()
url = reverse('ticket-list')
date = str(fake.date_object())
data = {
'assignee': None,
'name': fake.word(),
'description': fake.text(),
'status': random.choice([x.value for x in TicketStatus]),
'start': date,
'end': date,
}
response = self.client.post(url, json.dumps(data), content_type='application/json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Ticket.objects.count(), 1)
self.assertEqual(Ticket.objects.get().name, data['name'])
これで開始日と終了日が同じ日付でも、チケットの登録が可能であることを担保できました。
