LoginSignup
0
0

Djangoのテストをまとめる

Last updated at Posted at 2020-11-28

はじめに

現場がユニットテストにフォーカスし始めたので、目検が多かったテスト文化をユニットテストに向けて深化させる(現場はphpだけど)。でもたしかに自動テスト文化になったほうが説得力が違う

参考

image.png

大事なこと

  • プロダクションコード(実際のコード)がない状態でテストを作る

実際のコードを書く前に絶対に失敗するテストを書く(仮説を立てる)ことが大事

ディレクトリの状態

アプリケーションは複数ある

  • gmarker
  • kanban
  • linebot
  • register
  • shopping
  • vietnam_research
D/OneDrive/dev/Portfolio
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 を実行。するとテストは失敗する。

TestCaseの名前空間は unittest じゃなくて django.test。pycharmからテストできなかったのはこれが原因だった。

- from unittest import TestCase
+ from django.test import TestCase

やったー、いつもの三角ボタンからテストできるぞ!:relaxed:
image.png

例えば mixedproject というリポジトリがあってそのフォルダがルート(.git がある)状態になっていて、いろんなスクリプトフォルダがあるなかのひとつに hogeproject があってそれがdjangoプロジェクトの起点だった場合、そもそもプロジェクトを分離して hogeproject でリポジトリを作るべきである

なぜかテストが実行できなくて地獄を見るぞ。泣く。
自分のプロジェクトだとこんなことしないんだけど、人のリポジトリを間借りしてやってるときなんかはちょっと遠慮しちゃって ココニツクルネ なんてやりはじめるとこんなことになる

import文が hogeproject から始まる感じになってたらあやしい

ただ、一応回避方法は発見した。
それは hogeproject を起点としてIDEに読み込ませるという方法

tests.py
from django.test import TestCase

class SmokeTest(TestCase):
    def test_bad_maths(self):
        self.assertEqual(1 + 1, 3)  # 失敗

PyCharmのUIでテスト実行

こっちがすき。安定の操作感
image.png

コマンドラインからのテスト実行

現場でVSCodeしか使えないような場合の実行の仕方

test実行(配下のアプリケーションすべてのテストを実行してくれるってわけだ)
Portfolio> cd mysite
Portfolio\mysite> python manage.py test

テスト中のデータベースのライフサイクル

さりげなく database を作って、最後に消していることがわかる。
image.png

テストデータベースのライフサイクルはテストコマンドの開始と終了で、 def ごとに毎回 setup() を実行してくれてそのたびにテーブルはロールバックされるが、pkは増え続けるので pk=1 みたいなテストを実行すると存在しないと言われてしまう。テーブルから pk でひいてはいけない!

settings.py で指定したdatabaseログイン権限に データベース作成 権限がないとだめ。
image.png

ここまで聞くと簡単そうなんだけど、現場入りするとそもそもテストが走らないみたいな環境要因もあったりするよね。カオス。でも、環境要因が解決してから「どんなテストをすればいいか」というのが思いつかないともっとカオスだからこうして轍(わだち)を残すことが大事。テストっていったらスクリーンショットでしょって文化が僕にもあるけど、そんなものは変えていかないと。。。

テーブルにコンタクトできるか

No.1 テーブルは0件

Industryモデルはvietnam_researchに実際にあるモデル
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(そらそうよ)

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 になんか日付入れないとだめだよ

NG
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 を入れてみて...

mysite/vietnam_research/tests.py
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)

検証

mysite/vietnam_research/tests.py
    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年 だってことが影響してる??(書き方が違う?自分のソースコード確認するのが一番早いか...ゴソゴソ)
image.png

コピペしてたカラムに代入するっちゅー凡ミスやわ...
    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_researchindex.html しかないんでね

mysite/vietnam_research/tests.py
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')
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0