私は@Hassekからの提案が本当に好きで、私たち全員がさまざまな懸念を持ってフレームワークにアプローチしているため、テストだけでなく、Djangoの多くの側面に当てはまる標準的な慣行の明らかな欠如について彼が述べている優れた点を強調したいと思います.また、アプリケーションの設計に関して非常に高い柔軟性を備えているため、同じ問題に適用できる大幅に異なるソリューションが得られることがよくあります。
とはいえ、私たちのほとんどは、アプリケーションをテストするときに、主に次のような多くの同じ目標に向かって努力しています。
- テスト モジュールをきちんと整理する
- 再利用可能なアサーション メソッドとヘルパー メソッド、テスト メソッドの LOC を削減するヘルパー関数を作成して、よりコンパクトで読みやすいものにする
- アプリケーション コンポーネントのテスト方法に対する明確で体系的なアプローチがあることを示す
@Hassekのように、これらはあなたが適用している可能性のある慣行と直接矛盾する可能性のある私の好みですが、私たちの場合に限り、機能することを証明したことを共有するのはいいことだと思います.
テスト ケース フィクスチャなし
アプリケーション フィクスチャは、データベースに存在することを保証したい特定の一定のモデル データがある場合、たとえば町の名前と郵便局番号のコレクションなど、うまく機能します。
ただし、これはテスト ケース データを提供するための柔軟性のないソリューションだと思います。テスト フィクスチャは非常に冗長です。モデルのミューテーションにより、フィクスチャ データを再現するための長いプロセスを経るか、退屈な手動変更を実行する必要があり、参照整合性を手動で維持することは困難です。
さらに、モデルだけでなく、多くの種類のフィクスチャをテストで使用する可能性が高くなります。API リクエストからの応答本文を保存したり、NoSQL データベース バックエンドをターゲットとするフィクスチャを作成したり、使用されるフィクスチャを記述したりします。フォームデータの入力など
最終的に、API を使用してデータを作成することは簡潔で読みやすく、関係を見つけるのがはるかに簡単になるため、私たちのほとんどは、動的にフィクスチャを作成するためにファクトリを使用することに頼っています。
工場を広く活用する
ファクトリ関数とメソッドは、テスト データを踏みにじるよりも望ましいものです。ヘルパー ファクトリ モジュール レベル関数またはテスト ケース メソッドを作成して、アプリケーション テスト間またはプロジェクト全体で再利用することができます。特に、factory_boy
@Hassek が言及している は、フィクスチャ データを継承/拡張し、自動シーケンスを実行する機能を提供します。
ファクトリを利用する最終的な目標は、コードの重複を減らし、テスト データの作成方法を合理化することです。正確な指標を提供することはできませんが、テスト メソッドを目の肥えた目で見れば、テスト コードの大部分が主に、テストを実行するために必要なデータを準備していることに気付くでしょう。
これが正しく行われないと、テストの読み取りと保守が大変な作業になります。これは、データの変更が全面的にそれほど明白ではないテストの失敗につながる場合にエスカレートする傾向があり、その時点で体系的なリファクタリング作業を適用できなくなります。
この問題に対する私の個人的なアプローチは、モデルのメソッドmyproject.factory
へのアクセスしやすい参照を作成するモジュールとQuerySet.create
、ほとんどのアプリケーション テストで定期的に使用する可能性のあるオブジェクトへの参照を作成することから始めることです。
from django.contrib.auth.models import User, AnonymousUser
from django.test import RequestFactory
from myproject.cars.models import Manufacturer, Car
from myproject.stores.models import Store
create_user = User.objects.create_user
create_manufacturer = Manufacturer.objects.create
create_car = Car.objects.create
create_store = Store.objects.create
_factory = RequestFactory()
def get(path='/', data={}, user=AnonymousUser(), **extra):
request = _factory.get(path, data, **extra)
request.user = user
return request
def post(path='/', data={}, user=AnonymousUser(), **extra):
request = _factory.post(path, data, **extra)
request.user = user
return request
これにより、次のようなことができます。
from myproject import factory as f # Terse alias
# A verbose, albeit readable approach to creating instances
manufacturer = f.create_manufacturer(name='Foomobiles')
car1 = f.create_car(manufacturer=manufacturer, name='Foo')
car2 = f.create_car(manufacturer=manufacturer, name='Bar')
# Reduce the crud for creating some common objects
manufacturer = f.create_manufacturer(name='Foomobiles')
data = {name: 'Foo', manufacturer: manufacturer.id)
request = f.post(data=data)
view = CarCreateView()
response = view.post(request)
ほとんどの人はコードの重複を減らすことに厳格ですが、実際には、テストの包括性に貢献していると感じたときに、意図的に導入しています。繰り返しになりますが、工場に対してどのようなアプローチを取る場合でも、目標は、各テスト メソッドのヘッダーに導入するブレインファックの量を最小限に抑えることです。
モックを使用しますが、賢く使用してください
私は のファンですmock
。なぜなら、彼が対処したかった問題であると私が信じるものに対する著者の解決策に感謝するようになったからです。パッケージが提供するツールを使用すると、期待される結果を注入してテスト アサーションを作成できます。
# Creating mocks to simplify tests
factory = RequestFactory()
request = factory.get()
request.user = Mock(is_authenticated=lamda: True) # A mock of an authenticated user
view = DispatchForAuthenticatedOnlyView().as_view()
response = view(request)
# Patching objects to return expected data
@patch.object(CurrencyApi, 'get_currency_list', return_value="{'foo': 1.00, 'bar': 15.00}")
def test_converts_between_two_currencies(self, currency_list_mock):
converter = Converter() # Uses CurrencyApi under the hood
result = converter.convert(from='bar', to='foo', ammount=45)
self.assertEqual(4, result)
ご覧のとおり、モックは非常に便利ですが、厄介な副作用があります。モックは、アプリケーションがどのように動作するかについての仮定を明確に示し、カップリングを導入します。Converter
以外のものを使用するようにリファクタリングされた場合CurrencyApi
、誰かがテスト メソッドが突然失敗する理由を明らかに理解できない可能性があります。
したがって、大きな力には大きな責任が伴います。賢く、モックを使用して根深いテストの障害を回避する場合、テストの失敗の本質を完全に難読化する可能性があります。
何よりも、一貫性を保つこと。非常に一貫性がある
これは最も重要なポイントです。絶対にすべてに一貫性を持たせてください:
- 各テスト モジュールでコードを整理する方法
- アプリケーション コンポーネントのテスト ケースを導入する方法
- それらのコンポーネントの動作をアサートするためのテスト メソッドをどのように導入するか
- テストメソッドをどのように構築するか
- 共通コンポーネント (クラスベースのビュー、モデル、フォームなど) をテストする方法
- 再利用の適用方法
ほとんどのプロジェクトでは、協力してテストにどのようにアプローチするかということは見過ごされがちです。アプリケーション コード自体は完璧に見えますが (スタイル ガイドの遵守、Python のイディオムの使用、関連する問題を解決するための Django 独自のアプローチの再適用、フレームワーク コンポーネントの教科書の使用など)。テスト コードを有効で便利なコミュニケーション ツールに変えることができますが、テスト コードの明確なガイドラインだけで十分だとしたら、それはもったいないことです。