5

@mock.patchPython モック ライブラリとデコレータを使用して日付をモックすることに依存する一連のテストと、ここにある日付モック コード サンプルを使用します。これを使用して、FakeDate クラスを作成します。

class FakeDate(original_date):
    "A fake replacement for datetime.date that can be mocked for testing."
    def __new__(cls, *args, **kwargs):
        return original_date.__new__(original_date, *args, **kwargs)

そして、私たちのテストでは次のものがあります。

from datetime import date as real_date

@mock.patch('datetime.date', FakeDate)
def test_mondays_since_date(self):

    FakeDate.today = classmethod(lambda cls: real_date(2014, 1, 1))  # A Wednesday
    self.assertNotEqual(datetime.date.today(), real_date.today())
    self.assertEqual(datetime.date.today().year, 2014)
    # and so on..

Django を 1.4.8 から 1.5.5 にアップグレードするまで、すべてがうまくいきました。残念ながら、モックの日付が原因でテストが失敗するようになりましたが、モデルの保存操作でのみです。スタック トレースは次のとおりです。

File "/site-packages/django/db/models/base.py", line 546, in save
  force_update=force_update, update_fields=update_fields)
File "/site-packages/django/db/models/base.py", line 650, in save_base
  result = manager._insert([self], fields=fields, return_id=update_pk, using=using, raw=raw)
File "/site-packages/django/db/models/manager.py", line 215, in _insert
  return insert_query(self.model, objs, fields, **kwargs)
File "/site-packages/django/db/models/query.py", line 1675, in insert_query
  return query.get_compiler(using=using).execute_sql(return_id)
File "/site-packages/django/db/models/sql/compiler.py", line 942, in execute_sql
  for sql, params in self.as_sql():
File "/site-packages/django/db/models/sql/compiler.py", line 900, in as_sql
  for obj in self.query.objs
File "/site-packages/django/db/models/fields/__init__.py", line 304, in get_db_prep_save
  prepared=False)
File "/site-packages/django/db/models/fields/__init__.py", line 738, in get_db_prep_value
  value = self.get_prep_value(value)
File "/site-packages/django/db/models/fields/__init__.py", line 733, in get_prep_value
  return self.to_python(value)
File "/site-packages/django/db/models/fields/__init__.py", line 697, in to_python
  parsed = parse_date(value)
File "/site-packages/django/utils/dateparse.py", line 36, in parse_date
  match = date_re.match(value)
TypeError: expected string or buffer

私はDjangoソースに自分の道をpdbしましたが、問題はここにあるようです( django/db/models/fields/ init .py :

def to_python(self, value):
    if value is None:
        return value
    if isinstance(value, datetime.datetime):
        if settings.USE_TZ and timezone.is_aware(value):
            # Convert aware datetimes to the default time zone
            # before casting them to dates (#17742).
            default_timezone = timezone.get_default_timezone()
            value = timezone.make_naive(value, default_timezone)
        return value.date()
    if isinstance(value, datetime.date):  # <-- This is the problem!
        return value

    try:
        parsed = parse_date(value)

型等価式が失敗しているため、parse_date実際にエラーが発生する呼び出し。(つまり、式valueから返された日付である は、標準の libオブジェクトFakeDate.today()とは見なされません。)datetime.date

それで、問題がどこにあるかはわかっていますが、それを回避するにはどうすればよいでしょうか? アプリケーションのテストでは、日付のモッキングが重要です。

[編集 1: Django の以前のバージョンの比較]

Django 1.5.5 で失敗する上記の式を比較すると、以下は 1.4.8 です (これは失敗しません)。

def to_python(self, value):
    if value is None:
        return value
    if isinstance(value, datetime.datetime):
        return value.date()
    if isinstance(value, datetime.date):
        return value

    value = smart_str(value)

    try:
        parsed = parse_date(value)

つまり、それらは同じです。では、なぜ 1 つが成功し、もう 1 つが失敗するのか - これはテストランナーの変更に関連しているのでしょうか?

[編集 2: より多くのデバッグ]

不一致をもう少し掘り下げる:

> /site-packages/django/db/models/fields/__init__.py(685)to_python()
    684         import ipdb; ipdb.set_trace()
--> 685         if value is None:
    686             return value
ipdb> value
datetime.date(2012, 12, 7)
ipdb> isinstance(value, datetime.date)
False
ipdb> type(value)
<type 'datetime.date'>
ipdb> type(datetime.date)
<type 'type'>
ipdb> datetime.date
<class 'testutils.FakeDate'>
ipdb> datetime.datetime
<type 'datetime.datetime'>

[編集 3: 問題の特定]

1.4 ブランチと 1.5 ブランチの間に不一致があることを発見しましたが、それはテスト ランナーにはありません。鍵はvalue = smart_str(value)1.4 ブランチの行です。これは beforeparse_dateで呼び出され、FakeDate を解析可能な文字列 repr に変換します (例: '2012-05-09')。これは、爆撃する 1.5 バージョンでは呼び出されません。

これは、1.4.x ブランチ内のシーケンスです。

# value = FakeDate(2012, 12, 31)
# code fails the isinstance(datetime.date) test
value = smart_str(value)
# value is now a string '2012-12-31'
parsed = parse_date(value)
# inside parse_date we try a regex match
match = date_re.match(value)
# because we have called smart_str, this now parses as a date

1.5.x ブランチ内のシーケンスには smart_str 変換が含まれていないため、valueこの場合の引数は文字列ではなく FakeDate オブジェクトであるため、正規表現の一致は失敗します。

[編集 5: Django に提出されたバグ]

これについては、Django イシュー トラッカー ( https://code.djangoproject.com/ticket/21523 ) にバグを送信しました。

4

1 に答える 1

2

これに関する私の調査は、一連の編集として問題になっていますが、これの長いと短いは、1.4 と 1.5 の間の to_python メソッドへの変更は、有効な datetime.date でも datetime.datetime でもないものはすべて、通過するための文字列。

django.utils.encoding.smart_strメソッドが 1.5 で削除されたかのように (smart_textこれ以上掘り下げなくても) 見えますto_python

django Trac インスタンスでチケットを作成しました ( https://code.djangoproject.com/ticket/21523 )。

私はこの問題のパッチも作成しましたが、明らかにこれがうまくいかない可能性があります (そして、パッチは 1.5.x 用であり、これは既に古くなっているため、これがうまくいくとは本当に期待していません)。 .

[編集 1: 解決策!]

解決策があります;-)-私はここに書いています-http: //tech.yunojuno.com/mocking-dates-with-django-キーはFakeDateインスタンスチェックメソッドをオーバーライドしているため、実際の日時を比較する場合.date を FakeDate に変換すると、True になります。参照用に、いくつかのサンプル FakeDate クラスと関連するテストを含む要点をまとめました - https://gist.github.com/hugorodgerbrown/7750432

于 2013-11-28T17:54:37.417 に答える