【Django】tests.pyの基本|テストプログラムの書き方

※本サイトにはプロモーション・広告が含まれています。

(最終更新月:2021年11月)

✔当記事はこのような方に向けて書かれた記事です

「作ったアプリがきちんと動作するのか確認したい!」

「エラーをいちいち手作業で確認するのって面倒くさい!」

「テストプログラムってなんか面倒くさい」

✔当記事を通じてお伝えすること

  1. テストプログラムとは?
  2. テストプログラムの書き方
  3. テストプログラムの実行方法
  4. テストプログラムのコード例

当記事では、テストプログラムの書き方はもちろん、そもそも必要なのかどうかや実例としてのサンプルまでご紹介しています。

ぜひ最後までご覧ください。

✔YouTube解説動画

当記事の内容は動画を見ながら進めると、約15分程度で完了します。

動画ならではの情報も解説しているので、記事と一緒にご覧ください。

動画の概要欄には、単元ごとのコードを紹介しているgithubページも載せています。

筆者プロフィール

筆者プロフィールアイコン

【現職】プロダクトマネージャー

【副業】ブログ(月間20万PV)/YouTube/Web・アプリ制作

「プログラミング × ライティング × 営業」の経験を活かし、30後半からのIT系職へシフト。現在はプロダクトマネージャーとして、さまざまな関係者の間に入り奮闘してます。当サイトでは、実際に手を動かせるWebアプリの開発を通じて、プログラミングはもちろん、IT職に必要な情報を提供していきます。

【当ブログで紹介しているサイト】

当サイトチュートリアルで作成したデモ版日報アプリ

Django × Reactで開発したツール系Webアプリ

✔人に見せても恥ずかしくないコードを書こう

「リーダブルコード」は、わかりやすく良いコードの定義を教えてくれる本です。

  • 見るからにきれいなコードの書き方
  • コードの分割方法
  • 変数や関数の命名規則

エンジニアのスタンダートとすべき基準を一から解説しています。

何回も読むのに値する本なので、ぜひ手にとって読んでみてください。

テストプログラムとは?

テストプログラムとは、Djangoに備わっている以下の特徴を持つエラーチェックのプログラムです。

  • コマンド一つで完全自動で実行できる
  • テスト数に限度がない
  • 何回でも繰り返し使える

なぜテストプログラムを使うかというと、ひとつひとつ手動で動作確認するのは時間がかかるから。

テストプログラムはとっつきにくいのも事実ですが、一度覚えればとても便利で、実は簡単です。

テストプログラムの書き方

テストプログラムの書き方を見ていきます。

以下を説明します。

  • テストプログラムの記述場所
  • テストプログラムの構成
  • テストプログラムの実行方法

テストプログラムの記述場所

テストプログラムは、アプリフォルダのtests.py内に記述します。

プロジェクトフォルダ > アプリフォルダ > tests.py

当チュートリアルで作っている日報アプリでは以下のとおり。

src > nippo > tests.py

テストプログラムの構成

テストプログラムは、TestCaseクラスを継承して作ります。

TestCaseクラスは以下の2つが主な要素

  • 初期設定の関数
  • エラーチェックのための関数

初期設定の関数

テストプログラムであるTestCaseクラスには初期設定関数が必要です。

なぜなら、テスト用のデータベースがあらたに作られるため、データが何も入っていないから。

関数名は「setUp」となります。

必要なデータを保存しておきましょう。

エラーチェックのための関数

テストプログラムでエラーをチェックするのは、このエラーチェックのための関数です。

以下のことを頭に入れておく必要があります。

  • 関数名は、「test_」で始めること
  • returnは不要である
  • printは使用できる
  • self.assertEqualなどを使い、動作チェックをおこなう

TestCaseクラスの書き方

TestCaseクラスを作ってみます。

  • 独自のTestCaseを作成
  • 初期設定関数を定義する

独自のTestCaseクラスの作成:NippoTestCase

「TestCase」をインポートし、継承したクラスを作ります。

from django.test import TestCase

class NippoTestCase(TestCase):
    pass

初期設定関数を定義:setUp関数

初期設定関数を定義しなければいけない理由は、アプリで使用しているデータベースを使わないから。

テスト用のデータベースが実行の度に作られているのです。

from django.test import TestCase
from nippo.models import NippoModel

class NippoTestCase(TestCase):
    def setUp(self):
        obj = NippoModel(title="testTitle1", content="testContent1")
        obj.save()

SQLiteを使用している場合は、新たなファイルが作られるようなことはありません。

ただしほかのデータベースを使っているのなら、テスト用のデータベースに権限を付与しておく必要があります。

テスト関数の書き方

テスト関数の書き方では以下のポイントをおさえましょう。

  • test_」で始まる関数名とすること
  • self.assertEqual()を使い、動作が正しいかを確認すること

「self.assertEqual(値1, 値2)」で値1と値2が等しい時は正しく動作していると判断されます。

ほかにも使えるメソッドはありますが、まずはこれだけ覚えておこう!

コード例:tests.py

データベースに正しく保存されているかをチェックします。

def test_saved_single_object(self):
    qs_counter = NippoModel.objects.count()
    self.assertEqual(qs_counter, 1)

コード解説

setUp関数が先に実行されるので、初期設定でデータベースにレコードが保存されているかをチェック。

qs_counter = NippoModel.objects.count()

objects.count()メソッドで、データベースへいくつのレコードが保存されているかを変数へ格納しています

「qs_counter」変数には「1」が格納されているべきなので、assertEqualで確認。

self.assertEqual(qs_counter, 1)

テストプログラムの実行方法

