Python中的测试模块unittest和doctest的使用教程
我要坦白一点。尽管我是一个应用相当广泛的公共域 Python 库的创造者,但在我的模块中引入的单元测试是非常不系统的。实际上,那些测试大部分 是包括在 gnosis.xml.pickle 的 Gnosis Utilities 中的,并由该子软件包(subpackage)的贡献者所编写。我还发现,我下载的绝大多数第三方 Python 包都缺少完备的单元测试集。
不仅如此,Gnosis Utilities 中现有的测试也受困于另一个缺陷:您经常需要在极其大量的细节中去推定期望的输出,以确定测试的成败。测试实际上 -- 在很多情况下 -- 更像是使用库的某些部分的小实用工具。这些测试(或实用工具)支持来自任意数据源(类型正确)的输入和/或描述性数据格式的输出。实际上,当您需要调试一些细微的错误时,这些测试实用工具更有用。但是对于库版本间变化的自解释的完整性检查(sanity checks)来说,这些类测试就不能胜任了。
在这一期文章中,我尝试使用 Python 标准库模块 doctest 和 unittest 来改进我的实用工具集中的测试,并带领您与我一起体验(并指出一些最好的方法)。
脚本 gnosis/xml/objectify/test/test_basic.py 给出了一个关于当前测试的缺点及解决方案的典型示例。下面是该脚本的最新版本:
清单 1. test_basic.py
"Read and print and objectified XML file" import sys from gnosis.xml.objectify import XML_Objectify, pyobj_printer if len(sys.argv) > 1: for filename in sys.argv[1:]: for parser in ('DOM','EXPAT'): try: xml_obj = XML_Objectify(filename, parser=parser) py_obj = xml_obj.make_instance() print pyobj_printer(py_obj).encode('UTF-8') sys.stderr.write("++ SUCCESS (using "+parser+")\n") print "="*50 except: sys.stderr.write("++ FAILED (using "+parser+")\n") print "="*50 else: print "Please specify one or more XML files to Objectify."
实用工具函数 pyobj_printer() 生成了任意 Python 对象(具体说是这样一个对象,它既没有用到 gnosis.xml.objectify 的任何其他实用工具,也没有用到 Gnosis Utilities 中的 任何其他东西)的一个 非-XML 表示。在以后的版本中,我将可能会把这个函数移到 Gnosis 包内的其他地方。无论如何, pyobj_printer() 使用各种类-Python 的缩进和符号来描述对象和它们的属性(类似于 pprint ,但是扩展了实例,而不仅限于扩展内置的数据类型)。
如果一些特别的 XML 可能不能正确被地“对象化(objectified)”, test_basic.py 脚本会提供一个很好的调试工具 -- 您可以可视化地查看结果对象的属性和值。此外,如果您重定向了 STDOUT,您可以查看 STDERR 上的简单消息,如这个例子中:
清单 2. 分析 STDERR 结果消息
$ python test_basic.py testns.xml > /dev/null ++ SUCCESS (using DOM) ++ FAILED (using EXPAT)
不过,上面运行的例子中对成功或失败的界定很不明显:成功只是意味着没有出现异常,而不表示(重定向的)输出 正确。
使用 doctest
doctest 模块让您可以在文档字符串(docstrings)内嵌入注释以显示各种语句的期望行为,尤其是函数和方法的结果。这样做很像是让文档字符串看起来如同一个交互式 shell 会话;完成这一任务的一个简单方法是,从一个 Python 交互式 shell 中(或者从 Idel、PythonWin、MacPython 或者其他带有交互式会话的 IDE 中)拷贝-粘贴。这一改进的 test_basic.py 脚本举例说明了自诊断功能的添加:
清单 3. 具有自诊断功能的 test_basic.py 脚本
import sys from gnosis.xml.objectify import XML_Objectify, pyobj_printer, EXPAT, DOM LF = "\n" def show(xml_src, parser): """Self test using simple or user-specified XML data >>> xml = '''<?xml version="1.0"?> ... <!DOCTYPE Spam SYSTEM "spam.dtd" > ... <Spam> ... <Eggs>Some text about eggs.</Eggs> ... <MoreSpam>Ode to Spam</MoreSpam> ... </Spam>''' >>> squeeze = lambda s: s.replace(LF*2,LF).strip() >>> print squeeze(show(xml,DOM)[0]) -----* _XO_Spam *----- {Eggs} PCDATA=Some text about eggs. {MoreSpam} PCDATA=Ode to Spam >>> print squeeze(show(xml,EXPAT)[0]) -----* _XO_Spam *----- {Eggs} PCDATA=Some text about eggs. {MoreSpam} PCDATA=Ode to Spam PCDATA= """ try: xml_obj = XML_Objectify(xml_src, parser=parser) py_obj = xml_obj.make_instance() return (pyobj_printer(py_obj).encode('UTF-8'), "++ SUCCESS (using "+parser+")\n") except: return ("","++ FAILED (using "+parser+")\n") if __name__ == "__main__": if len(sys.argv)==1 or sys.argv[1]=="-v": import doctest, test_basic doctest.testmod(test_basic) elif sys.argv[1] in ('-h','-help','--help'): print "You may specify XML files to objectify instead of self-test" print "(Use '-v' for verbose output, otherwise no message means success)" else: for filename in sys.argv[1:]: for parser in (DOM, EXPAT): output, message = show(filename, parser) print output sys.stderr.write(message) print "="*50
注意,我在经过改进(和扩展)的测试脚本中放入了 main 代码块,这样,如果您在命令行中指定了 XML 文件,脚本将继续执行以前的行为。这样就让您可以继续分析测试用例以外其他的 XML,并只着眼于结果 -- 或者找出 gnosis.xml.objectify 所做事情中的错误,或者只是理解其目的。按标准的方式,您可以使用 -h 或 --help 参数来获得用法的说明。
当不带任何参数(或者带有只被 doctest 使用的 -v 参数)运行 test_basic.py 时,就会发现有趣的新功能。在这个例子中,我们在模块/脚本自身上运行 doctest -- 您可以看到,实际上我们将 test_basic 导入到脚本自己的名称空间中,这样我们可以简单地导入其他希望要测试的模块。 doctest.testmod() 函数去遍历模块本身、它的函数以及它的类中的所有文档字符串,以找出所有类似交互式 shell 会话的内容;在这个例子中,会在 show() 函数中找到这样一个会话。
show() 的文档字符串举例说明了在设计好的 doctest 会话过程中的几个小“陷阱(gotchas)”。不幸的是, doctest 在解析显式会话时,将空行作为会话结束来处理 -- 所以,像 pyobj_printer() 的返回值这样的输出需要加一些保护(be munged slightly)以进行测试。最简单的途径是使用文档字符串本身所定义的像 squeeze() 这样的函数(它只是除去紧跟在后面的换行)。此外,由于文档字符串毕竟是字符串换码(escape),所以 \n 这样的序列被扩展,这样使得在代码示例 内部对换行进行换码稍微有一些混乱。您可以使用 \\n ,不过我发现对 LF 的定义解决了这些问题。
在 show() 的文档字符串中定义的自测试所做的不仅是确保不发生异常(对照于最初的测试脚本)。为正确的“对象化(objectification)”至少要检查一个简单的 XML 文档。当然,仍然有可能不能正确地处理一些其他的 XML 文档 -- 例如,上面我们试过的名称空间 XML 文档 testns.xml 遇到了 EXPAT 解析器失败。由 doctest处理的文档字符串 可能会在其内部包含回溯(traceback),但是在特别的情况下,更好的方法是使用 unittest 。
使用 unittest
另一个包含在 gnosis.xml.objectify 中的测试是 test_expat.py 。创建这一测试的主要原因仅在于,使用 EXPAT 解析器的子软件包用户常常需要调用一个特别的设置函数来启用有名称空间的 XML 文档的处理(这个实际情况是演化来的而不是设计如此,并且以后可能会改变)。老的测试会试图不借助设置去打印对象,如果发生异常则捕获之,然后如果需要的话借助设置再去打印(并给出一个关于所发生事情的消息)。
而如果使用 test_basic.py , test_expat.py 工具让您可以分析 gnosis.xml.objectify 如何去描述一个新奇的 XML 文档。但是与以前一样,有很多我们可能想去验证的具体行为。 test_expat.py 的一个增强的、扩展的版本使用 unittest 来分析各种动作执行时发生的事情,包括持有特定条件或(近似)等式的断言,或出现期望的某些异常。看一看:
清单 4. 自诊断的 test_expat.py 脚本
"Objectify using Expat parser, namespace setup where needed" import unittest, sys, cStringIO from os.path import isfile from gnosis.xml.objectify import make_instance, config_nspace_sep,\ XML_Objectify BASIC, NS = 'test.xml','testns.xml' class Prerequisite(unittest.TestCase): def testHaveLibrary(self): "Import the gnosis.xml.objectify library" import gnosis.xml.objectify def testHaveFiles(self): "Check for sample XML files, NS and BASIC" self.failUnless(isfile(BASIC)) self.failUnless(isfile(NS)) class ExpatTest(unittest.TestCase): def setUp(self): self.orig_nspace = XML_Objectify.expat_kwargs.get('nspace_sep','') def testNoNamespace(self): "Objectify namespace-free XML document" o = make_instance(BASIC) def testNamespaceFailure(self): "Raise SyntaxError on non-setup namespace XML" self.assertRaises(SyntaxError, make_instance, NS) def testNamespaceSuccess(self): "Sucessfully objectify NS after setup" config_nspace_sep(None) o = make_instance(NS) def testNspaceBasic(self): "Successfully objectify BASIC despite extra setup" config_nspace_sep(None) o = make_instance(BASIC) def tearDown(self): XML_Objectify.expat_kwargs['nspace_sep'] = self.orig_nspace if __name__ == '__main__': if len(sys.argv) == 1: unittest.main() elif sys.argv[1] in ('-q','--quiet'): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(Prerequisite)) suite.addTest(unittest.makeSuite(ExpatTest)) out = cStringIO.StringIO() results = unittest.TextTestRunner(stream=out).run(suite) if not results.wasSuccessful(): for failure in results.failures: print "FAIL:", failure[0] for error in results.errors: print "ERROR:", error[0] elif sys.argv[1].startswith('-'): # pass args to unittest unittest.main() else: from gnosis.xml.objectify import pyobj_printer as show config_nspace_sep(None) for fname in sys.argv[1:]: print show(make_instance(fname)).encode('UTF-8')
使用 unittest 为较简单的 doctest 方式增添了相当多的能力。我们可以将我们的测试分为几个类,每一个类都继承自 unittest.TestCase 。在每一个测试类内部,每一个名称以“.test”开始的方法都被认为是另一个测试。为 ExpatTest 定义的两个额外的类很有趣:在每次使用类执行测试前运行 .setUp() ,测试结束时运行 .tearDown() (不管测试是成功、失败还是出现错误)。在我们上面的例子中,我们为专用的 expat_kwargs 字典做了一点簿记以确保每个测试独立地运行。
顺便提一下,失败(failure)和错误(error)之间的区别很重要。一个测试可能会因为一些具体的断言无效而失败(断言方法或者以“.fail”开头,或者以“.assert”开头)。在某种意义上,失败是期望中的 -- 最起码从某种意义上我们已经具体分析过。另一方面,错误是意外的问题 -- 因为我们事先不知道哪里会出错,我们需要分析实际测试运行中的回溯来诊断这种问题。不过,我们可以设计让失败给出诊断错误的提示。例如,如果 Prerequisite.haveFiles() 失败,将在一些 TestExpat 测试中出现错误;如果前者是成功的,您将不得不到其他地方去查找错误的根源。
在 unittest.TestCase 的继承类中,具体的测试方法中可能会包括一些 .assert...() 或者 .fail...() 方法,但也可能只是具有一系列我们相信应该会成功执行的动作。如果测试方法没有按预期运行,我们将得到一个错误(以及描述这个错误的回溯)。
test_expat.py 中的 _main_ 程序块也值得察看。在最简单的情况下,我们可以只使用 unittest.main() 来运行测试用例,这将断定哪些需要运行。使用这种方式时, unittest 模块将接受一个 -v 选项以给出更详细的输出。根据指定的文件名,在执行了名称空间设置后,我们打印出指定的 XML 文件的表示,从而大致上保持了对此工具稍老版本的向后兼容。
_main_ 中最有趣的分支是期待 -q 或 --quiet 标签的那个分支。如您将期望的,除非发生失败或错误,否则这个分支将是静默的(quiet,即尽量减少输出)。不仅如此,由于它是静默的,它只会为每个问题显示一行关于失败/错误位置的报告,而不是整个诊断回溯。除了对静默输出风格的直接利用以外,这个分支还举例说明了相对于测试套件的自定义测试以及对结果报告的控制。稍微有些长的 unittest.TextTestRunner() 的默认输出被定向到 StringIO out -- 如果您想查看它,欢迎您到 out.getvalue() 去查找。不过, result 对象让我们对全面成功进行测试,如果不是完全成功还可以让我们处理失败和错误。显然,由于它们是变量中的值,您可以轻松地将 result 对象的内容记录入日志,或者在 GUI 中显示,不管怎么样,不是仅仅打印到 STDOUT。
组合测试
可能 unittest 框架最好的特性是让您可以轻松地组合包含不同模块的测试。实际上,如果使用 Python 2.3+,您甚至可以将 doctest 测试转化为 unittest 套件。让我们将到目前为止所创建的测试组合到一个脚本 test_all.py 中(诚然,说它是我们目前为止所做的测试有些夸张):
清单 5. test_all.py 组合了单元测试
"Combine tests for gnosis.xml.objectify package (req 2.3+)" import unittest, doctest, test_basic, test_expat suite = doctest.DocTestSuite(test_basic) suite.addTest(unittest.makeSuite(test_expat.Prerequisite)) suite.addTest(unittest.makeSuite(test_expat.ExpatTest)) unittest.TextTestRunner(verbosity=2).run(suite)
由于 test_expat.py 只是包含测试类,所以它们可以容易地添加到本地的测试套件中。 doctest.DocTestSuite() 函数执行文档字符串测试的转换。让我们来看看 test_all.py 运行时会做什么:
清单 6. 来自 test_all.py 的成功输出
$ python2.3 test_all.py doctest of test_basic.show ... ok Check for sample XML files, NS and BASIC ... ok Import the gnosis.xml.objectify library ... ok Raise SyntaxError on non-setup namespace XML ... ok Sucessfully objectify NS after setup ... ok Objectify namespace-free XML document ... ok Successfully objectify BASIC despite extra setup ... ok ---------------------------------------------------------------------- Ran 7 tests in 0.052s OK
注意对执行的测试的描述:在使用 unittest 测试方法的情况下,他们的描述来自于相应的 docstring 函数。如果您没有指定文档字符串,类和方法名被用作最合适的描述。来看一下如果一些测试失败时我们会得到什么,同样有趣(为本文去掉了回溯细节):
清单 7. 当一些测试失败时的结果
$ mv testns.xml testns.xml# && python2.3 test_all.py 2>&1 | head -7 doctest of test_basic.show ... ok Check for sample XML files, NS and BASIC ... FAIL Import the gnosis.xml.objectify library ... ok Raise SyntaxError on non-setup namespace XML ... ERROR Sucessfully objectify NS after setup ... ERROR Objectify namespace-free XML document ... ok Successfully objectify BASIC despite extra setup ... ok
随便提及,这个失败写到 STDERR 的最后一行是“FAILED (failures=1, errors=2)”,如果您需要的话这是一个很好的总结(相对于成功时最终的“OK”)。
从这里开始
本文向您介绍了 unittest 和 doctest 的一些典型用法,它们已经改进了我自己的软件中的测试。阅读 Python 文档,以深入了解可用于测试套件、测试用例和测试结果的全部范围的方法。它们全部都遵循例子中所描述的模式。
让自己遵从 Python 的标准测试模块规定的方法学是良好的软件实践。测试驱动(test-driven)的开发在很多软件周期中都很流行;不过,显然 Python 是一门适合于测试驱动模型的语言。而且,如果只是考虑软件包更可能按计划工作,一个软件包或库如果伴随有一组周全的测试,会比缺乏这些测试的软件包或库对用户更为有用。

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











