2

私はかなり基本的なdoctestableファイルを持っています:

class Foo():
    """
    >>> 3+2
    5
    """

if __name__ in ("__main__", "__console__"):
    import doctest
    doctest.testmod(verbose=True)

これは、Pythonを直接実行すると期待どおりに機能します。

ただし、iPythonでは

1 items had no tests:
    __main__
0 tests in 1 items.
0 passed and 0 failed.
Test passed.

これはDjangoプロジェクトの一部であり、manage.pyが設定されるように、適切なすべての変数にアクセスする必要があるため、code.InteractiveConsoleを使用する変更されたコマンドを実行することもできます。その結果の1つ__name__が設定されます。に' __console__'。

上記のコードを使用すると、iPythonの場合と同じ結果が得られます。最後の行を次のように変更してみました。

 this = __import__(__name__)
 doctest.testmod(this, verbose=True)

でImportErrorが発生しますが__console__、これは理にかなっていると思います。これは、pythonまたはipythonのいずれにも影響しません。

ですから、Djangoポニーマジックがすぐに必要になると予想しているので、これら3つのメソッドすべて、特にInteractiveConsoleメソッドを介してdoctestを正常に実行できるようにしたいと思います。

明確にするために、これは私が期待していることです:

Trying:
    3+2
Expecting:
    5
ok
1 items had no tests:
    __main__
1 items passed all tests:
   1 tests in __main__.Foo
1 tests in 2 items.
1 passed and 0 failed.
Test passed.
4

2 に答える 2

8

根本的な問題は、(独自のモジュールを介して)ipython奇妙なトリックを実行するため、その時点で「疑わしいモジュール」がそこに存在しないことです。したがって、doctestはそれに再帰しません。__main__FakeModuledoctest__dict__Foo

これが1つの解決策です:

class Foo():
    """
    >>> 3+2
    5
    """

if __name__ in ("__main__", "__console__"):
    import doctest, inspect, sys
    m = sys.modules['__main__']
    m.__test__ = dict((n,v) for (n,v) in globals().items()
                            if inspect.isclass(v))
    doctest.testmod(verbose=True)

これは、要求に応じて生成されます。

$ ipython dot.py 
Trying:
    3+2
Expecting:
    5
ok
1 items had no tests:
    __main__
1 items passed all tests:
   1 tests in __main__.__test__.Foo
1 tests in 2 items.
1 passed and 0 failed.
Test passed.
Python 2.5.1 (r251:54863, Feb  6 2009, 19:02:12) 
  [[ snip snip ]]
In [1]: 

グローバルを設定するだけでは機能しません__test__。これも、考えているもののグローバルとして設定しても、によって復元される実際のオブジェクトには実際__main__には配置されないためです。後者は、式が内部で使用しているものとまったく同じです(実際にはを使用しますが、そこに存在することがわかっているので、ここでは特別な注意は必要ありません...それはあなたが期待するオブジェクトではありません!-)。__dict__m = sys.modules['__main__']doctestsys.modules.get__main__sys.modules

また、m.__test__ = globals()別の理由で、直接設定するだけでも機能しません。doctestの値__test__が文字列、関数、クラス、またはモジュールであることを確認します。いくつかの選択がないglobals()と、その条件を満たすことを保証できません(実際には機能しません)。 。ここでは、クラスのみを選択しています。関数なども必要な場合は、呼び出し内のgenexporの句でinを使用できます。ifdict

スクリプトを実行できるDjangoシェルをどのように実行しているかは正確にはわかりません(python manage.py shell引数を受け入れないと思うので、何か他のことをしている必要があり、正確に何を推測することはできません!-)。同様のアプローチが役立つはずです(Djangoシェルがipythonを使用している場合でも、使用可能な場合はデフォルトであるか、プレーンPythonを使用している場合でも):__test__取得したオブジェクトを適切に設定しますsys.modules['__main__'](または__console__、それがdoctest.testmodに渡される場合はI推測)は、doctestがテスト文字列を見つけるために内部で実行することを模倣しているため、機能するはずです。

そして、結論として、デザイン、アーキテクチャ、シンプルさ、透明性、そして「黒魔術」についての哲学的考察...:

このすべての努力は、基本的に、ipython(およびおそらくその部分をipythonに委任しているだけかもしれませんがDjango)があなたの「便利さ」のためにあなたに代わって行っている「黒魔術」を打ち負かすために必要なものです...いつでも2つのフレームワーク(またはそれ以上;-)がそれぞれ独自の黒魔術を独立して実行しているため、相互運用性は突然かなりの労力を必要とし、便利なものになる可能性があります;-)。

黒魔術、内省、偽のモジュールなどがなければ、同じ便利さが(ipython、django、および/またはdoctestのいずれか1つ以上によって)提供された可能性があると言っているのではありません。これらの各フレームワークの設計者と保守担当者は優れたエンジニアであり、宿題を徹底的に行い、必要と判断したユーザーの利便性を実現するために不可欠な最小限の黒魔術のみを実行していると思います。それにもかかわらず、そのような状況でも、フレームワークの作成者が考えていたものから少しでも外れたことをしたいと思うとすぐに、「黒​​魔術」は便利さの夢からデバッグの悪夢に突然変わります。

OK、この場合は悪夢ではないかもしれませんが、この質問はしばらく開いていて、賞金の誘惑があってもまだ多くの答えが得られていないことに気づきました-あなたは今2つの答えを選ぶことができますがから、doctestの__test__特別な機能を使用して、@codeapeはironpythonの固有の__IP.magic_run機能を使用しています。内部または文書化されていないものに依存しないので、私は私の方が好き__test__です-doctestの文書化された機能ですが、__IPこれら2つの迫り来る主要なアンダースコアで、私に「深い内部、触れないでください」と叫びます;-) ... if次のポイントリリースで壊れます。私はまったく驚かないでしょう。それでも、好みの問題-その答えは間違いなくより「便利」であると考えられるかもしれません。

しかし、これはまさに私のポイントです。単純さ、透明性、および/または内部/文書化されていない/不安定な機能の回避という点で、利便性は莫大な代償を伴う可能性があります。したがって、私たち全員への教訓として、私たちが逃げることができる最小の黒魔術&c(あちこちで便利なイプシロンをあきらめることを犠牲にしても)、私たち全員が長期的には幸せになるでしょう(そして将来的に現在の取り組みを活用する必要のある他の開発者をより幸せにするでしょう)。

于 2009-08-29T17:05:02.443 に答える
2

次の作品:

$ ipython
...
In [1]: %run file.py

Trying:
    3+2
Expecting:
    5
ok
1 items had no tests:
    __main__
1 items passed all tests:
   1 tests in __main__.Foo
1 tests in 2 items.
1 passed and 0 failed.
Test passed.

In [2]: 

なぜipython file.py動かないのか分かりません。ただし、上記は少なくとも回避策です。

編集:

それが機能しない理由を見つけました。それは非常に簡単です:

  • テストするモジュールを指定しない場合は、モジュールをテストするdoctest.testmod()ことを前提としてい__main__ます。
  • IPythonがコマンドラインで渡されたファイルを実行する場合、__main__モジュールはIPythonの__main__ものであり、モジュールではありません。そのため、doctestはIPythonのエントリスクリプトでdoctestを実行しようとします。

以下は機能しますが、少し奇妙に感じます:

if __name__ == '__main__':
    import doctest
    import the_current_module
    doctest.testmod(the_current_module)

つまり、基本的にモジュールはそれ自体をインポートします(これは「少し奇妙に感じる」部分です)。しかし、それは機能します。私が嫌いなこと。このアプローチでは、すべてのモジュールが独自の名前をソースに含める必要があります。

編集2:

次のスクリプトはipython_doctest、ipythonを希望どおりに動作させます。

#! /usr/bin/env bash

echo "__IP.magic_run(\"$1\")" > __ipython_run.py
ipython __ipython_run.py

%run argnameこのスクリプトは、 IPythonで実行されるPythonスクリプトを作成します。

例:

$ ./ipython_doctest file.py
Trying:
    3+2
Expecting:
    5
ok
1 items had no tests:
    __main__
1 items passed all tests:
   1 tests in __main__.Foo
1 tests in 2 items.
1 passed and 0 failed.
Test passed.
Python 2.5 (r25:51908, Mar  7 2008, 03:27:42) 
Type "copyright", "credits" or "license" for more information.

IPython 0.9.1 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object'. ?object also works, ?? prints more.

In [1]:
于 2009-08-26T20:10:54.760 に答える