実行方法は、以下のとおり。

python manage.py test app名

日報アプリで実行

初期設定関数とテスト関数が書き終わりましたので、実行してみましょう。

python manage.py test nippo

以下の記述でOKと出れば、テストの実行結果が問題なかったことを表します。

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

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

そのほかの実行方法

実行方法はほかにもあります。

  • ファイル単位での実行:python manage.py test アプリ名.ファイル名(.pyを除く)
  • テストクラス単位での実行:python manage.py アプリ名.ファイル名.テストクラス名
  • メソッド単位での実行:python manage.py アプリ名.ファイル名.テストクラス名.関数名

例えば先程作成した関数だけ実行したい場合は以下のようにします。

python manage.py test nippo.tests.NippoTestCase.test_saved_single_object

日報アプリのために作成したテストプログラムのコード例

当アプリ(日報アプリ)では3つのエラー確認関数を定義しました

  1. データベースへの保存ができているかどうか
  2. queryが存在しないページへアクセスした際にステータスコード「404」を返すかどうか
  3. createページでPOST送信した時の動作とデータベース内の情報を確認

【コピペ可】tests.pyのコード

以下がそのコードです(コピペもご自由にどうぞ!)

from django.test import TestCase
from django.urls import reverse
from nippo.models import NippoModel

class NippoTestCase(TestCase):
    #初期設定
    def setUp(self):
        obj = NippoModel(title="testTitle1", content="testContent1")
        obj.save()

    #日報の作成ができているか
    def test_saved_single_object(self):
        qs_counter = NippoModel.objects.count()
        self.assertEqual(qs_counter, 1)
    
    #queryが存在しない時に、404ページを返すかどうか
    def test_response_404(self):
        detail_url = reverse('nippo-detail', kwargs={"pk": 100})
        detail_response = self.client.get(detail_url)
        update_url = reverse('nippo-update', kwargs={"pk": 100})
        update_response = self.client.get(update_url)
        delete_url = reverse('nippo-delete', kwargs={"pk": 100})
        delete_response = self.client.get(delete_url)
        self.assertEqual(detail_response.status_code, 404)
        self.assertEqual(update_response.status_code, 404)
        self.assertEqual(delete_response.status_code, 404)

    #createページできちんとデータが保存されているか
    def test_create_on_createView(self):
        url = reverse('nippo-create')
        create_data = {"title": "title_from_test", "content": "content_from_test"}
        response = self.client.post(url, create_data)
        qs_counter2 = NippoModel.objects.count()
        self.assertEqual(response.status_code, 302)
        self.assertEqual(qs_counter2, 2)

実行状況

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
...
----------------------------------------------------------------------
Ran 3 tests in 0.101s

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

日報アプリテストプログラムのコード解説

それぞれのテスト関数について解説をしていきます。

  • test_response_404関数
  • test_create_on_createView関数

test_response_404関数:404エラーが出ているかをチェック

存在しないpkを入力した際に、ステータスコード「404」が返されているかをチェックしていきます。

detail_url = reverse('nippo-detail', kwargs={"pk": 100})

reverseメソッドを使って、ページ名にpkを渡し、URLを変数へ格納しています。

reverseメソッドについて詳しくはこちら。

detail_response = self.client.get(detail_url)

ページへアクセスした際のレスポンスを得る場合は、以下のようにします。

self.client.get(ページのURL)

self.assertEqual(detail_response.status_code, 404)

アクセスの際のレスポンスは、こちらで取得できます。

レスポンス.status_code

404が返されていれば成功!となりますので、上記のような記述となっています

ディテールビューとアップデートビュー、デリートビューで確認をしています。

test_create_on_createView関数:ページ上からPOST送信を確認

nippoCreateViewでデータをPOST送信した際にきちんとデータが保存できるかを確認します

create_data = {"title": "title_from_test", "content": "content_from_test"}

新規保存するデータを、辞書型で変数へ格納します。

response = self.client.post(url, create_data)

データを保存したい場合は、以下のとおり。

self.client.post(ページのURL, 保存したいデータ)

self.assertEqual(response.status_code, 302)
self.assertEqual(qs_counter2, 2)

こちらの2行での確認事項はこれ。

  1. 保存された場合にきちんとリダイレクト(302)されているかどうか
  2. 保存データの個数が2つになっているか

エラー発生時の表記は?

test_create_on_createView関数を最初は下記のとおり記述していました。

    def test_create_on_createView(self):
        url = reverse('nippo-create')
        create_data = {"title": "title_from_test", "content": "content_from_test"}
        response = self.client.post(url, create_data)
        self.assertEqual(response.status_code, 201)

201レスポンスを想定してい書いてしまったのです。

そのときのエラーメッセージは以下のとおり。

FAIL: test_create_on_createView (nippo.tests.NippoTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/.../src/nippo/tests.py", line 33, in test_create_on_createView
    self.assertEqual(response.status_code, 201)
AssertionError: 302 != 201

最後の文章できちんと、指定した201ではなく302だということを説明してくれています。

おわりに

当記事の内容をまとめます。

  • テストプログラムは、あとが楽になるので先に書いておくべき
  • テストプログラムの構成は、初期設定とテスト関数の2つ
  • テストプログラムは思いついたときに好きに書けば良い

テストプログラムは一度コツをつかめば、書くのはそこまで難しくありません。

不安を感じることがあれば、それを前もって書いておけば良いのです。

Webサーバーで公開するときやアプリが複雑になったときなどに必ず重宝します。

さて、次回は今まで作成したページの見た目を整える方法をお伝えします。

タイトルとURLをコピーしました