PHP는 주로 절차 적 프로그래밍이지만 객체 지향 프로그래밍 (OOP)도 지원합니다. Python은 OOP, 기능 및 절차 프로그래밍을 포함한 다양한 패러다임을 지원합니다. PHP는 웹 개발에 적합하며 Python은 데이터 분석 및 기계 학습과 같은 다양한 응용 프로그램에 적합합니다.

PHP는 웹 개발 및 빠른 프로토 타이핑에 적합하며 Python은 데이터 과학 및 기계 학습에 적합합니다. 1.PHP는 간단한 구문과 함께 동적 웹 개발에 사용되며 빠른 개발에 적합합니다. 2. Python은 간결한 구문을 가지고 있으며 여러 분야에 적합하며 강력한 라이브러리 생태계가 있습니다.

Python은 부드러운 학습 곡선과 간결한 구문으로 초보자에게 더 적합합니다. JavaScript는 가파른 학습 곡선과 유연한 구문으로 프론트 엔드 개발에 적합합니다. 1. Python Syntax는 직관적이며 데이터 과학 및 백엔드 개발에 적합합니다. 2. JavaScript는 유연하며 프론트 엔드 및 서버 측 프로그래밍에서 널리 사용됩니다.

PHP는 1994 년에 시작되었으며 Rasmuslerdorf에 의해 개발되었습니다. 원래 웹 사이트 방문자를 추적하는 데 사용되었으며 점차 서버 측 스크립팅 언어로 진화했으며 웹 개발에 널리 사용되었습니다. Python은 1980 년대 후반 Guidovan Rossum에 의해 개발되었으며 1991 년에 처음 출시되었습니다. 코드 가독성과 단순성을 강조하며 과학 컴퓨팅, 데이터 분석 및 기타 분야에 적합합니다.

