はじめに
皆さんは自分でアプリケーションを作った後、テストをしているでしょうか。
ひょっとしたら、完成したことに満足してテストしていない人も多いのではないでしょうか。
しかし、テストすることはアプリケーションを開発する際に非常に大切なことです。
今回はテストの役割や何故しなければならないのか解説していきます。
また、後半ではダミーデータを作成するPopulation Scriptを書いて実際にテストをしてみたいと思います。
この記事を読んでテストのノウハウを今後の開発に活かしていきましょう。
今回の記事について
この記事の後半ではDjangoというWebフレームワークを使用して、データベースを作成しテストを行います。
Djangoにもともとテストを行える機能が付属されていますが、他の開発でも適応できる汎用性を考えて、今回はそれらをしようしないで行っていきます。
情報を補足しながら説明していくので、Python自体に触れていなくても構いませんが、何かサービスを作った経験がある方が理解しやすいかもしれません。
コードの意味が難しくて良く分からない方は、テストはこのように行うのかと雰囲気だけでも掴んでいただけば幸いです。
今回使用するパッケージ、ライブラリ
- Python 3.7.2
- Django 2.1
- Faker 1.0.7
目次
テストとはなにか
テストとは、コードの動作を確認する単純なプログラムです。
テストは様々なレベルで実行されます。
例えば、アプリケーションの小さな機能を確認するためのテストである単体テストやアプリケーション全体の動作確認を行う統合テストがあります。
このようなテストは、実際にソフトウェアを動かすことで確認するテストとあまり違いがないように感じるかももしれません。
それではなぜテストコードを書いてテストを行う必要があるのでしょうか。
なぜテストをするのか、テストを行う理由とは?
そもそも、なぜテストをする必要があるのでしょうか。
せっかくアプリケーションが出来てちゃんと動作するのに、わざわざテストをする必要性を感じなかったり、プログラミング言語やWebフレームワークの勉強でそんな事をやっている時間がないと感じる人もいるかもしれません。
そもそもテストを作ったところで、アプリケーションの機能が上がるわけはありません。
もし皆さんがアプリケーションを作るだけの目的であれば、確かに必要ないのかもしれません。
しかし、テストを書くことで以下の4つのメリットがあります。
時間を節約できる
もし、自分の書いたコードがアプリケーションが自分の望んだとおりの動作をするかどうかを確認したい場合、どのようにするでしょうか。
小規模なものであれば、実際にアプリケーションを動かさせば済みます。
しかしある程度機能が増えたり、複雑になったりするといちいち自分で動作確認するのは非常に面倒になります。
そんなときにテストするコードを書いておけば、一瞬でテストを行うことができます。
それにより時間を大幅に短縮できます。
問題の発生を防ぐことができる
テストがないと、自分の開発したアプリケーションの目的や意図した動作が曖昧になってしまう可能性があります。
また時間が立ってしまうと自分の書いたコードであっても、そのコードの意味が理解しづらくなってしまうこともあります。
テストをすることで自分のコードの間違えに気づけるだけでなく、なぜそのコードを書いたのかを理解することにもつながります。
コードが魅力的になる
たとえ皆さんがどんなに素晴らしいアプリケーションを作ったとしても、テストがないと他のエンジニアは見てくれないことがあります。
それだけ、エンジニアはテストの重要性を知っているのです。
そしてテストのないアプリケーションは信用されません。
Django を開発した Jacob Kaplan-Moss は次の言葉を残しています。
テストのないコードは、デザインとして壊れている。
つまり他のエンジニアに見てもらい、自分の実力をアピールする際にはテストは非常に重要なのです。
チームの共同開発で役立つ
これは実際に開発の現場に行かないとイメージが沸きづらいかもしれません。
前の3点は個人開発者向けに書きました。
しかし、複数人でアプリケーション開発を行う場合は、チームでメンテナンスを行う必要があります。
テストは、あなたが書いたコードを他人がうっかり壊してしまうことから守ってくれます。
また、他の人が行っているテストを重複して行う必要がなくなります。
このようにテストを通じて、エンジニア同士がコミュニケーションをとることにも繋がります。
テスト用モデルの作成
今まではテストはなぜ必要なのか説明してきました。
ここからは実際にテストを書いて、書いたコードがしっかりと動作するか確認していきたいと思います。
今回はDjangoでのモデルからデータベース作成します。
そしてそこに希望のデータをちゃんと入れられるのか確認するコードを書いていきたいと思います。
まずはサンプルのモデルからデータベースを作成していきたいと思います。
モデルは以下のように定義します。
# first_app/model.py
from django.db import models
class Topic(models.Model):
top_name = models.CharField(max_length=264, unique=True)
def __str__(self):
return self.top_name
class WebPage(models.Model):
topic = models.ForeignKey("Topic", on_delete=models.CASCADE)
name = models.CharField(max_length=264, unique=True)
url = models.URLField(unique=True)
def __str__(self):
return self.name
class AccessRecord(models.Model):
name = models.ForeignKey('WebPage', on_delete=models.CASCADE)
date = models.DateField()
def __str__(self):
return str(self.date)
Djangoの勉強をされる方もいるかもしれないので、丁寧に説明したいと思います。
Djangoではmodelを作成する際には、もともと用意されているmodels.Model
というクラスを継承します。
その中でmodels.何とかField
という定義をすることで、色々なデータの要素を受け取ることができます。
Field nameは様々な種類があり、それぞれで受け取れるものが違いますので、公式ドキュメントを確認してください。
Django: モデルフィールドリファレンス
テーブル間の関係はone-to-one
, one-to-many
そしてmany-to-many
の3つに大別されています。
基本的にField Nameはone-to-one
ですので、一対一対応のデータになります。
またmodels.ForeignKey
によって、one-to-many
を表現しています。
これによって、他のテーブルと関連付けて表示させるようにしています。
class内の最後には、
def __str__(self):
return self.top_name
と書いていますが、これは特殊メソッドと呼ばれているものです。
管理画面でこれらのデータを扱う時に、定義した名前で表示されるようにしています。
もしこれを定義しないと継承元の__str__
が呼び出されて、<Topic:Topic object>
というよく分からない文字列が出力されてしまいます。
モデルを定義するときに取り敢えず書いておくことをお勧めします。
そしてこのモデルから次のコマンドでデータベースを作成します。
# windows
> py manage.py makemigrations
> py manage.py migrate
# mac
$ python manage.py makemigrations
$ python manage.py migrate
上記の宣言でマイグレーションファイル
という中間ファイルが作成され、そのファイルからモデルで定義したデータがSQLで書き換えられます。
これでデータが作成されました。
どのようなデータが出来ているかイメージを掴んでもらうために、以下にそれぞれのテーブルを載せておきますた。
分かりやすいように1セットのデータが入っています。
WebPage
topic | name | url | |
---|---|---|---|
1 | Social | https://www.facebook.com/ |
Topic
top_name | |
---|---|
1 | Social |
AccessRecord
name | date | |
---|---|---|
1 | 2019-05-21 |
先ほどの説明したように、それぞれのデータテーブルがForeign Key
で紐づいているため、それぞれが独立しているわけではありません。
WebPageのtopicはTopicのtop_nameと、WebPageのnameはAccessRecordのnameで紐づいています。
ただこれだけだとイメージが沸きづらいかもしれません。
管理画面で実際にどうなっているか確認してみましょう。
WebPageは以下のようになっています。
Topicは単純にtop_nameのデータだけ入っています。
またAccessRecordにはDateが紐づいているので以下のようになっています。
このデータベースを使ってテストを行っていきます。
Population Scriptを書こう
それではPopulation Scriptというものを書いていこうと思います。
Population Script(ポピュレイション・スクリプト)とはデータベースにダミーデータを生成して入れる事で、ちゃんとデータが期待通り入っているかを確認するためのコードです。
データを入れて動作を確認する際には、適当な乱数や意味のない文字列を入れる事もできます。
しかし状況にあったそれっぽいデータを入れた方が、テストの信憑性が増します。
今回は、fakerというダミーデータを生成できるPythonのライブラリを使います。
このライブラリでは人名や住所、URLなどを実在しないそれっぽいデータとして生成してくれます。
Faker is a Python package that generates fake data for you.
faker github
コードは以下に書きます。
# population_first_app.py
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
import django
django.setup()
import random
from faker import Faker
from first_app.models import AccessRecord, Topic, WebPage
fakegen = Faker()
topics = ['Search', 'Social', 'Marketplace', 'News', 'Games']
def add_topics():
t = Topic.objects.get_or_create(top_name=random.choice(topics))[0]
t.save()
return t
def populate(N=5):
for entry in range(N):
# トピックの作成
top = add_topics()
# ダミーデータの作成
fake_url = fakegen.url()
fake_date = fakegen.date()
fake_name = fakegen.company()
# 新しいWebPageのダミーデータの作成
webpg = WebPage.objects.get_or_create(
topic=top, url=fake_url, name=fake_name)[0]
# アクセスレコードのダミーデータの作成
acc_rec = AccessRecord.objects.get_or_create(
name=webpg, date=fake_date)[0]
# コードの実行
if __name__ == "__main__":
print('populating scripts')
populate(20)
print('populating complate')
それでは順に説明していきます。
これからDjangoを勉強しようという人、もしくは勉強している人向けに丁寧に説明します。
興味がない方や関係ないと思われる方は適宜読み飛ばしてください。
最初の方に書いてあるコードはDjango特有のコードです。
良く分からない方は気にしなくても結構です。
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
import django
django.setup()
これらのコードを先に書かないとmodel.pyからモデルをインポートできません。
データを作成する際に重要な記述となります。
次に必要なライブラリとモデルをインポートします。
import random
from faker import Faker
from first_app.models import AccessRecord, Topic, WebPage
そしてTopicとして使いたいものをリストで用意し、Fakerからfakegenというオブジェクトを生成しておきます。
fakegen = Faker()
topics = ['Search', 'Social', 'Marketplace', 'News', 'Games']
これで基本的な準備完了です。
ここからダミーデータを実際に作成していきます。
まずはTopicのダミーデータを生成する関数を定義します。
def add_topics():
t = Topic.objects.get_or_create(top_name=random.choice(topics))[0]
t.save()
return t
特にDjangoでは、
モデル名.objects.メソッド名
と書くことでモデル内のデータを扱うことができます。
get_or_create()というメソッドは、オブジェクトが存在しない場合はデータベースにオブジェクトを登録して登録した値を返し、オブジェクトが存在する場合にはデータベースにオブジェクトを登録せずに値を返します。
非常に強力なものなので、ぜひ覚えておいてください。
そしてsave()でそれを保存し、返り値として次の関数に渡していきます。
次にダミーデータを作成し、データベースに挿入する関数を定義します。
def populate(N=5):
for entry in range(N):
top = add_topics()
fake_url = fakegen.url()
fake_date = fakegen.date()
fake_name = fakegen.company()
webpg = WebPage.objects.get_or_create(
topic=top, url=fake_url, name=fake_name)[0]
acc_rec = AccessRecord.objects.get_or_create(
name=webpg, date=fake_date)[0]
populate関数の引数はfor文で回す回数を取っているので、後で自由に変えられるようにしてあります。
今回は試しに5とデフォルト引数を定義しました。
そして以下のコードでダミーデータを生成します。
top = add_topics()
fake_url = fakegen.url()
fake_date = fakegen.date()
fake_name = fakegen.company()
あとは先ほどと同じように、get_or_create()を使ってデータを入れています。
webpg = WebPage.objects.get_or_create(
topic=top, url=fake_url, name=fake_name)[0]
acc_rec = AccessRecord.objects.get_or_create(
name=webpg, date=fake_date)[0]
これでPopulation Scriptが書けたので実行できます。
実行するためのコードは以下のようになります。
if __name__ == "__main__":
print('populating scripts')
populate(20)
print('populating complate')
実行できたことが確認できるようにprint()でコマンド上に文章を表示できるようにしました。
デフォルト引数で5としましたが、今回は20個のデータを生成できるように変更しました。
ここで不思議に思われる方がいるかもしれません。
ただこの関数を実行するだけであれば、
print('populating scripts')
populate(20)
print('populating complate')
で十分動きます。
それなのに、なぜわざわざ
if __name__ == "__main__":
と書くのでしょうか。
これは「このコードがかかれているファイルを実行したときにだけこの関数が実行される」という意味です。
もしこのファイルが他のファイルから実行された場合、上記のように直書きしてしまうと、中身の内容も実行され思わぬエラーが生じる可能性があります。
このファイルが他のファイルに呼ばれる可能性は少ないかもしれませんが、このように記述する方法は非常によく使うのでぜひ覚えておきましょう。
実装内容の確認
それでは、このコードを実行してみます。
# windows
> py population_first_app.py
populating scripts
populating complate
# mac
$ python population_first_app.py
populating scripts
populating complate
上手く実行できました。
管理画面から実際にデータがちゃんと入っているかを確認してみましょう。
このようにそれっぽい名前がたくさん入っています。
ちゃんとモデルにデータが入っているようです。
一番上のデータについて具体的に見てみましょう。
WebPage
Topic
AccessRecord
確かに自分の希望通りにデータが差し込まれていました。
これでテストが完了です。
その他のダミーデータ作成で使えるライブラリ
さきほどfakerというライブラリを紹介しましたが、Numpyというライブラリでもダミーデータを作成できるので紹介します。
例えば、年齢を生成したい場合は以下のように記述します。
import numpy as np
from numpy.random import *
randint(20,40)
randint()ではカッコ内にかかれた数字の範囲で、一様な確率の数字を生成できます。
しかしものによっては平均より上の値や下の値が非常に少ないものもあります。
身長のような一様ではなく特定の分布(正規分布と言います)を示しているときにも、Numpyでは数値を生成できます。
import numpy as np
from numpy.random import *
normal(170,6)
normal()のカッコ内では平均値と分散を定義します。
例えば日本人のデータであれば厚生労働省のホームページに載っているので、調べて記述するとよりリアルなダミーデータが生成できると思います。
まとめ
今回はテストをなぜ行うべきかとそのメリット、そして後半では実際にデータベースのテストを行いました。
初学者はアプリケーションの開発に精いっぱいで、テストの書くところまで頭が回らないかもしれません。
しかし、テストは現場の開発では非常に重要です。
テストを書くと他の初学者と差を付けることができます。
ぜひテストを書くことにも挑戦してみてください。