VS 코드는 Windows 8에서 실행될 수 있지만 경험은 크지 않을 수 있습니다. 먼저 시스템이 최신 패치로 업데이트되었는지 확인한 다음 시스템 아키텍처와 일치하는 VS 코드 설치 패키지를 다운로드하여 프롬프트대로 설치하십시오. 설치 후 일부 확장은 Windows 8과 호환되지 않을 수 있으며 대체 확장을 찾거나 가상 시스템에서 새로운 Windows 시스템을 사용해야합니다. 필요한 연장을 설치하여 제대로 작동하는지 확인하십시오. Windows 8에서는 VS 코드가 가능하지만 더 나은 개발 경험과 보안을 위해 새로운 Windows 시스템으로 업그레이드하는 것이 좋습니다.

VS 코드는 파이썬을 작성하는 데 사용될 수 있으며 파이썬 애플리케이션을 개발하기에 이상적인 도구가되는 많은 기능을 제공합니다. 사용자는 다음을 수행 할 수 있습니다. Python 확장 기능을 설치하여 코드 완료, 구문 강조 및 디버깅과 같은 기능을 얻습니다. 디버거를 사용하여 코드를 단계별로 추적하고 오류를 찾아 수정하십시오. 버전 제어를 위해 git을 통합합니다. 코드 서식 도구를 사용하여 코드 일관성을 유지하십시오. 라인 도구를 사용하여 잠재적 인 문제를 미리 발견하십시오.

vs 코드에서는 다음 단계를 통해 터미널에서 프로그램을 실행할 수 있습니다. 코드를 준비하고 통합 터미널을 열어 코드 디렉토리가 터미널 작업 디렉토리와 일치하는지 확인하십시오. 프로그래밍 언어 (예 : Python의 Python Your_file_name.py)에 따라 실행 명령을 선택하여 성공적으로 실행되는지 여부를 확인하고 오류를 해결하십시오. 디버거를 사용하여 디버깅 효율을 향상시킵니다.

VS 코드 확장은 악의적 인 코드 숨기기, 취약성 악용 및 합법적 인 확장으로 자위하는 등 악성 위험을 초래합니다. 악의적 인 확장을 식별하는 방법에는 게시자 확인, 주석 읽기, 코드 확인 및주의해서 설치가 포함됩니다. 보안 조치에는 보안 인식, 좋은 습관, 정기적 인 업데이트 및 바이러스 백신 소프트웨어도 포함됩니다.
