├── .editorconfig ├── .gitignore ├── .textlintrc ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Makefile ├── README.md ├── requirements.txt └── src ├── _static └── .gitkeep ├── annotations.rst ├── assertions.rst ├── bibliography.rst ├── code-coverage-analysis.rst ├── conf.py ├── configuration.rst ├── copyright.rst ├── extending-phpunit.rst ├── fixtures.rst ├── incomplete-and-skipped-tests.rst ├── index.rst ├── installation.rst ├── organizing-tests.rst ├── risky-tests.rst ├── test-doubles.rst ├── textui.rst └── writing-tests-for-phpunit.rst /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_style = space 11 | indent_size = 2 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | 18 | [*.php] 19 | indent_size = 4 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /build 3 | /node_modules 4 | /package-lock.json 5 | 6 | -------------------------------------------------------------------------------- /.textlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "filters": {}, 3 | "plugins": [ 4 | "rst" 5 | ], 6 | "rules": { 7 | "alex": true, 8 | "common-misspellings": true, 9 | "en-capitalization": true, 10 | "no-todo": true, 11 | "stop-words": true, 12 | "terminology": true, 13 | "write-good": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # 贡献者行为准则 2 | 3 | 作为该项目的贡献者和维护者,为了建立一个开放而热情的社区,我们承诺尊重所有通过报告问题、发表功能请求、更新文档、提交拉取请求或补丁以及其他活动做出贡献的人。 4 | 5 | 我们致力于使每个人都能无困扰地参与该项目,而无关其经验水平、性别、性别认同和表达方式、性取向、残疾、个人面貌、身材、种族、种族、年龄、宗教信仰或国籍。 6 | 7 | 参与者不可接受的行为示例包括: 8 | 9 | * 使用性相关的语言或图像 10 | * 人身攻击 11 | * 小白行为或侮辱性/贬损性评论 12 | * 公开或私下的骚扰 13 | * 未经明确许可发布他人的私人信息,例如现实或网络地址 14 | * 其他不道德或不专业的行为 15 | 16 | 项目维护者有权并有责任删除、编辑或拒绝与本行为准则不符的评论、提交、代码、Wiki 编辑、问题和其他贡献,或暂时或永久地禁止某个贡献者进行被项目维护者认定为不当、有威胁、冒犯或有害的其他行为。 17 | 18 | 通过采用本《行为准则》,项目维护者承诺将公平、一致地将这些原则应用于项目管理的各个方面。不遵守或不执行《行为准则》的项目维护者可能会被从项目团队中永久撤职。 19 | 20 | 当个人代表项目或其社区时,对于项目范畴和公共范畴,皆适用本《行为准则》。 21 | 22 | 可以通过 sebastian@phpunit.de 联系项目维护人员来举报滥用、骚扰或其他不可接受的行为。所有的投诉都将得到审查和调查,并将根据情况做出认为是必要和适当的回应。维护者有义务对事件的举报人保密。 23 | 24 | 本行为准则改编自《[Contributor Covenant(贡献者契约)][homepage]》,版本 1.3.0,可从[https://contributor-covenant.org/version/1/3/0/][version]获取。 25 | 26 | [homepage]: https://contributor-covenant.org 27 | [version]: https://contributor-covenant.org/version/1/3/0/ 28 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the PHPUnit Documentation 2 | 3 | ## 贡献者行为准则 4 | 5 | 请注意,此项目发布时带有《[贡献者行为准则](CODE_OF_CONDUCT.md)》。 6 | 参与此项目即意味着您同意遵守其条款。 7 | 8 | ## 工作流程 9 | 10 | * Fork the project 11 | * Fork 此项目 12 | * 运行 `make html`,确定文档可以完成构建并且显示情况符合预期 13 | * 向文档中和你的更改有关的最旧分支发送拉取请求 14 | 15 | 如果您的拉取请求修正了 bug 或错别字,请以受影响的最旧分支为目标。 16 | 17 | 请确保你已经设置好了用于 Git 的[用户名和 Email 地址](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup)。 18 | 在项目的提交历史中看到诸如 `silly nick name ` 19 | 这样的字符串出现真的挺傻的。 20 | 21 | 由于时间限制,我们无法始终以最快的速度做出回应。请不要为此耽误个人时间, 22 | 并且,如果您觉得我们忘了回应,随时可以提醒我们。 23 | 24 | ## 报告问题 25 | 26 | 请使用受影响的语言的问题跟踪器来开启新事务。 27 | 通用问题则总是应当在英文文档的问题跟踪器中进行报告。 28 | 29 | * [英文文档](https://github.com/sebastianbergmann/phpunit-documentation-english/issues) 30 | * [西班牙语文档](https://github.com/sebastianbergmann/phpunit-documentation-spanish/issues) 31 | * [法语文档](https://github.com/sebastianbergmann/phpunit-documentation-french/issues) 32 | * [巴西葡萄牙语文档](https://github.com/sebastianbergmann/phpunit-documentation-brazilian-portuguese/issues) 33 | * [日语文档](https://github.com/sebastianbergmann/phpunit-documentation-japanese/issues) 34 | * [简体中文文档](https://github.com/sebastianbergmann/phpunit-documentation-chinese/issues) 35 | * [俄语文档](https://github.com/sebastianbergmann/phpunit-documentation-russian/issues) 36 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) src 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) src 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PHPUnit.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PHPUnit.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/PHPUnit" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PHPUnit" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **The PHPUnit project is no longer involved with the translation of its documentation to other languages. This repository is now archived.** 2 | 3 | # 翻译 4 | 5 | 文档的每一种翻译都用单独的存储库来维护: 6 | 7 | * [英文文档](https://github.com/sebastianbergmann/phpunit-documentation-english) 8 | * [西班牙语文档](https://github.com/sebastianbergmann/phpunit-documentation-spanish) 9 | * [法语文档](https://github.com/sebastianbergmann/phpunit-documentation-french) 10 | * [巴西葡萄牙语文档](https://github.com/sebastianbergmann/phpunit-documentation-brazilian-portuguese) 11 | * [日语文档](https://github.com/sebastianbergmann/phpunit-documentation-japanese) 12 | * [简体中文文档](https://github.com/sebastianbergmann/phpunit-documentation-chinese) 13 | * [俄语文档](https://github.com/sebastianbergmann/phpunit-documentation-russian) 14 | 15 | ## 添加新的翻译 16 | 17 | 如果要创建新的翻译,请在英语文档的问题跟踪器(issue tracker)中开一个新的问题(issue), 18 | 说明您要翻译的语言。会创建一个新的存储库并将其添加到可用翻译中。 19 | 20 | 理想情况下,您应当已经基于英语文档的分支或副本而准备好一个翻译的版本, 21 | 这个版本稍后将导入到官方存储库中。 22 | 23 | # 构建文档 24 | 25 | ## 需求 26 | 27 | - Python 28 | - [Sphinx](http://www.sphinx-doc.org/) 29 | - [Read the Docs Sphinx Theme](https://github.com/rtfd/sphinx_rtd_theme) 30 | 31 | ## 构建 HTML 文档 32 | 33 | 要构建完整的文档,运行: 34 | 35 | ``` 36 | $ make html 37 | ``` 38 | 39 | 之后你就可以在 `build/html` 中找到相应的 HTML 文件了。 40 | 41 | ## 校对自动化 42 | 43 | ### 安装 44 | 45 | ``` 46 | $ pip install docutils-ast-writer 47 | $ npm install 48 | ``` 49 | 50 | ### 使用 51 | 52 | ``` 53 | $ ./node_modules/.bin/textlint src 54 | ``` 55 | 56 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx==1.6.5 2 | sphinx_rtd_theme==0.3.0 3 | -------------------------------------------------------------------------------- /src/_static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastianbergmann/phpunit-documentation-chinese/74185373cbd06adc2870a368ec6c361210e8d617/src/_static/.gitkeep -------------------------------------------------------------------------------- /src/annotations.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. _appendixes.annotations: 4 | 5 | =========== 6 | 标注 7 | =========== 8 | 9 | 所谓标注,是指某些编程语言中允许加在源代码中的一种特殊格式的语法元数据。PHP 并没有专门的语言特性来支持对源代码进行标注,然而 PHP 社区早已经形成惯例,通过在文档注释块中使用诸如 ``@annotation arguments`` 这样的标签来为源代码加上标注。在 PHP 中,文档注释块是可反射的:可以对函数、方法、类以及属性调用相应级别的反射 API ``getDocComment()`` 方法来获取相应的文档注释块。诸如 PHPUnit 这样的应用程序在运行时用这些信息来配置其行为。 10 | 11 | 12 | .. admonition:: 注 13 | 14 | PHP中的文档注释块必须以 ``/**`` 开头,以 ``*/`` 结尾。任何其他形式的注释中出现的标注都将被忽略。 15 | 16 | 本附录列出了 PHPUnit 所支持的所有标注种类。 17 | 18 | .. _appendixes.annotations.author: 19 | 20 | @author 21 | ####### 22 | 23 | ``@author`` 标注是 ``@group`` 标注(参见 :ref:`appendixes.annotations.group`\ )的别名,允许基于作者对测试进行过滤。 24 | 25 | .. _appendixes.annotations.after: 26 | 27 | @after 28 | ###### 29 | 30 | ``@after`` 标注用于指明此方法应当在测试用例类中的每个测试方法运行完成之后调用。 31 | 32 | .. code-block:: php 33 | 34 | assertSame(0, $this->ba->getBalance()); 274 | } 275 | 276 | 如果提供,这将有效地过滤代码覆盖率报告,将其限制为仅包括所指代码部分中的已执行部分。这将确保代码只在有针对它的专用测试覆盖的情况下才会被标记为已覆盖,而如果它被针对其他类的测试间接使用则并不会标记为已覆盖,从而避免代码覆盖范围的误报。 277 | 278 | 279 | 此标注可以添加给测试类的文档注释块,也可以添加给单个测试方法的文档注释块。推荐的方法是将此标注添加给测试类的文档注释块而不是测试方法的文档注释块。 280 | 281 | 如果\ :ref:`配置文件 `\ 中的 ``forceCoversAnnotation`` 配置选项设置为 ``true``\ ,则每个测试方法都必须拥有相应的 ``@covers`` 标注(无论是在测试类还是单个测试方法上)。 282 | 283 | :numref:`appendixes.annotations.covers.tables.annotations` 展示了 ``@covers`` 标注的语法。 284 | :ref:`code-coverage-analysis.specifying-covered-parts`\ 这部分有关于使用此标注的更长一些的示例。 285 | 286 | 请注意,此标注要求用完全限定类名(FQCN,fully-qualified class name)。为了让读者更容易理解,推荐写上开头的反斜杠(虽然此标注并不要求如此也能正常运行)。 287 | 288 | .. rst-class:: table 289 | .. list-table:: 用于指明测试覆盖哪些方法的标注 290 | :name: appendixes.annotations.covers.tables.annotations 291 | :header-rows: 1 292 | 293 | * - 标注 294 | - 描述 295 | * - ``@covers ClassName::methodName``\ (不推荐) 296 | - 指明所标注的测试方法覆盖指定的方法。 297 | * - ``@covers ClassName``\ (推荐) 298 | - 指明所标注的测试方法覆盖给定类的全部方法。 299 | * - ``@covers ClassName``\ (不推荐) 300 | - 指明所标注的测试方法覆盖给定类以及其所有父类的全部方法。 301 | * - ``@covers ClassName::``\ (不推荐) 302 | - 指明所标注的测试方法覆盖给定类的所有 public 方法。 303 | * - ``@covers ClassName::``\ (不推荐) 304 | - 指明所标注的测试方法覆盖给定类的所有 protected 方法。 305 | * - ``@covers ClassName::``\ (不推荐) 306 | - 指明所标注的测试方法覆盖给定类的所有 private 方法。 307 | * - ``@covers ClassName::``\ (不推荐) 308 | - 指明所标注的测试方法覆盖给定类的所有非 public 方法。 309 | * - ``@covers ClassName::``\ (不推荐) 310 | - 指明所标注的测试方法覆盖给定类的所有非 protected 方法。 311 | * - ``@covers ClassName::``\ (不推荐) 312 | - 指明所标注的测试方法覆盖给定类的所有非 private 方法。 313 | * - ``@covers ::functionName``\ (推荐) 314 | - 指明所标注的测试方法覆盖给定的全局函数。 315 | 316 | .. _appendixes.annotations.coversDefaultClass: 317 | 318 | @coversDefaultClass 319 | ################### 320 | 321 | ``@coversDefaultClass`` 标注用于指定一个默认的命名空间或类名,这样就不用在每个 ``@covers`` 标注中重复长名称。参见\ :numref:`appendixes.annotations.examples.CoversDefaultClassTest.php`\ 。 322 | 323 | 请注意,此标注要求用完全限定类名(FQCN,fully-qualified class name)。为了让读者更容易理解,推荐写上开头的反斜杠(虽然此标注并不要求如此也能正常运行)。 324 | 325 | .. code-block:: php 326 | :caption: 用 @coversDefaultClass 来缩短标注 327 | :name: appendixes.annotations.examples.CoversDefaultClassTest.php 328 | 329 | publicMethod(); 344 | } 345 | } 346 | 347 | .. _appendixes.annotations.coversNothing: 348 | 349 | @coversNothing 350 | ############## 351 | 352 | 在测试代码中用 ``@coversNothing`` 标注来指明所标注的测试用例不需要记录任何代码覆盖率信息。 353 | 354 | 这可以用于集成测试。例子可参见\ :ref:`code-coverage-analysis.specifying-covered-parts.examples.GuestbookIntegrationTest.php`。 355 | 356 | 这个标注可以用在类级别或者方法级别,并且会覆盖掉所有 ``@covers`` 标注。 357 | 358 | .. _appendixes.annotations.dataProvider: 359 | 360 | @dataProvider 361 | ############# 362 | 363 | 测试方法可以接受任意参数。这些参数由一个或多个数据供给器方法(在\ :ref:`writing-tests-for-phpunit.data-providers.examples.DataTest.php`\ 中,是 ``provider()`` 方法)提供。用 ``@dataProvider`` 标注来指定要使用的数据供给器方法。 364 | 365 | 更多细节,参见\ :ref:`writing-tests-for-phpunit.data-providers`。 366 | 367 | .. _appendixes.annotations.depends: 368 | 369 | @depends 370 | ######## 371 | 372 | PHPUnit 支持对测试方法之间的显式依赖关系进行声明。这种依赖关系并不是定义在测试方法的执行顺序中,而是允许生产者(producer)返回一个测试基境(fixture)的实例,并将此实例传递给依赖于它的消费者(consumer)们。\ :ref:`writing-tests-for-phpunit.examples.StackTest2.php`\ 展示了如何用 ``@depends`` 标注来表达测试方法之间的依赖关系。 373 | 374 | 更多细节,参见\ :ref:`writing-tests-for-phpunit.test-dependencies`。 375 | 376 | .. _appendixes.annotations.doesNotPerformAssertions: 377 | 378 | @doesNotPerformAssertions 379 | ######################### 380 | 381 | 防止不执行任何断言的测试被视为有风险。 382 | 383 | .. _appendixes.annotations.group: 384 | 385 | @group 386 | ###### 387 | 388 | 测试可以用 ``@group`` 标注来标记为属于一个或多个组,就像这样: 389 | 390 | .. code-block:: php 391 | 392 | assertSame(0, $this->ba->getBalance()); 543 | } 544 | 545 | .. _appendixes.annotations.testdox: 546 | 547 | @testdox 548 | ######## 549 | 550 | 指定生成敏捷文档句子时使用的替换描述。 551 | 552 | 测试类和测试方法都可以应用 ``@testdox`` 标注。 553 | 554 | .. code-block:: php 555 | 556 | assertSame(0, $this->ba->getBalance()); 570 | } 571 | } 572 | 573 | .. admonition:: 注 574 | 575 | 在 PHPUnit 7.0 之前(由于标注解析中的一个 bug),使用 ``@testdox`` 标注也会激活 ``@test`` 标注的行为。 576 | 577 | 如果将 ``@testdox`` 标注在方法级别和 ``@dataProvider`` 联用,可以在替换描述中将方法参数用作占位符。 578 | 579 | .. code-block:: php 580 | 581 | /** 582 | * @dataProvider additionProvider 583 | * @testdox Adding $a to $b results in $expected 584 | */ 585 | public function testAdd($a, $b, $expected) 586 | { 587 | $this->assertSame($expected, $a + $b); 588 | } 589 | 590 | public function additionProvider() 591 | { 592 | return [ 593 | [0, 0, 0], 594 | [0, 1, 1], 595 | [1, 0, 1], 596 | [1, 1, 3] 597 | ]; 598 | } 599 | 600 | .. _appendixes.annotations.testWith: 601 | 602 | @testWith 603 | ######### 604 | 605 | 除了实现一个方法并将之与 ``@dataProvider`` 联用外,你也可以用 ``@testWith`` 标注来定义数据集。 606 | 607 | 数据集由一个或多个元素组成。要定义具有多个元素的数据集,每个元素都要定义在单独一行中。数据集的每个元素都必须是以 JSON 格式定义的数组。 608 | 609 | 参见\ :ref:`writing-tests-for-phpunit.data-providers`\ 来学习更多关于传递数据集合给测试的信息。 610 | 611 | .. code-block:: php 612 | 613 | /** 614 | * @testWith ["test", 4] 615 | * ["longer-string", 13] 616 | */ 617 | public function testStringLength(string $input, int $expectedLength): void 618 | { 619 | $this->assertSame($expectedLength, strlen($input)); 620 | } 621 | 622 | 以 JSON 格式表示的对象会转换为关联数组。 623 | 624 | .. code-block:: php 625 | 626 | /** 627 | * @testWith [{"day": "monday", "conditions": "sunny"}, ["day", "conditions"]] 628 | */ 629 | public function testArrayKeys(array $array, array $keys): void 630 | { 631 | $this->assertSame($keys, array_keys($array)); 632 | } 633 | 634 | .. _appendixes.annotations.ticket: 635 | 636 | @ticket 637 | ####### 638 | 639 | ``@ticket`` 标注是 ``@group`` 标注(参见 :ref:`appendixes.annotations.group`\ )的别名,允许基于事务 ID 对测试进行过滤。 640 | 641 | .. _appendixes.annotations.uses: 642 | 643 | @uses 644 | ##### 645 | 646 | ``@uses`` 标注用来指明那些将会在测试中执行到但同时又不打算让其被测试所覆盖的代码。在对代码单元进行测试时所必须的值对象就是个很好的例子。 647 | 648 | .. code-block:: php 649 | 650 | /** 651 | * @covers \BankAccount 652 | * @uses \Money 653 | */ 654 | public function testMoneyCanBeDepositedInAccount(): void 655 | { 656 | // ... 657 | } 658 | 659 | :numref:`code-coverage-analysis.specifying-covered-parts.examples.InvoiceTest.php` 展示了另一个示例。 660 | 661 | 在严格覆盖模式中,意外覆盖的代码将导致测试判定为失败,这个标注就比较有用,另外它也有助于阅读代码。关于严格覆盖模式的更多信息,参见\ :ref:`risky-tests.unintentionally-covered-code`。 662 | 663 | 请注意,此标注要求用完全限定类名(FQCN,fully-qualified class name)。为了让读者更容易理解,推荐写上开头的反斜杠(虽然此标注并不要求如此也能正常运行)。 664 | -------------------------------------------------------------------------------- /src/bibliography.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. _appendixes.bibliography: 4 | 5 | ============ 6 | 参考书目 7 | ============ 8 | 9 | [Meszaros2007] Gerard Meszaros《xUnit Test Patterns: Refactoring Test Code》。 10 | -------------------------------------------------------------------------------- /src/code-coverage-analysis.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. _code-coverage-analysis: 4 | 5 | ====================== 6 | 代码覆盖率分析 7 | ====================== 8 | 9 | *Wikipedia*\ : 10 | 11 | 计算机科学中所说的代码覆盖率是一种用于衡量特定测试套件对程序源代码测试程度的指标。拥有高代码覆盖率的程序相较于低代码低概率的程序而言测试的更加彻底、包含软件 bug 的可能性更低。 12 | 13 | 在本章中,你将学到 PHPUnit 中与代码覆盖率相关的一切功能。通过这部分功能,能够了解在测试运行过程中执行了生产代码的哪些部分。它使用了 `php-code-coverage `_ 组件,而这个组件又使用了 PHP 的 `Xdebug `_ 或 `PCOV `_ 扩展或 `PHPDBG `_ 所提供的代码覆盖率功能。 14 | 15 | .. admonition:: 注 16 | 17 | 如果在运行测试时看到警告信息说没有可用的代码覆盖率驱动,这意味着你正在使用 PHP 命令行二进制文件(``php``)且未加载 Xdebug 或 PCOV。 18 | 19 | PHPUnit 可以生成基于 HTML 的代码覆盖率报告,同时也能生成好几种(Clover、Crap4J、PHPUnit)基于 XML 的代码覆盖率信息记录文件。代码覆盖率信息也能以文本格式提供(同时可以输出到 STDOUT)或以 PHP 代码格式输出以供进一步处理。 20 | 21 | :ref:`textui`\ 中列出了各种控制代码覆盖率功能的命令行参数供参考,同时\ :ref:`appendixes.configuration.logging`\ 中可以找到其他相关的配置信息。 22 | 23 | .. _code-coverage-analysis.metrics: 24 | 25 | 用于代码覆盖率的软件衡量标准 26 | ################################## 27 | 28 | 目前存在多种软件衡量标准用于衡量代码覆盖率: 29 | 30 | *行覆盖率(Line Coverage)* 31 | 32 | *行覆盖率(Line Coverage)*\ 按单个可执行行是否已执行进行计量。 33 | 34 | *分支覆盖率(Branch Coverage)* 35 | 36 | *分支覆盖率(Branch Coverage)*\ 按控制结构的分支进行计量。测试套件运行时每个控制结构的布尔表达式求值为 ``true`` 和 ``false`` 各自计为一个分支。 37 | 38 | *路径覆盖率(Path Coverage)* 39 | 40 | *路径覆盖率(Path Coverage)*\ 按测试套件运行时函数或者方法内部所经历的执行路径进行计量。一个执行路径指的是从进入函数或方法一直到离开的过程中经过各个分支的特定序列。 41 | 42 | *函数与方法覆盖率(Function and Method Coverage)* 43 | 44 | *函数与方法覆盖率(Function and Method Coverage)*\ 按单个函数或方法是否已调用进行计量。仅当函数或方法的所有可执行行全部已覆盖时 php-code-coverage 才将其视为已覆盖。 45 | 46 | *类与特质覆盖率(Class and Trait Coverage)* 47 | 48 | *类与特质覆盖率(Class and Trait Coverage)*\ 按单个类或特质的所有方法是否全部已覆盖进行计量。仅当一个类或特质的所有方法全部已覆盖时 php-code-coverage 才将其视为已覆盖。 49 | 50 | *变更风险反模式(CRAP)指数(Change Risk Anti-Patterns (CRAP) Index)* 51 | 52 | *变更风险反模式(CRAP)指数(Change Risk Anti-Patterns (CRAP) Index)*\ 是基于代码单元的圈复杂度(cyclomatic complexity)与代码覆盖率计算得出的。不太复杂并具有恰当测试覆盖率的代码将得出较低的 CRAP 指数。可以通过编写测试或重构代码来降低其复杂性的方式来降低 CRAP 指数。 53 | 54 | .. _code-coverage-analysis.including-files: 55 | 56 | 包含文件 57 | ############### 58 | 59 | 为了告诉 PHPUnit 哪些源代码文件要包含在代码覆盖率报告中,必须配置过滤器。可以用\ :ref:`命令行 `\ 选项 ``--coverage-filter`` 或通过配置文件(参见\ :ref:`appendixes.configuration.coverage.include`)来完成。 60 | 61 | ``includeUncoveredFilesInCodeCoverageReport`` 和 ``processUncoveredFilesForCodeCoverageReport`` 配置设置可用于配置过滤器的使用方式: 62 | 63 | - ``includeUncoveredFilesInCodeCoverageReport="false"`` 意味着只有至少有一行已执行代码的文件才会包括在代码覆盖率报告中 64 | 65 | - ``includeUncoveredFilesInCodeCoverageReport="true"``\ (默认值)意味着所有文件都会包括在代码覆盖率报告中,即使文件中没有任何一行代码被执行过也一样 66 | 67 | - ``processUncoveredFilesForCodeCoverageReport="false"``\ (默认值)意味着没有已执行代码行的文件会被加入到代码覆盖率报告中(如果设置了 ``includeUncoveredFilesInCodeCoverageReport="true"``),但它并不会被 PHPUnit 加载,因此也不会对其进行分析来获取正确的可执行代码行信息 68 | 69 | - ``processUncoveredFilesForCodeCoverageReport="true"`` 意味着没有已执行代码行的文件会被 PHPUnit 加载,从而也能对其进行分析来获取正确的可执行代码行信息 70 | 71 | .. admonition:: 注 72 | 73 | 请注意,当设置了 ``processUncoveredFilesForCodeCoverageReport="true"`` 时将对源代码文件进行载入,这在某些情况下可能导致问题,比如,源代码文件包含有处于类或者函数作用域之外的代码。 74 | 75 | .. _code-coverage-analysis.ignoring-code-blocks: 76 | 77 | 忽略代码块 78 | #################### 79 | 80 | 有时,一些代码块是无法对其进行测试的,因此希望在代码覆盖率分析中忽略它们。在 PHPUnit 中可以用 ``@codeCoverageIgnore``、``@codeCoverageIgnoreStart`` 与 ``@codeCoverageIgnoreEnd`` 标注来做到这点,如\ :numref:`code-coverage-analysis.ignoring-code-blocks.examples.Sample.php` 中所示。 81 | 82 | .. code-block:: php 83 | :caption: 使用 ``@codeCoverageIgnore``、``@codeCoverageIgnoreStart`` 和 ``@codeCoverageIgnoreEnd`` 标注 84 | :name: code-coverage-analysis.ignoring-code-blocks.examples.Sample.php 85 | 86 | `\ )可以用在测试代码中来指明测试类(或测试方法)想要对哪些代码部分进行测试。如果提供了这个信息,则可以有效过滤代码覆盖率报告,仅包含所指定的代码部分中的已执行代码。:numref:`code-coverage-analysis.specifying-covered-parts.examples.InvoiceTest.php` 展示了一个例子。 125 | 126 | 127 | .. admonition:: 注 128 | 129 | 如果用 ``@covers`` 标注指定了一个方法吗,那么只有所指方法会被视为已覆盖,这个方法所调用的方法不会视为已覆盖。因此,如果用\ *提取方法*\ 重构了已覆盖的方法,则需要添加相应的 ``@covers`` 标注。这就是推荐将此标注用在类作用域而非方法作用域的原因。 130 | 131 | .. code-block:: php 132 | :caption: 指明了要覆盖的类的测试类 133 | :name: code-coverage-analysis.specifying-covered-parts.examples.InvoiceTest.php 134 | 135 | invoice = new Invoice; 149 | } 150 | 151 | public function testAmountInitiallyIsEmpty(): void 152 | { 153 | $this->assertEquals(new Money, $this->invoice->getAmount()); 154 | } 155 | } 156 | 157 | .. code-block:: php 158 | :caption: 指明了要覆盖哪个方法的测试 159 | :name: code-coverage-analysis.specifying-covered-parts.examples.BankAccountTest.php 160 | 161 | ba = new BankAccount; 171 | } 172 | 173 | /** 174 | * @covers \BankAccount::getBalance 175 | */ 176 | public function testBalanceIsInitiallyZero(): void 177 | { 178 | $this->assertSame(0, $this->ba->getBalance()); 179 | } 180 | 181 | /** 182 | * @covers \BankAccount::withdrawMoney 183 | */ 184 | public function testBalanceCannotBecomeNegative(): void 185 | { 186 | try { 187 | $this->ba->withdrawMoney(1); 188 | } 189 | 190 | catch (BankAccountException $e) { 191 | $this->assertSame(0, $this->ba->getBalance()); 192 | 193 | return; 194 | } 195 | 196 | $this->fail(); 197 | } 198 | 199 | /** 200 | * @covers \BankAccount::depositMoney 201 | */ 202 | public function testBalanceCannotBecomeNegative2(): void 203 | { 204 | try { 205 | $this->ba->depositMoney(-1); 206 | } 207 | 208 | catch (BankAccountException $e) { 209 | $this->assertSame(0, $this->ba->getBalance()); 210 | 211 | return; 212 | } 213 | 214 | $this->fail(); 215 | } 216 | 217 | /** 218 | * @covers \BankAccount::getBalance 219 | * @covers \BankAccount::depositMoney 220 | * @covers \BankAccount::withdrawMoney 221 | */ 222 | public function testDepositWithdrawMoney(): void 223 | { 224 | $this->assertSame(0, $this->ba->getBalance()); 225 | $this->ba->depositMoney(1); 226 | $this->assertSame(1, $this->ba->getBalance()); 227 | $this->ba->withdrawMoney(1); 228 | $this->assertSame(0, $this->ba->getBalance()); 229 | } 230 | } 231 | 232 | 同时,可以用 ``@coversNothing`` 标注来指明一个测试不覆盖\ *任何*\ 方法(参见\ :ref:`appendixes.annotations.coversNothing`)。这可以在编写集成测试时用于确保代码覆盖全部来自单元测试。 233 | 234 | .. code-block:: php 235 | :caption: 指明应当不覆盖任何方法的测试 236 | :name: code-coverage-analysis.specifying-covered-parts.examples.GuestbookIntegrationTest.php 237 | 238 | addEntry("suzy", "Hello world!"); 250 | 251 | $queryTable = $this->getConnection()->createQueryTable( 252 | 'guestbook', 'SELECT * FROM guestbook' 253 | ); 254 | 255 | $expectedTable = $this->createFlatXmlDataSet("expectedBook.xml") 256 | ->getTable("guestbook"); 257 | 258 | $this->assertTablesEqual($expectedTable, $queryTable); 259 | } 260 | } 261 | 262 | .. _code-coverage-analysis.edge-cases: 263 | 264 | 边缘情况 265 | ########## 266 | 267 | 本节中展示了一些值得注意的边缘情况,在这些边缘情况中可能出现令人迷惑的代码覆盖率信息。 268 | 269 | .. code-block:: php 270 | :name: code-coverage-analysis.edge-cases.examples.Sample.php 271 | 272 | `` by default 38 | lexers['php'] = PhpLexer(startinline=True) 39 | lexers['php-annotations'] = PhpLexer(startinline=True) 40 | 41 | # If extensions (or modules to document with autodoc) are in another directory, 42 | # add these directories to sys.path here. If the directory is relative to the 43 | # documentation root, use os.path.abspath to make it absolute, like shown here. 44 | #sys.path.insert(0, os.path.abspath('.')) 45 | 46 | # -- General configuration ------------------------------------------------ 47 | 48 | # If your documentation needs a minimal Sphinx version, state it here. 49 | needs_sphinx = '1.3' 50 | 51 | # Add any Sphinx extension module names here, as strings. They can be 52 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 53 | # ones. 54 | extensions = [ 55 | 'sphinx.ext.autodoc', 56 | ] 57 | 58 | # Add any paths that contain templates here, relative to this directory. 59 | templates_path = ['_templates'] 60 | 61 | # The suffix(es) of source filenames. 62 | # You can specify multiple suffix as a list of string: 63 | # source_suffix = ['.rst', '.md'] 64 | source_suffix = '.rst' 65 | 66 | # The encoding of source files. 67 | #source_encoding = 'utf-8-sig' 68 | 69 | # The master toctree document. 70 | master_doc = 'index' 71 | 72 | # General information about the project. 73 | project = u'PHPUnit' 74 | copyright = u'2020, Sebastian Bergmann' 75 | author = u'Sebastian Bergmann' 76 | epub_author = u'Sebastian Bergmann' 77 | 78 | # The version info for the project you're documenting, acts as replacement for 79 | # |version| and |release|, also used in various other places throughout the 80 | # built documents. 81 | # 82 | # The short X.Y version. 83 | version = get_version().strip() 84 | # The full version, including alpha/beta/rc tags. 85 | release = version 86 | 87 | # The language for content autogenerated by Sphinx. Refer to documentation 88 | # for a list of supported languages. 89 | # 90 | # This is also used if you do content translation via gettext catalogs. 91 | # Usually you set "language" from the command line for these cases. 92 | language = None 93 | 94 | # There are two options for replacing |today|: either, you set today to some 95 | # non-false value, then it is used: 96 | #today = '' 97 | # Else, today_fmt is used as the format for a strftime call. 98 | today_fmt = '%Y-%m-%d' 99 | 100 | # List of patterns, relative to source directory, that match files and 101 | # directories to ignore when looking for source files. 102 | exclude_patterns = [] 103 | 104 | # The reST default role (used for this markup: `text`) to use for all 105 | # documents. 106 | #default_role = None 107 | 108 | # If true, '()' will be appended to :func: etc. cross-reference text. 109 | #add_function_parentheses = True 110 | 111 | # If true, the current module name will be prepended to all description 112 | # unit titles (such as .. function::). 113 | #add_module_names = True 114 | 115 | # If true, sectionauthor and moduleauthor directives will be shown in the 116 | # output. They are ignored by default. 117 | #show_authors = False 118 | 119 | # The name of the Pygments (syntax highlighting) style to use. 120 | pygments_style = 'sphinx' 121 | 122 | # A list of ignored prefixes for module index sorting. 123 | #modindex_common_prefix = [] 124 | 125 | # If true, keep warnings as "system message" paragraphs in the built documents. 126 | #keep_warnings = False 127 | 128 | # If true, `todo` and `todoList` produce output, else they produce nothing. 129 | todo_include_todos = False 130 | 131 | 132 | # -- Options for HTML output ---------------------------------------------- 133 | 134 | html_theme = 'sphinx_rtd_theme' 135 | html_theme_options = { 136 | 'collapse_navigation': False, 137 | 'display_version': False 138 | } 139 | 140 | # Add any paths that contain custom themes here, relative to this directory. 141 | #html_theme_path = ['_templates'] 142 | 143 | html_add_permalinks = "" 144 | 145 | # The name for this set of Sphinx documents. If None, it defaults to 146 | # " v documentation". 147 | html_title = u'PHPUnit %s 手册' % get_version() 148 | 149 | # A shorter title for the navigation bar. Default is the same as html_title. 150 | #html_short_title = None 151 | 152 | # The name of an image file (relative to this directory) to place at the top 153 | # of the sidebar. 154 | #html_logo = None 155 | 156 | # The name of an image file (within the static path) to use as favicon of the 157 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 158 | # pixels large. 159 | #html_favicon = None 160 | 161 | # Add any paths that contain custom static files (such as style sheets) here, 162 | # relative to this directory. They are copied after the builtin static files, 163 | # so a file named "default.css" will overwrite the builtin "default.css". 164 | html_static_path = ['_static'] 165 | 166 | # Add any extra paths that contain custom files (such as robots.txt or 167 | # .htaccess) here, relative to this directory. These files are copied 168 | # directly to the root of the documentation. 169 | #html_extra_path = [] 170 | 171 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 172 | # using the given strftime format. 173 | #html_last_updated_fmt = '%b %d, %Y' 174 | 175 | # If true, SmartyPants will be used to convert quotes and dashes to 176 | # typographically correct entities. 177 | #html_use_smartypants = True 178 | 179 | # Custom sidebar templates, maps document names to template names. 180 | #html_sidebars = {} 181 | 182 | # Additional templates that should be rendered to pages, maps page names to 183 | # template names. 184 | #html_additional_pages = {} 185 | 186 | # If false, no module index is generated. 187 | #html_domain_indices = True 188 | 189 | # If false, no index is generated. 190 | #html_use_index = True 191 | 192 | # If true, the index is split into individual pages for each letter. 193 | #html_split_index = False 194 | 195 | # If true, links to the reST sources are added to the pages. 196 | #html_show_sourcelink = True 197 | 198 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 199 | html_show_sphinx = False 200 | 201 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 202 | #html_show_copyright = True 203 | 204 | # If true, an OpenSearch description file will be output, and all pages will 205 | # contain a tag referring to it. The value of this option must be the 206 | # base URL from which the finished HTML is served. 207 | #html_use_opensearch = '' 208 | 209 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 210 | #html_file_suffix = None 211 | 212 | # Language to be used for generating the HTML full-text search index. 213 | # Sphinx supports the following languages: 214 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 215 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 216 | #html_search_language = 'en' 217 | 218 | # A dictionary with options for the search language support, empty by default. 219 | # Now only 'ja' uses this config value 220 | #html_search_options = {'type': 'default'} 221 | 222 | # The name of a javascript file (relative to the configuration directory) that 223 | # implements a search results scorer. If empty, the default will be used. 224 | #html_search_scorer = 'scorer.js' 225 | 226 | # Output file base name for HTML help builder. 227 | htmlhelp_basename = 'PHPUnitdoc' 228 | 229 | html_context = { 230 | "display_github": True, 231 | "github_user": "sebastianbergmann", 232 | "github_repo": "phpunit-documentation-chinese", 233 | "github_version": version, 234 | "conf_py_path": "/src/", 235 | } 236 | 237 | # -- Options for LaTeX output --------------------------------------------- 238 | 239 | latex_elements = { 240 | # The paper size ('letterpaper' or 'a4paper'). 241 | 'papersize': 'a4paper', 242 | 243 | # The font size ('10pt', '11pt' or '12pt'). 244 | #'pointsize': '10pt', 245 | 246 | # Additional stuff for the LaTeX preamble. 247 | #'preamble': '', 248 | 249 | # Latex figure (float) alignment 250 | #'figure_align': 'htbp', 251 | } 252 | 253 | # Grouping the document tree into LaTeX files. List of tuples 254 | # (source start file, target name, title, 255 | # author, documentclass [howto, manual, or own class]). 256 | latex_documents = [ 257 | (master_doc, 'PHPUnit.tex', u'PHPUnit 手册', 258 | u'Sebastian Bergmann', 'manual'), 259 | ] 260 | 261 | # The name of an image file (relative to this directory) to place at the top of 262 | # the title page. 263 | #latex_logo = None 264 | 265 | # For "manual" documents, if this is true, then toplevel headings are parts, 266 | # not chapters. 267 | #latex_use_parts = False 268 | 269 | # If true, show page references after internal links. 270 | #latex_show_pagerefs = False 271 | 272 | # If true, show URL addresses after external links. 273 | #latex_show_urls = False 274 | 275 | # Documents to append as an appendix to all manuals. 276 | #latex_appendices = [] 277 | 278 | # If false, no module index is generated. 279 | #latex_domain_indices = True 280 | 281 | 282 | # -- Options for manual page output --------------------------------------- 283 | 284 | # One entry per manual page. List of tuples 285 | # (source start file, name, description, authors, manual section). 286 | man_pages = [ 287 | (master_doc, 'phpunit', u'PHPUnit 文档', 288 | [author], 1) 289 | ] 290 | 291 | # If true, show URL addresses after external links. 292 | #man_show_urls = False 293 | 294 | 295 | # -- Options for Texinfo output ------------------------------------------- 296 | 297 | # Grouping the document tree into Texinfo files. List of tuples 298 | # (source start file, target name, title, author, 299 | # dir menu entry, description, category) 300 | texinfo_documents = [ 301 | (master_doc, 'PHPUnit', u'PHPUnit 手册', 302 | author, 'PHPUnit', 'One line description of project.', 303 | 'Miscellaneous'), 304 | ] 305 | 306 | # Documents to append as an appendix to all manuals. 307 | #texinfo_appendices = [] 308 | 309 | # If false, no module index is generated. 310 | #texinfo_domain_indices = True 311 | 312 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 313 | #texinfo_show_urls = 'footnote' 314 | 315 | # If true, do not generate a @detailmenu in the "Top" node's menu. 316 | #texinfo_no_detailmenu = False 317 | 318 | numfig = True 319 | 320 | numfig_format = { 321 | 'code-block': u'示例 %s', 322 | 'figure': u'图表 %s', 323 | 'table': u'表格 %s', 324 | 'section': u'章节' 325 | } 326 | 327 | 328 | # -- Options for epub output ------------------------------------------- 329 | 330 | -------------------------------------------------------------------------------- /src/configuration.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. _appendixes.configuration: 4 | 5 | ========================== 6 | XML 配置文件 7 | ========================== 8 | 9 | .. _appendixes.configuration.phpunit: 10 | 11 | ```` 元素 12 | ######################### 13 | 14 | .. _appendixes.configuration.phpunit.backupGlobals: 15 | 16 | ``backupGlobals`` 属性 17 | ------------------------------- 18 | 19 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 20 | 21 | PHPUnit 可选地允许在每个测试之前备份所有全局与超全局变量,并在每个测试结束后还原这些备份。 22 | 23 | 此属性为所有测试配置此操作,可以在测试用例类和测试方法级别用 ``@backupGlobals`` 标注来覆盖此配置。 24 | 25 | .. _appendixes.configuration.phpunit.backupStaticAttributes: 26 | 27 | ``backupStaticAttributes`` 属性 28 | ---------------------------------------- 29 | 30 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 31 | 32 | PHPUnit 可选地允许在每个测试之前备份所有已声明类的静态属性,并在每个测试结束后还原这些备份。 33 | 34 | 此属性为所有测试配置此操作,可以在测试用例类和测试方法级别用 ``@backupStaticAttributes`` 标注来覆盖此配置。 35 | 36 | .. _appendixes.configuration.phpunit.bootstrap: 37 | 38 | ``bootstrap`` 属性 39 | --------------------------- 40 | 41 | 此属性配置在执行测试之前加载的引导脚本。此脚本通常仅注册用于加载被测代码的自动加载器回调函数。 42 | 43 | .. _appendixes.configuration.phpunit.cacheResult: 44 | 45 | ``cacheResult`` 属性 46 | ----------------------------- 47 | 48 | 可能值:``true`` 或 ``false``\ (默认值:\ ``true``) 49 | 50 | 此属性配置测试结果的缓存。该缓存是某些其他功能正常工作所必须的。 51 | 52 | .. _appendixes.configuration.phpunit.cacheResultFile: 53 | 54 | ``cacheResultFile`` 属性 55 | --------------------------------- 56 | 57 | 此属性配置测试结果缓存(见上文)储存在哪个文件中。 58 | 59 | .. _appendixes.configuration.phpunit.colors: 60 | 61 | ``colors`` 属性 62 | ------------------------ 63 | 64 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 65 | 66 | 此属性配置 PHPUnit 的输出是否使用彩色。 67 | 68 | 将此属性设置为 ``true`` 等效于使用 ``--colors=auto`` 命令行选项。 69 | 70 | 将此属性设置为 ``false`` 等效于使用 ``--colors=never`` 命令行选项。 71 | 72 | .. _appendixes.configuration.phpunit.columns: 73 | 74 | ``columns`` 属性 75 | ------------------------- 76 | 77 | 可能值:整数或字符串 ``max``\ (默认值:\ ``80``) 78 | 79 | 此属性配置进度输出所用的列数。 80 | 81 | 如果定义了 ``max`` 作为其值,则列数为当前终端的最大值。 82 | 83 | .. _appendixes.configuration.phpunit.convertDeprecationsToExceptions: 84 | 85 | ``convertDeprecationsToExceptions`` 属性 86 | ------------------------------------------------- 87 | 88 | 可能值:``true`` 或 ``false``\ (默认值:\ ``true``) 89 | 90 | 此属性配置是否将被测代码所触发的 ``E_DEPRECATED`` 和 ``E_USER_DEPRECATED`` 事件转换为异常(并将此测试标记为错误)。 91 | 92 | .. _appendixes.configuration.phpunit.convertErrorsToExceptions: 93 | 94 | ``convertErrorsToExceptions`` 属性 95 | ------------------------------------------- 96 | 97 | 可能值:``true`` 或 ``false``\ (默认值:\ ``true``) 98 | 99 | 此属性配置是否将被测代码所触发的 ``E_ERROR`` 和 ``E_USER_ERROR`` 事件转换为异常(并将此测试标记为错误)。 100 | 101 | .. _appendixes.configuration.phpunit.convertNoticesToExceptions: 102 | 103 | ``convertNoticesToExceptions`` 属性 104 | -------------------------------------------- 105 | 106 | 可能值:``true`` 或 ``false``\ (默认值:\ ``true``) 107 | 108 | 此属性配置是否将被测代码所触发的 ``E_STRICT``、``E_NOTICE`` 和 ``E_USER_NOTICE`` 事件转换为异常(并将此测试标记为错误)。 109 | 110 | .. _appendixes.configuration.phpunit.convertWarningsToExceptions: 111 | 112 | ``convertWarningsToExceptions`` 属性 113 | --------------------------------------------- 114 | 115 | 可能值:``true`` 或 ``false``\ (默认值:\ ``true``) 116 | 117 | 此属性配置是否将被测代码所触发的 ``E_WARNING`` 和 ``E_USER_WARNING`` 事件转换为异常(并将此测试标记为错误)。 118 | 119 | .. _appendixes.configuration.phpunit.forceCoversAnnotation: 120 | 121 | ``forceCoversAnnotation`` 属性 122 | --------------------------------------- 123 | 124 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 125 | 126 | 此属性配置的是在没有 :ref:`@covers ` 标注时,测试是否要标记为有风险(参见\ :ref:`risky-tests.unintentionally-covered-code`)。 127 | 128 | .. _appendixes.configuration.phpunit.printerClass: 129 | 130 | ``printerClass`` 属性 131 | ------------------------------ 132 | 133 | 默认值:``PHPUnit\TextUI\ResultPrinter`` 134 | 135 | 此属性配置的是一个类名,这个类是 ``PHPUnit\TextUI\ResultPrinter`` 或者是扩展自 ``PHPUnit\TextUI\ResultPrinter`` 的类。会使用此类的一个实例对象来打印进度和测试结果。 136 | 137 | .. _appendixes.configuration.phpunit.printerFile: 138 | 139 | ``printerFile`` 属性 140 | ----------------------------- 141 | 142 | 此属性可用于配置 ``printerClass`` 所配置的类的声明所在源代码文件,以防此类无法自动加载的情况。 143 | 144 | .. _appendixes.configuration.phpunit.processIsolation: 145 | 146 | ``processIsolation`` 属性 147 | ---------------------------------- 148 | 149 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 150 | 151 | 此属性配置是否应当对每个测试都用单独的 PHP 进程来运行以提高隔离度。 152 | 153 | .. _appendixes.configuration.phpunit.stopOnError: 154 | 155 | ``stopOnError`` 属性 156 | ----------------------------- 157 | 158 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 159 | 160 | 此属性配置是否应当在第一个以“错误(error)”状态结束的测试之后停止测试套件的执行。 161 | 162 | .. _appendixes.configuration.phpunit.stopOnFailure: 163 | 164 | ``stopOnFailure`` 属性 165 | ------------------------------- 166 | 167 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 168 | 169 | 此属性配置是否应当在第一个以“失败(failure)”状态结束的测试之后停止测试套件的执行。 170 | 171 | .. _appendixes.configuration.phpunit.stopOnIncomplete: 172 | 173 | ``stopOnIncomplete`` 属性 174 | ---------------------------------- 175 | 176 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 177 | 178 | 此属性配置是否应当在第一个以“未完成(incomplete)”状态结束的测试之后停止测试套件的执行。 179 | 180 | .. _appendixes.configuration.phpunit.stopOnRisky: 181 | 182 | ``stopOnRisky`` 属性 183 | ----------------------------- 184 | 185 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 186 | 187 | 此属性配置是否应当在第一个以“有风险(risky)”状态结束的测试之后停止测试套件的执行。 188 | 189 | .. _appendixes.configuration.phpunit.stopOnSkipped: 190 | 191 | ``stopOnSkipped`` 属性 192 | ------------------------------- 193 | 194 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 195 | 196 | 此属性配置是否应当在第一个以“跳过(skipped)”状态结束的测试之后停止测试套件的执行。 197 | 198 | .. _appendixes.configuration.phpunit.stopOnWarning: 199 | 200 | ``stopOnWarning`` 属性 201 | ------------------------------- 202 | 203 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 204 | 205 | 此属性配置是否应当在第一个以“警告(warning)”状态结束的测试之后停止测试套件的执行。 206 | 207 | .. _appendixes.configuration.phpunit.stopOnDefect: 208 | 209 | ``stopOnDefect`` 属性 210 | ------------------------------ 211 | 212 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 213 | 214 | 此属性配置是否应当在第一个以“错误(error)”、“失败(failure)”,“有风险(risky)”或“警告(warning)”状态结束的测试之后停止测试套件的执行。 215 | 216 | .. _appendixes.configuration.phpunit.failOnRisky: 217 | 218 | ``failOnRisky`` 属性 219 | ----------------------------- 220 | 221 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 222 | 223 | 此属性配置的是在所有测试都成功但有部分测试被标记为有风险(risky)时,PHPUnit 测试执行器在退出时是否应当使用指示失败的 sell 退出码。 224 | 225 | .. _appendixes.configuration.phpunit.failOnWarning: 226 | 227 | ``failOnWarning`` 属性 228 | ------------------------------- 229 | 230 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 231 | 232 | 此属性配置的是在所有测试都成功但有部分测试有警告(warning)时,PHPUnit 测试执行器在退出时是否应当使用指示失败的 sell 退出码。 233 | 234 | .. _appendixes.configuration.phpunit.beStrictAboutChangesToGlobalState: 235 | 236 | ``beStrictAboutChangesToGlobalState`` 属性 237 | --------------------------------------------------- 238 | 239 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 240 | 241 | 此属性配置当被测代码(或测试代码)操纵全局状态时,PHPUnit 是否应将测试标记为有风险(risky)。 242 | 243 | .. _appendixes.configuration.phpunit.beStrictAboutOutputDuringTests: 244 | 245 | ``beStrictAboutOutputDuringTests`` 属性 246 | ------------------------------------------------ 247 | 248 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 249 | 250 | 此属性配置当被测代码(或测试代码)打印输出时,PHPUnit 是否应将测试标记为有风险(risky)。 251 | 252 | .. _appendixes.configuration.phpunit.beStrictAboutResourceUsageDuringSmallTests: 253 | 254 | ``beStrictAboutResourceUsageDuringSmallTests`` 属性 255 | ------------------------------------------------------------ 256 | 257 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 258 | 259 | 此属性配置的是当标注为 ``@small`` 的测试调用对 ``resource`` 变量进行操作的 PHP 内建函数或方法时,PHPUnit 是否应将其标记为有风险(risky)。 260 | 261 | .. _appendixes.configuration.phpunit.beStrictAboutTestsThatDoNotTestAnything: 262 | 263 | ``beStrictAboutTestsThatDoNotTestAnything`` 属性 264 | --------------------------------------------------------- 265 | 266 | 可能值:``true`` 或 ``false``\ (默认值:\ ``true``) 267 | 268 | 此属性配置在测试未执行任何断言(预期也算在内)时,PHPUnit 是否应将其标记为有风险(risky)。 269 | 270 | .. _appendixes.configuration.phpunit.beStrictAboutTodoAnnotatedTests: 271 | 272 | ``beStrictAboutTodoAnnotatedTests`` 属性 273 | ------------------------------------------------- 274 | 275 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 276 | 277 | 此属性配置在测试有 ``@todo`` 标注时,PHPUnit 是否应将其标记为有风险。 278 | 279 | .. _appendixes.configuration.phpunit.beStrictAboutCoversAnnotation: 280 | 281 | ``beStrictAboutCoversAnnotation`` 属性 282 | ----------------------------------------------- 283 | 284 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 285 | 286 | 此属性配置在测试执行未使用 ``@covers`` 或 ``@uses`` 指定的代码时,PHPUnit 是否应将其标记为有风险。 287 | 288 | .. _appendixes.configuration.phpunit.enforceTimeLimit: 289 | 290 | ``enforceTimeLimit`` 属性 291 | ---------------------------------- 292 | 293 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 294 | 295 | 此属性配置是否应实施时间限制。 296 | 297 | .. _appendixes.configuration.phpunit.defaultTimeLimit: 298 | 299 | ``defaultTimeLimit`` 属性 300 | ---------------------------------- 301 | 302 | 可能值:整数(默认值:``0``) 303 | 304 | 此属性配置默认时间限制(以秒为单位)。 305 | 306 | .. _appendixes.configuration.phpunit.timeoutForSmallTests: 307 | 308 | ``timeoutForSmallTests`` 属性 309 | -------------------------------------- 310 | 311 | 可能值:整数(默认值:``1``) 312 | 313 | 此属性配置标注为 ``@small`` 的测试的时间限制(以秒为单位)。 314 | 315 | .. _appendixes.configuration.phpunit.timeoutForMediumTests: 316 | 317 | ``timeoutForMediumTests`` 属性 318 | --------------------------------------- 319 | 320 | 可能值:整数(默认值:``10``) 321 | 322 | 此属性配置标注为 ``@medium`` 的测试的时间限制(以秒为单位)。 323 | 324 | .. _appendixes.configuration.phpunit.timeoutForLargeTests: 325 | 326 | ``timeoutForLargeTests`` 属性 327 | -------------------------------------- 328 | 329 | 可能值:整数(默认值:``60``) 330 | 331 | 此属性配置标注为 ``@large`` 的测试的时间限制(以秒为单位)。 332 | 333 | .. _appendixes.configuration.phpunit.testSuiteLoaderClass: 334 | 335 | ``testSuiteLoaderClass`` 属性 336 | -------------------------------------- 337 | 338 | 默认值:``PHPUnit\Runner\StandardTestSuiteLoader`` 339 | 340 | 此属性配置的是一个类名,这个类必须实现 ``PHPUnit\Runner\TestSuiteLoader`` 接口。会用此类的一个实例对象来加载测试套件。 341 | 342 | .. _appendixes.configuration.phpunit.testSuiteLoaderFile: 343 | 344 | ``testSuiteLoaderFile`` 属性 345 | ------------------------------------- 346 | 347 | 此属性可用于配置 ``testSuiteLoaderClass`` 所配置的类的声明所在源代码文件,以防此类无法自动加载的情况。 348 | 349 | .. _appendixes.configuration.phpunit.defaultTestSuite: 350 | 351 | ``defaultTestSuite`` 属性 352 | ---------------------------------- 353 | 354 | 此属性配置默认测试套件名称。 355 | 356 | .. _appendixes.configuration.phpunit.verbose: 357 | 358 | ``verbose`` 属性 359 | ------------------------- 360 | 361 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 362 | 363 | 此属性配置是否应打印更详细的输出。 364 | 365 | .. _appendixes.configuration.phpunit.stderr: 366 | 367 | ``stderr`` 属性 368 | ------------------------ 369 | 370 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 371 | 372 | 此属性配置 PHPUnit 是否应将其输出打印到 ``stderr`` 而不是 ``stdout``。 373 | 374 | .. _appendixes.configuration.phpunit.reverseDefectList: 375 | 376 | ``reverseDefectList`` 属性 377 | ----------------------------------- 378 | 379 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 380 | 381 | 此属性配置是否应以逆序打印不成功的测试。 382 | 383 | .. _appendixes.configuration.phpunit.registerMockObjectsFromTestArgumentsRecursively: 384 | 385 | ``registerMockObjectsFromTestArgumentsRecursively`` 属性 386 | ----------------------------------------------------------------- 387 | 388 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 389 | 390 | 此属性配置是否应对用 ``@depends`` 批注从一个测试传递到另一测试的数组和对象图进行递归扫描以查找仿件对象。 391 | 392 | .. _appendixes.configuration.phpunit.extensionsDirectory: 393 | 394 | ``extensionsDirectory`` 属性 395 | ------------------------------------- 396 | 397 | 当使用 ``phpunit.phar`` 时,此属性可用于配置一个目录,这个目录中的所有 * .phar 文件都会被加载作为PHPUnit 测试执行器的扩展。 398 | 399 | .. _appendixes.configuration.phpunit.executionOrder: 400 | 401 | ``executionOrder`` 属性 402 | -------------------------------- 403 | 404 | 可能值:``default``、``defects``、``depends``、``no-depends``、``duration``、``random``、``reverse``、``size`` 405 | 406 | 可以使用多个值。这些值之间需要用 ``,`` 分割。 407 | 408 | 此属性配置测试的执行顺序。 409 | 410 | .. _appendixes.configuration.phpunit.resolveDependencies: 411 | 412 | ``resolveDependencies`` 属性 413 | ------------------------------------- 414 | 415 | 可能值:``true`` 或 ``false``\ (默认值:\ ``true``) 416 | 417 | 此属性配置是否应解决测试之间(用 ``@depends`` 注解表示)的依赖关系。 418 | 419 | .. _appendixes.configuration.phpunit.testdox: 420 | 421 | ``testdox`` 属性 422 | ------------------------- 423 | 424 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 425 | 426 | 此属性配置是否以 TestDox 格式打印输出。 427 | 428 | ``noInteraction`` 属性 429 | ------------------------------- 430 | 431 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 432 | 433 | 此属性配置在使用 TestDox 格式时,比如说,进度是否应当是动画。 434 | 435 | .. _appendixes.configuration.testsuites: 436 | 437 | ```` 元素 438 | ############################ 439 | 440 | 父元素:```` 441 | 442 | 此元素是一个或多个 ```` 元素的根元素,用于配置需要执行的测试。 443 | 444 | .. _appendixes.configuration.testsuites.testsuite: 445 | 446 | ```` 元素 447 | --------------------------- 448 | 449 | 父元素:```` 450 | 451 | ```` 元素必须拥有 ``name`` 属性,可以有一个或多个 ```` 及 ```` 子元素,分别代表需要搜索测试的目录及元素。 452 | 453 | .. code-block:: xml 454 | 455 | 456 | 457 | tests/unit 458 | 459 | 460 | 461 | tests/integration 462 | 463 | 464 | 465 | tests/edge-to-edge 466 | 467 | 468 | 469 | 可以用 ``phpVersion`` 和 ``phpVersionOperator`` 属性来指定 PHP 版本需求: 470 | 471 | .. code-block:: xml 472 | 473 | 474 | 475 | tests/unit 476 | 477 | 478 | 479 | 在上面的示例中,仅当 PHP 版本至少为 8.0.0 时,才会将 ``tests/unit`` 目录中的测试添加到测试套件中。``phpVersionOperator`` 属性是可选的,默认为 ``>=``。 480 | 481 | .. _appendixes.configuration.coverage: 482 | 483 | ```` 元素 484 | ########################## 485 | 486 | 父元素:```` 487 | 488 | ```` 元素及其子元素可用于配置代码覆盖率: 489 | 490 | .. code-block:: xml 491 | 492 | 498 | 499 | 500 | 501 | ``cacheDirectory`` 属性 502 | -------------------------------- 503 | 504 | 可能值:字符串 505 | 506 | 当收集并处理代码覆盖率数据时,将执行静态代码分析以改善有关覆盖代码的推理。这是一项昂贵的操作,而其结果可以缓存。设置 ``cacheDirectory`` 属性后,静态分析结果将缓存在指定目录中。 507 | 508 | ``includeUncoveredFiles`` 属性 509 | --------------------------------------- 510 | 511 | 可能值:``true`` 或 ``false``\ (默认值:\ ``true``) 512 | 513 | 当设置为 ``true`` 时,所有配置为代码覆盖率分析需要考虑的源代码文件都将包含在代码覆盖率报告中。这包括测试运行时并未执行的源代码文件。 514 | 515 | ``processUncoveredFiles`` 属性 516 | --------------------------------------- 517 | 518 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 519 | 520 | 当设置为 ``true`` 时,所有配置为代码覆盖率分析需要考虑的源代码文件都将被处理。这包括测试运行时并未执行的源代码文件。 521 | 522 | ``ignoreDeprecatedCodeUnits`` 属性 523 | ------------------------------------------- 524 | 525 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 526 | 527 | 此属性配置代码覆盖率是否应忽略标注为 ``@deprecated`` 的代码单元。 528 | 529 | ``pathCoverage`` 属性 530 | ------------------------------ 531 | 532 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 533 | 534 | 设置为 ``false`` 时,将仅收集、处理和报告行覆盖率数据。 535 | 536 | 设置为 ``true`` 时,将收集、处理和报告行覆盖率、分支覆盖率和路径覆盖率数据。这需要支持路径覆盖率的代码覆盖率驱动程序。目前只有 Xdebug 实现了路径覆盖率。 537 | 538 | ``disableCodeCoverageIgnore`` 属性 539 | ------------------------------------------- 540 | 541 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 542 | 543 | 此属性配置是否应忽略 ``@codeCoverageIgnore*`` 批注。 544 | 545 | .. _appendixes.configuration.coverage.include: 546 | 547 | The ```` Element 548 | ------------------------- 549 | 550 | 父元素:```` 551 | 552 | 配置要包含在代码覆盖率报告中的文件集合。 553 | 554 | .. code-block:: xml 555 | 556 | 557 | src 558 | 559 | 560 | 上面示例指示 PHPUnit 在代码覆盖率报告中要包含在 ``src`` 目录及其子目录中的所有带 ``.php`` 后缀的源代码文件。 561 | 562 | 563 | .. _appendixes.configuration.coverage.exclude: 564 | 565 | ```` 元素 566 | ------------------------- 567 | 568 | 父元素:```` 569 | 570 | 配置要从代码覆盖率报告中排除的文件集合。 571 | 572 | .. code-block:: xml 573 | 574 | 575 | src 576 | 577 | 578 | 579 | src/generated 580 | src/autoload.php 581 | 582 | 583 | 上面示例指示 PHPUnit 在代码覆盖率报告中要包含在 ``src`` 目录及其子目录中的所有带 ``.php`` 后缀的源代码文件,但要排除 ``src/generated`` 目录及其子目录中的所有带 ``.php`` 后缀的文件以及 ``src/autoload.php`` 文件。 584 | 585 | 586 | .. _appendixes.configuration.coverage.directory: 587 | 588 | ```` 元素 589 | --------------------------- 590 | 591 | 父元素:````、```` 592 | 593 | 配置要包含在代码覆盖率报告中或从代码覆盖率报告中排除的目录及其子目录。 594 | 595 | ``prefix`` 属性 596 | ************************ 597 | 598 | 可能值:字符串 599 | 600 | 配置基于前缀的过滤器,该过滤器将应用于目录及其子目录中的文件名。 601 | 602 | ``suffix`` 属性 603 | ************************ 604 | 605 | 可能值:string(默认值:``'.php'``) 606 | 607 | 配置基于后缀的过滤器,该过滤器将应用于目录及其子目录中的文件名。 608 | 609 | ``phpVersion`` 属性 610 | **************************** 611 | 612 | 可能值:字符串 613 | 614 | 配置基于用来运行当前 PHPUnit 进程的 PHP 运行时版本的过滤器。 615 | 616 | ``phpVersionOperator`` 属性 617 | ************************************ 618 | 619 | 可能值:``'<'``、``'lt'``、``'<='``、``'le'``、``'>'``、``'gt'``、``'>='``、``'ge'``、``'=='``、``'='``、``'eq'``, ``'!='``, ``'<>'``, ``'ne'``\ (默认值:``'>='``) 620 | 621 | 配置基于用来运行当前 PHPUnit 进程的 PHP 运行时版本的过滤器的 ``version_compare()`` 操作所用的比较运算符。 622 | 623 | 624 | .. _appendixes.configuration.coverage.file: 625 | 626 | ```` 元素 627 | ---------------------- 628 | 629 | 父元素:````、```` 630 | 631 | 配置要包含在代码覆盖率报告中或从代码覆盖率报告中排除的文件。 632 | 633 | 634 | .. _appendixes.configuration.coverage.report: 635 | 636 | ```` 元素 637 | ------------------------ 638 | 639 | 父元素:```` 640 | 641 | 配置要生成的代码覆盖率报告。 642 | 643 | .. code-block:: xml 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | .. _appendixes.configuration.coverage.report.clover: 656 | 657 | ```` 元素 658 | ************************ 659 | 660 | 父元素:```` 661 | 662 | 配置 Clover XML 格式的代码覆盖率报告。 663 | 664 | ``outputFile`` 属性 665 | ++++++++++++++++++++++++++++ 666 | 667 | 可能值:字符串 668 | 669 | Clover XML 报告写入的文件。 670 | 671 | 672 | .. _appendixes.configuration.coverage.report.crap4j: 673 | 674 | ```` 元素 675 | ************************ 676 | 677 | 父元素:```` 678 | 679 | 配置 Crap4J XML 格式的代码覆盖率报告。 680 | 681 | ``outputFile`` 属性 682 | ++++++++++++++++++++++++++++ 683 | 684 | 可能值:字符串 685 | 686 | Crap4J XML 报告写入的文件。 687 | 688 | ``threshold`` 属性 689 | +++++++++++++++++++++++++++ 690 | 691 | 可能值:integer(默认值:``50``) 692 | 693 | 694 | .. _appendixes.configuration.coverage.report.html: 695 | 696 | ```` 元素 697 | ********************** 698 | 699 | 父元素:```` 700 | 701 | 配置 HTML 格式的代码覆盖率报告。 702 | 703 | ``outputDirectory`` 属性 704 | +++++++++++++++++++++++++++++++++ 705 | 706 | HTML 报告写入的目录。 707 | 708 | ``lowUpperBound`` 属性 709 | +++++++++++++++++++++++++++++++ 710 | 711 | 可能值:integer(默认值:``50``) 712 | 713 | 应当被视为“低覆盖率”的上限。 714 | 715 | ``highLowerBound`` 属性 716 | ++++++++++++++++++++++++++++++++ 717 | 718 | 可能值:整数(默认值:``90``) 719 | 720 | 应当被视为“高覆盖率”的下限。 721 | 722 | 723 | .. _appendixes.configuration.coverage.report.php: 724 | 725 | ```` 元素 726 | ********************* 727 | 728 | 父元素:```` 729 | 730 | 配置 PHP 格式的代码覆盖率报告。 731 | 732 | ``outputFile`` 属性 733 | ++++++++++++++++++++++++++++ 734 | 735 | 可能值:字符串 736 | 737 | PHP 报告写入的文件。 738 | 739 | 740 | .. _appendixes.configuration.coverage.report.text: 741 | 742 | ```` 元素 743 | ********************** 744 | 745 | 父元素:```` 746 | 747 | 配置文本格式的代码覆盖率报告。 748 | 749 | ``outputFile`` 属性 750 | ++++++++++++++++++++++++++++ 751 | 752 | 可能值:字符串 753 | 754 | 文本报告写入的文件。 755 | 756 | ``showUncoveredFiles`` 属性 757 | ++++++++++++++++++++++++++++++++++++ 758 | 759 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 760 | 761 | ``showOnlySummary`` 属性 762 | +++++++++++++++++++++++++++++++++ 763 | 764 | 可能值:``true`` 或 ``false``\ (默认值:\ ``false``) 765 | 766 | 767 | .. _appendixes.configuration.coverage.report.xml: 768 | 769 | ```` 元素 770 | ********************* 771 | 772 | 父元素:```` 773 | 774 | 配置 PHPUnit XML 格式的代码覆盖率报告。 775 | 776 | ``outputDirectory`` 属性 777 | +++++++++++++++++++++++++++++++++ 778 | 779 | 可能值:字符串 780 | 781 | PHPUnit XML 报告写入的目录。 782 | 783 | 784 | .. _appendixes.configuration.logging: 785 | 786 | ```` 元素 787 | ######################### 788 | 789 | 父元素:```` 790 | 791 | ```` 元素及其子元素可用于配置测试执行期间的日志记录。 792 | 793 | .. code-block:: xml 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | .. _appendixes.configuration.logging.junit: 806 | 807 | ```` 元素 808 | ----------------------- 809 | 810 | 父元素:```` 811 | 812 | 配置 JUnit XML 格式的测试结果日志文件。 813 | 814 | ``outputFile`` 属性 815 | **************************** 816 | 817 | 可能值:字符串 818 | 819 | JUnit XML 格式的测试结果日志写入的文件。 820 | 821 | 822 | .. _appendixes.configuration.logging.teamcity: 823 | 824 | ```` 元素 825 | -------------------------- 826 | 827 | 父元素:```` 828 | 829 | 配置 TeamCity 格式的测试结果日志文件。 830 | 831 | ``outputFile`` 属性 832 | **************************** 833 | 834 | 可能值:字符串 835 | 836 | TeamCity 格式的测试结果日志写入的文件。 837 | 838 | 839 | .. _appendixes.configuration.logging.testdoxHtml: 840 | 841 | ```` 元素 842 | ----------------------------- 843 | 844 | 父元素:```` 845 | 846 | 配置 TestDox HTML 格式的测试结果日志文件。 847 | 848 | ``outputFile`` 属性 849 | **************************** 850 | 851 | 可能值:字符串 852 | 853 | TestDox HTML 格式的测试结果日志写入的文件。 854 | 855 | 856 | .. _appendixes.configuration.logging.testdoxText: 857 | 858 | ```` 元素 859 | ----------------------------- 860 | 861 | 父元素:```` 862 | 863 | 配置 TestDox 文本格式的测试结果日志文件。 864 | 865 | ``outputFile`` 属性 866 | **************************** 867 | 868 | 可能值:字符串 869 | 870 | TestDox 文本格式的测试结果日志写入的文件。 871 | 872 | 873 | .. _appendixes.configuration.logging.testdoxXml: 874 | 875 | ```` 元素 876 | ---------------------------- 877 | 878 | 父元素:```` 879 | 880 | 配置 TestDox XML 格式的测试结果日志文件。 881 | 882 | ``outputFile`` 属性 883 | **************************** 884 | 885 | 可能值:字符串 886 | 887 | TestDox XML 格式的测试结果日志写入的文件。 888 | 889 | 890 | .. _appendixes.configuration.logging.text: 891 | 892 | ```` 元素 893 | ---------------------- 894 | 895 | 父元素:```` 896 | 897 | 配置文本格式的测试结果日志文件。 898 | 899 | ``outputFile`` 属性 900 | **************************** 901 | 902 | 可能值:字符串 903 | 904 | 文本格式的测试结果日志写入的文件。 905 | 906 | 907 | .. _appendixes.configuration.groups: 908 | 909 | ```` 元素 910 | ######################## 911 | 912 | 父元素:```` 913 | 914 | ```` 元素及其 ````、````、```` 子元素用于从带有 ``@group`` 标注(相关文档参见 :ref:`appendixes.annotations.group`)的测试中选择需要运行(或不运行)的分组。 915 | 916 | .. code-block:: xml 917 | 918 | 919 | 920 | name 921 | 922 | 923 | name 924 | 925 | 926 | 927 | 上面的示例等效于以 ``--group name --exclude-group name`` 调用 PHPUnit 测试执行器。 928 | 929 | .. _appendixes.configuration.testdoxGroups: 930 | 931 | ```` 元素 932 | ############################### 933 | 934 | 父元素:```` 935 | 936 | ... <待完成> ... 937 | 938 | .. _appendixes.configuration.listeners: 939 | 940 | ```` 元素 941 | ########################### 942 | 943 | 父元素:```` 944 | 945 | ```` 元素及其 ```` 子元素可用于在测试执行期间附加额外的测试监听器。 946 | 947 | .. _appendixes.configuration.listeners.listener: 948 | 949 | ```` 元素 950 | -------------------------- 951 | 952 | 父元素:```` 953 | 954 | .. code-block:: xml 955 | 956 | 957 | 958 | 959 | 960 | 961 | Sebastian 962 | 963 | 964 | 22 965 | April 966 | 19.78 967 | 968 | 969 | 970 | 971 | 972 | 973 | 以上 XML 配置对应于将 ``$listener`` 对象(见下文)附到测试执行过程上。 974 | 975 | .. code-block:: php 976 | 977 | $listener = new MyListener( 978 | ['Sebastian'], 979 | 22, 980 | 'April', 981 | 19.78, 982 | null, 983 | new stdClass 984 | ); 985 | 986 | .. admonition:: 注 987 | 988 | 请注意,``PHPUnit\Framework\TestListener`` 接口已废弃,将在以后删除。应该改用测试执行器扩展来替代测试监听器。 989 | 990 | .. _appendixes.configuration.extensions: 991 | 992 | ```` 元素 993 | ############################ 994 | 995 | 父元素:```` 996 | 997 | ```` 元素及其 ```` 子元素可用于注册测试执行器扩展。 998 | 999 | .. _appendixes.configuration.extensions.extension: 1000 | 1001 | ```` 元素 1002 | --------------------------- 1003 | 1004 | 父元素:```` 1005 | 1006 | .. code-block:: xml 1007 | 1008 | 1009 | 1010 | 1011 | 1012 | .. _appendixes.configuration.extensions.extension.arguments: 1013 | 1014 | ```` 元素 1015 | *************************** 1016 | 1017 | 父元素:```` 1018 | 1019 | ```` 元素可用于配置单个 ````。 1020 | 1021 | 接受类型的元素的列表,该列表用于配置各个扩展。参数会按照在配置中定义的顺序被传递给扩展类的 ``__constructor`` 方法。 1022 | 1023 | 可用类型: 1024 | 1025 | - ```` 1026 | - ```` 1027 | - ```` 1028 | - ````\ (浮点数) 1029 | - ```` 1030 | - ```` 1031 | 1032 | .. code-block:: xml 1033 | 1034 | 1035 | 1036 | 1 1037 | 2 1038 | 3 1039 | hello world 1040 | true 1041 | 1.23 1042 | 1043 | 1044 | value1 1045 | 1046 | 1047 | value2 1048 | 1049 | 1050 | 1051 | constructor arg 1 1052 | constructor arg 2 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | .. _appendixes.configuration.php: 1059 | 1060 | ```` 元素 1061 | ##################### 1062 | 1063 | 父元素:```` 1064 | 1065 | ```` 元素及其子元素用于配置 PHP 设置、常量以及全局变量。同时也可用于向 ``include_path`` 前面添加内容。 1066 | 1067 | .. _appendixes.configuration.php.includePath: 1068 | 1069 | ```` 元素 1070 | ----------------------------- 1071 | 1072 | 父元素:```` 1073 | 1074 | 此元素可用于向 ``include_path`` 前面添加一个路径。 1075 | 1076 | .. _appendixes.configuration.php.ini: 1077 | 1078 | ```` 元素 1079 | --------------------- 1080 | 1081 | 父元素:```` 1082 | 1083 | 此元素可用于设置 PHP 配置。 1084 | 1085 | .. code-block:: xml 1086 | 1087 | 1088 | 1089 | 1090 | 1091 | 以上 XML 配置对应于如下 PHP 代码: 1092 | 1093 | .. code-block:: php 1094 | 1095 | ini_set('foo', 'bar'); 1096 | 1097 | .. _appendixes.configuration.php.const: 1098 | 1099 | ```` 元素 1100 | ----------------------- 1101 | 1102 | 父元素:```` 1103 | 1104 | 此元素可用于设置全局常数。 1105 | 1106 | .. code-block:: xml 1107 | 1108 | 1109 | 1110 | 1111 | 1112 | 以上 XML 配置对应于如下 PHP 代码: 1113 | 1114 | .. code-block:: php 1115 | 1116 | define('foo', 'bar'); 1117 | 1118 | .. _appendixes.configuration.php.var: 1119 | 1120 | ``` 元素 1121 | --------------------- 1122 | 1123 | 父元素:```` 1124 | 1125 | 此元素可用于设置全局变量。 1126 | 1127 | .. code-block:: xml 1128 | 1129 | 1130 | 1131 | 1132 | 1133 | 以上 XML 配置对应于如下 PHP 代码: 1134 | 1135 | .. code-block:: php 1136 | 1137 | $GLOBALS['foo'] = 'bar'; 1138 | 1139 | .. _appendixes.configuration.php.env: 1140 | 1141 | ```` 元素 1142 | --------------------- 1143 | 1144 | 父元素:```` 1145 | 1146 | 此元素可用于在超全局数组 ``$_ENV`` 中设置一个值。 1147 | 1148 | .. code-block:: xml 1149 | 1150 | 1151 | 1152 | 1153 | 1154 | 以上 XML 配置对应于如下 PHP 代码: 1155 | 1156 | .. code-block:: php 1157 | 1158 | $_ENV['foo'] = 'bar'; 1159 | 1160 | 默认情况下,如果环境变量已经存在,则不会覆盖之。要强制覆盖已存在的变量,用 ``force`` 属性: 1161 | 1162 | .. code-block:: xml 1163 | 1164 | 1165 | 1166 | 1167 | 1168 | .. _appendixes.configuration.php.get: 1169 | 1170 | ```` 元素 1171 | --------------------- 1172 | 1173 | 父元素:```` 1174 | 1175 | 此元素可用于在超全局数组 ``$_GET`` 中设置一个值。 1176 | 1177 | .. code-block:: xml 1178 | 1179 | 1180 | 1181 | 1182 | 1183 | 以上 XML 配置对应于如下 PHP 代码: 1184 | 1185 | .. code-block:: php 1186 | 1187 | $_GET['foo'] = 'bar'; 1188 | 1189 | .. _appendixes.configuration.php.post: 1190 | 1191 | ```` 元素 1192 | ---------------------- 1193 | 1194 | 父元素:```` 1195 | 1196 | 此元素可用于在超全局数组 ``$_POST`` 中设置一个值。 1197 | 1198 | .. code-block:: xml 1199 | 1200 | 1201 | 1202 | 1203 | 1204 | 以上 XML 配置对应于如下 PHP 代码: 1205 | 1206 | .. code-block:: php 1207 | 1208 | $_POST['foo'] = 'bar'; 1209 | 1210 | .. _appendixes.configuration.php.cookie: 1211 | 1212 | ```` 元素 1213 | ------------------------ 1214 | 1215 | 父元素:```` 1216 | 1217 | 此元素可用于在超全局数组 ``$_COOKIE`` 中设置一个值。 1218 | 1219 | .. code-block:: xml 1220 | 1221 | 1222 | 1223 | 1224 | 1225 | 以上 XML 配置对应于如下 PHP 代码: 1226 | 1227 | .. code-block:: php 1228 | 1229 | $_COOKIE['foo'] = 'bar'; 1230 | 1231 | .. _appendixes.configuration.php.server: 1232 | 1233 | ```` 元素 1234 | ------------------------ 1235 | 1236 | 父元素:```` 1237 | 1238 | 此元素可用于在超全局数组 ``$_SERVER`` 中设置一个值。 1239 | 1240 | .. code-block:: xml 1241 | 1242 | 1243 | 1244 | 1245 | 1246 | 以上 XML 配置对应于如下 PHP 代码: 1247 | 1248 | .. code-block:: php 1249 | 1250 | $_SERVER['foo'] = 'bar'; 1251 | 1252 | .. _appendixes.configuration.php.files: 1253 | 1254 | ```` 元素 1255 | ----------------------- 1256 | 1257 | 父元素:```` 1258 | 1259 | 此元素可用于在超全局数组 ``$_FILES`` 中设置一个值。 1260 | 1261 | .. code-block:: xml 1262 | 1263 | 1264 | 1265 | 1266 | 1267 | 以上 XML 配置对应于如下 PHP 代码: 1268 | 1269 | .. code-block:: php 1270 | 1271 | $_FILES['foo'] = 'bar'; 1272 | 1273 | .. _appendixes.configuration.php.request: 1274 | 1275 | ```` 元素 1276 | ------------------------- 1277 | 1278 | 父元素:```` 1279 | 1280 | 此元素可用于在超全局数组 ``$_REQUEST`` 中设置一个值。 1281 | 1282 | .. code-block:: xml 1283 | 1284 | 1285 | 1286 | 1287 | 1288 | 以上 XML 配置对应于如下 PHP 代码: 1289 | 1290 | .. code-block:: php 1291 | 1292 | $_REQUEST['foo'] = 'bar'; 1293 | 1294 | -------------------------------------------------------------------------------- /src/copyright.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. _appendixes.copyright: 4 | 5 | ========= 6 | 版权 7 | ========= 8 | 9 | .. code-block:: text 10 | 11 | 版权所有 © 2005-2020 Sebastian Bergmann。 12 | 13 | 本作品依据 Creative Commons Attribution 3.0 Unported 许可协议进行授权。 14 | 15 | 以下为许可摘要,完整的法律文本附在后面。 16 | 17 | -------------------------------------------------------------------- 18 | 19 | 您可以自由地: 20 | 21 | * 共享——复制、分发、传输本作品 22 | * 演绎——改编本作品 23 | 24 | 只要遵循以下条件: 25 | 26 | 署名。您必须按作者或许可人所指定的方式为作品署名(但不得以任何方式暗示他们认可您或者您对作品的使用)。 27 | 28 | * 对于任何重用或分发,你必须向他人说明本作品的许可条款。最好的方式是带上一个指向本页面的链接。 29 | 30 | * 只要取得版权所有者的许可,则以上条件均可免除。 31 | 32 | * 本许可证中的任何内容均不损害或限制作者的道德权利。 33 | 34 | 你的公平交易及其他权利绝不会受以上条款影响。 35 | 36 | 以上是对以下(完整的许可证)法规条文做出的易于理解的概括。 37 | 38 | ==================================================================== 39 | 40 | Creative Commons Legal Code 41 | Attribution 3.0 Unported 42 | 43 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 44 | LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN 45 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 46 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO 47 | WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS 48 | LIABILITY FOR DAMAGES RESULTING FROM ITS USE. 49 | 50 | License 51 | 52 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS 53 | CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS 54 | PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE 55 | WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW 56 | IS PROHIBITED. 57 | 58 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND 59 | AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS 60 | LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU 61 | THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF 62 | SUCH TERMS AND CONDITIONS. 63 | 64 | 1. Definitions 65 | 66 | a. "Adaptation" means a work based upon the Work, or upon the 67 | Work and other pre-existing works, such as a translation, 68 | adaptation, derivative work, arrangement of music or other 69 | alterations of a literary or artistic work, or phonogram or 70 | performance and includes cinematographic adaptations or any 71 | other form in which the Work may be recast, transformed, or 72 | adapted including in any form recognizably derived from the 73 | original, except that a work that constitutes a Collection 74 | will not be considered an Adaptation for the purpose of this 75 | License. For the avoidance of doubt, where the Work is a 76 | musical work, performance or phonogram, the synchronization of 77 | the Work in timed-relation with a moving image ("synching") 78 | will be considered an Adaptation for the purpose of this 79 | License. 80 | 81 | b. "Collection" means a collection of literary or artistic works, 82 | such as encyclopedias and anthologies, or performances, 83 | phonograms or broadcasts, or other works or subject matter 84 | other than works listed in Section 1(f) below, which, by 85 | reason of the selection and arrangement of their contents, 86 | constitute intellectual creations, in which the Work is 87 | included in its entirety in unmodified form along with one or 88 | more other contributions, each constituting separate and 89 | independent works in themselves, which together are assembled 90 | into a collective whole. A work that constitutes a Collection 91 | will not be considered an Adaptation (as defined above) for 92 | the purposes of this License. 93 | 94 | c. "Distribute" means to make available to the public the 95 | original and copies of the Work or Adaptation, as appropriate, 96 | through sale or other transfer of ownership. 97 | 98 | d. "Licensor" means the individual, individuals, entity or 99 | entities that offer(s) the Work under the terms of this License. 100 | 101 | e. "Original Author" means, in the case of a literary or artistic 102 | work, the individual, individuals, entity or entities who 103 | created the Work or if no individual or entity can be 104 | identified, the publisher; and in addition (i) in the case of 105 | a performance the actors, singers, musicians, dancers, and 106 | other persons who act, sing, deliver, declaim, play in, 107 | interpret or otherwise perform literary or artistic works or 108 | expressions of folklore; (ii) in the case of a phonogram the 109 | producer being the person or legal entity who first fixes the 110 | sounds of a performance or other sounds; and, (iii) in the 111 | case of broadcasts, the organization that transmits the 112 | broadcast. 113 | 114 | f. "Work" means the literary and/or artistic work offered under 115 | the terms of this License including without limitation any 116 | production in the literary, scientific and artistic domain, 117 | whatever may be the mode or form of its expression including 118 | digital form, such as a book, pamphlet and other writing; a 119 | lecture, address, sermon or other work of the same nature; a 120 | dramatic or dramatico-musical work; a choreographic work or 121 | entertainment in dumb show; a musical composition with or 122 | without words; a cinematographic work to which are assimilated 123 | works expressed by a process analogous to cinematography; a 124 | work of drawing, painting, architecture, sculpture, engraving 125 | or lithography; a photographic work to which are assimilated 126 | works expressed by a process analogous to photography; a work 127 | of applied art; an illustration, map, plan, sketch or three- 128 | dimensional work relative to geography, topography, 129 | architecture or science; a performance; a broadcast; a 130 | phonogram; a compilation of data to the extent it is protected 131 | as a copyrightable work; or a work performed by a variety or 132 | circus performer to the extent it is not otherwise considered 133 | a literary or artistic work. 134 | 135 | g. "You" means an individual or entity exercising rights under 136 | this License who has not previously violated the terms of 137 | this License with respect to the Work, or who has received 138 | express permission from the Licensor to exercise rights under 139 | this License despite a previous violation. 140 | 141 | h. "Publicly Perform" means to perform public recitations of the 142 | Work and to communicate to the public those public 143 | recitations, by any means or process, including by wire or 144 | wireless means or public digital performances; to make 145 | available to the public Works in such a way that members of 146 | the public may access these Works from a place and at a place 147 | individually chosen by them; to perform the Work to the public 148 | by any means or process and the communication to the public of 149 | the performances of the Work, including by public digital 150 | performance; to broadcast and rebroadcast the Work by any 151 | means including signs, sounds or images. 152 | 153 | i. "Reproduce" means to make copies of the Work by any means 154 | including without limitation by sound or visual recordings and 155 | the right of fixation and reproducing fixations of the Work, 156 | including storage of a protected performance or phonogram in 157 | digital form or other electronic medium. 158 | 159 | 2. Fair Dealing Rights. Nothing in this License is intended to 160 | reduce, limit, or restrict any uses free from copyright or rights 161 | arising from limitations or exceptions that are provided for in 162 | connection with the copyright protection under copyright law or 163 | other applicable laws. 164 | 165 | 3. License Grant. Subject to the terms and conditions of this 166 | License, Licensor hereby grants You a worldwide, royalty-free, 167 | non-exclusive, perpetual (for the duration of the applicable 168 | copyright) license to exercise the rights in the Work as stated 169 | below: 170 | 171 | a. to Reproduce the Work, to incorporate the Work into one or 172 | more Collections, and to Reproduce the Work as incorporated 173 | in the Collections; 174 | 175 | b. to create and Reproduce Adaptations provided that any such 176 | Adaptation, including any translation in any medium, takes 177 | reasonable steps to clearly label, demarcate or otherwise 178 | identify that changes were made to the original Work. For 179 | example, a translation could be marked "The original work was 180 | translated from English to Spanish," or a modification could 181 | indicate "The original work has been modified."; 182 | 183 | c. to Distribute and Publicly Perform the Work including as 184 | incorporated in Collections; and, 185 | 186 | d. to Distribute and Publicly Perform Adaptations. 187 | 188 | e. For the avoidance of doubt: 189 | 190 | i. Non-waivable Compulsory License Schemes. In those 191 | jurisdictions in which the right to collect royalties 192 | through any statutory or compulsory licensing scheme cannot 193 | be waived, the Licensor reserves the exclusive right to 194 | collect such royalties for any exercise by You of the 195 | rights granted under this License; 196 | 197 | ii. Waivable Compulsory License Schemes. In those 198 | jurisdictions in which the right to collect royalties 199 | through any statutory or compulsory licensing scheme can 200 | be waived, the Licensor waives the exclusive right to 201 | collect such royalties for any exercise by You of the 202 | rights granted under this License; and, 203 | 204 | iii. Voluntary License Schemes. The Licensor waives the right 205 | to collect royalties, whether individually or, in the 206 | event that the Licensor is a member of a collecting 207 | society that administers voluntary licensing schemes, via 208 | that society, from any exercise by You of the rights 209 | granted under this License. 210 | 211 | The above rights may be exercised in all media and formats whether 212 | now known or hereafter devised. The above rights include the right 213 | to make such modifications as are technically necessary to exercise 214 | the rights in other media and formats. Subject to Section 8(f), all 215 | rights not expressly granted by Licensor are hereby reserved. 216 | 217 | 4. Restrictions. The license granted in Section 3 above is expressly 218 | made subject to and limited by the following restrictions: 219 | 220 | a. You may Distribute or Publicly Perform the Work only under the 221 | terms of this License. You must include a copy of, or the 222 | Uniform Resource Identifier (URI) for, this License with every 223 | copy of the Work You Distribute or Publicly Perform. You may 224 | not offer or impose any terms on the Work that restrict the 225 | terms of this License or the ability of the recipient of the 226 | Work to exercise the rights granted to that recipient under 227 | the terms of the License. You may not sublicense the Work. You 228 | must keep intact all notices that refer to this License and to 229 | the disclaimer of warranties with every copy of the Work You 230 | Distribute or Publicly Perform. When You Distribute or 231 | Publicly Perform the Work, You may not impose any effective 232 | technological measures on the Work that restrict the ability 233 | of a recipient of the Work from You to exercise the rights 234 | granted to that recipient under the terms of the License. This 235 | Section 4(a) applies to the Work as incorporated in a 236 | Collection, but this does not require the Collection apart 237 | from the Work itself to be made subject to the terms of this 238 | License. If You create a Collection, upon notice from any 239 | Licensor You must, to the extent practicable, remove from the 240 | Collection any credit as required by Section 4(b), as 241 | requested. If You create an Adaptation, upon notice from any 242 | Licensor You must, to the extent practicable, remove from the 243 | Adaptation any credit as required by Section 4(b), as requested. 244 | 245 | b. If You Distribute, or Publicly Perform the Work or any 246 | Adaptations or Collections, You must, unless a request has 247 | been made pursuant to Section 4(a), keep intact all copyright 248 | notices for the Work and provide, reasonable to the medium or 249 | means You are utilizing: (i) the name of the Original Author 250 | (or pseudonym, if applicable) if supplied, and/or if the 251 | Original Author and/or Licensor designate another party or 252 | parties (e.g., a sponsor institute, publishing entity, 253 | journal) for attribution ("Attribution Parties") in Licensor's 254 | copyright notice, terms of service or by other reasonable 255 | means, the name of such party or parties; (ii) the title of 256 | the Work if supplied; (iii) to the extent reasonably 257 | practicable, the URI, if any, that Licensor specifies to be 258 | associated with the Work, unless such URI does not refer to 259 | the copyright notice or licensing information for the Work; 260 | and (iv), consistent with Section 3(b), in the case of an 261 | Adaptation, a credit identifying the use of the Work in the 262 | Adaptation (e.g., "French translation of the Work by Original 263 | Author," or "Screenplay based on original Work by Original 264 | Author"). The credit required by this Section 4 (b) may be 265 | implemented in any reasonable manner; provided, however, that 266 | in the case of a Adaptation or Collection, at a minimum such 267 | credit will appear, if a credit for all contributing authors 268 | of the Adaptation or Collection appears, then as part of these 269 | credits and in a manner at least as prominent as the credits 270 | for the other contributing authors. For the avoidance of 271 | doubt, You may only use the credit required by this Section 272 | for the purpose of attribution in the manner set out above 273 | and, by exercising Your rights under this License, You may not 274 | implicitly or explicitly assert or imply any connection with, 275 | sponsorship or endorsement by the Original Author, Licensor 276 | and/or Attribution Parties, as appropriate, of You or Your use 277 | of the Work, without the separate, express prior written 278 | permission of the Original Author, Licensor and/or 279 | Attribution Parties. 280 | 281 | c. Except as otherwise agreed in writing by the Licensor or as 282 | may be otherwise permitted by applicable law, if You 283 | Reproduce, Distribute or Publicly Perform the Work either by 284 | itself or as part of any Adaptations or Collections, You must 285 | not distort, mutilate, modify or take other derogatory action 286 | in relation to the Work which would be prejudicial to the 287 | Original Author's honor or reputation. Licensor agrees that in 288 | those jurisdictions (e.g. Japan), in which any exercise of the 289 | right granted in Section 3(b) of this License (the right to 290 | make Adaptations) would be deemed to be a distortion, 291 | mutilation, modification or other derogatory action 292 | prejudicial to the Original Author's honor and reputation, the 293 | Licensor will waive or not assert, as appropriate, this 294 | Section, to the fullest extent permitted by the applicable 295 | national law, to enable You to reasonably exercise Your right 296 | under Section 3(b) of this License (right to make Adaptations) 297 | but not otherwise. 298 | 299 | 5. Representations, Warranties and Disclaimer 300 | 301 | UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, 302 | LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR 303 | WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, 304 | STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF 305 | TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, 306 | NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, 307 | ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT 308 | DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF 309 | IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 310 | 311 | 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY 312 | APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY 313 | LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE 314 | OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF 315 | THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY 316 | OF SUCH DAMAGES. 317 | 318 | 7. Termination 319 | 320 | a. This License and the rights granted hereunder will terminate 321 | automatically upon any breach by You of the terms of this 322 | License. Individuals or entities who have received Adaptations 323 | or Collections from You under this License, however, will not 324 | have their licenses terminated provided such individuals or 325 | entities remain in full compliance with those licenses. 326 | Sections 1, 2, 5, 6, 7, and 8 will survive any termination of 327 | this License. 328 | 329 | b. Subject to the above terms and conditions, the license granted 330 | here is perpetual (for the duration of the applicable 331 | copyright in the Work). Notwithstanding the above, Licensor 332 | reserves the right to release the Work under different license 333 | terms or to stop distributing the Work at any time; provided, 334 | however that any such election will not serve to withdraw this 335 | License (or any other license that has been, or is required to 336 | be, granted under the terms of this License), and this License 337 | will continue in full force and effect unless terminated as 338 | stated above. 339 | 340 | 8. Miscellaneous 341 | 342 | a. Each time You Distribute or Publicly Perform the Work or a 343 | Collection, the Licensor offers to the recipient a license to 344 | the Work on the same terms and conditions as the license 345 | granted to You under this License. 346 | 347 | b. Each time You Distribute or Publicly Perform an Adaptation, 348 | Licensor offers to the recipient a license to the original 349 | Work on the same terms and conditions as the license granted 350 | to You under this License. 351 | 352 | c. If any provision of this License is invalid or unenforceable 353 | under applicable law, it shall not affect the validity or 354 | enforceability of the remainder of the terms of this License, 355 | and without further action by the parties to this agreement, 356 | such provision shall be reformed to the minimum extent 357 | necessary to make such provision valid and enforceable. 358 | 359 | d. No term or provision of this License shall be deemed waived 360 | and no breach consented to unless such waiver or consent shall 361 | be in writing and signed by the party to be charged with such 362 | waiver or consent. 363 | 364 | e. This License constitutes the entire agreement between the 365 | parties with respect to the Work licensed here. There are no 366 | understandings, agreements or representations with respect to 367 | the Work not specified here. Licensor shall not be bound by 368 | any additional provisions that may appear in any communication 369 | from You. This License may not be modified without the mutual 370 | written agreement of the Licensor and You. 371 | 372 | f. The rights granted under, and the subject matter referenced, 373 | in this License were drafted utilizing the terminology of the 374 | Berne Convention for the Protection of Literary and Artistic 375 | Works (as amended on September 28, 1979), the Rome Convention 376 | of 1961, the WIPO Copyright Treaty of 1996, the WIPO 377 | Performances and Phonograms Treaty of 1996 and the Universal 378 | Copyright Convention (as revised on July 24, 1971). These 379 | rights and subject matter take effect in the relevant 380 | jurisdiction in which the License terms are sought to be 381 | enforced according to the corresponding provisions of the 382 | implementation of those treaty provisions in the applicable 383 | national law. If the standard suite of rights granted under 384 | applicable copyright law includes additional rights not 385 | granted under this License, such additional rights are deemed 386 | to be included in the License; this License is not intended to 387 | restrict the license of any rights under applicable law. 388 | 389 | Creative Commons is not a party to this License, and makes no 390 | warranty whatsoever in connection with the Work. Creative Commons 391 | will not be liable to You or any party on any legal theory for any 392 | damages whatsoever, including without limitation any general, 393 | special, incidental or consequential damages arising in connection 394 | to this license. Notwithstanding the foregoing two (2) sentences, 395 | if Creative Commons has expressly identified itself as the Licensor 396 | hereunder, it shall have all rights and obligations of Licensor. 397 | 398 | Except for the limited purpose of indicating to the public that the 399 | Work is licensed under the CCPL, Creative Commons does not authorize 400 | the use by either party of the trademark "Creative Commons" or any 401 | related trademark or logo of Creative Commons without the prior 402 | written consent of Creative Commons. Any permitted use will be in 403 | compliance with Creative Commons' then-current trademark usage 404 | guidelines, as may be published on its website or otherwise made 405 | available upon request from time to time. For the avoidance of 406 | doubt, this trademark restriction does not form part of this 407 | License. 408 | 409 | Creative Commons may be contacted at http://creativecommons.org/. 410 | 411 | ==================================================================== 412 | 413 | 414 | -------------------------------------------------------------------------------- /src/extending-phpunit.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. _extending-phpunit: 4 | 5 | ================= 6 | 扩展 PHPUnit 7 | ================= 8 | 9 | 可以用多种方式对 PHPUnit 进行扩展,使编写测试更容易,以及对运行测试所得到的反馈进行定制。扩展 PHPUnit 时,一般从这些点入手: 10 | 11 | .. _extending-phpunit.PHPUnit_Framework_TestCase: 12 | 13 | PHPUnit\\Framework\\TestCase 的子类 14 | ##################################### 15 | 16 | 将自定义的断言和工具方法写在 ``PHPUnit\Framework\TestCase`` 的一个抽象子类中,然后从这个抽象子类派生你的测试用例类。这是扩展 PHPUnit 的最容易的方法。 17 | 18 | .. _extending-phpunit.custom-assertions: 19 | 20 | 编写自定义断言 21 | ####################### 22 | 23 | 编写自定义断言时,最佳实践是遵循 PHPUnit 自有断言的实现方式。正如\ :numref:`extending-phpunit.examples.Assert.php` 中所示,``assertTrue()`` 方法只是对 ``isTrue()`` 和 ``assertThat()`` 方法的封装:``isTrue()`` 创建了一个匹配器对象,将其传递给 ``assertThat()`` 进行评定。 24 | 25 | .. code-block:: php 26 | :caption: PHPUnit\\Framework\\Assert 类的 assertTrue() 和 isTrue() 方法 27 | :name: extending-phpunit.examples.Assert.php 28 | 29 | config_value_1 = $config_1; 153 | $this->config_value_2 = $config_2; 154 | } 155 | 156 | public function executeBeforeFirstTest(): void 157 | { 158 | if (strlen($this->config_value_1) { 159 | echo 'Testing with configuration value: ' . $this->config_value_1; 160 | } 161 | } 162 | 163 | public function executeAfterLastTest(): void 164 | { 165 | if ($this->config_value_2 > 10) { 166 | echo 'Second config value is OK!'; 167 | } 168 | } 169 | } 170 | 171 | 要通过 XML 给扩展输入配置,必须更新 XML 配置文件的 ``extensions`` 段来让其拥有配置值,如\ :numref:`extending-phpunit.examples.TestRunnerConfigurableExtensionConfig` 中所示: 172 | 173 | .. code-block:: xml 174 | :caption: 测试执行器扩展配置 175 | :name: extending-phpunit.examples.TestRunnerConfigurableExtensionConfig 176 | 177 | 178 | 179 | 180 | 181 | Hello world! 182 | 15 183 | 184 | 185 | 186 | 187 | 有关如何使用 ``arguments`` 配置的详细信息,参见 :ref:`appendixes.configuration.extensions.extension.arguments`。 188 | 189 | 请记住:所有配置都是可选的,因此要确保你的配置要么要有健全的默认值,要么它就应当在缺失配置的时候禁用自身。 190 | -------------------------------------------------------------------------------- /src/fixtures.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. _fixtures: 4 | 5 | =============== 6 | 基境(fixture) 7 | =============== 8 | 9 | 在编写测试时,最费时的部分之一是编写代码来将整个场景设置成某个已知的状态,并在测试结束后将其复原到初始状态。这个已知的状态称为测试的\ *基境(fixture)*\ 。 10 | 11 | 在\ :ref:`writing-tests-for-phpunit.examples.StackTest.php`\ 中,基境就是存储在 ``$stack`` 变量中的数组。然而,绝大多数时候基境均远比一个简单数组要复杂,用于建立基境的代码量也会随之增长。测试的真正内容就被淹没于建立基境带来的干扰中。当编写多个需要类似基境的测试时这个问题就变得更糟糕了。如果没有来自于测试框架的帮助,就不得不在写每一个测试时都将建立基境的代码重复一次。 12 | 13 | PHPUnit 支持共享建立基境的代码。在运行某个测试方法前,会调用一个名叫 ``setUp()`` 的模板方法。``setUp()`` 是创建测试所用对象的地方。当测试方法运行结束后,不管是成功还是失败,都会调用另外一个名叫 ``tearDown()`` 的模板方法。``tearDown()`` 是清理测试所用对象的地方。 14 | 15 | 在\ :ref:`writing-tests-for-phpunit.examples.StackTest2.php`\ 中,我们在测试之间运用生产者-消费者关系来共享基境。这并非总是预期的方式,甚至有时是不可能的。\ :numref:`fixtures.examples.StackTest.php` 展示了另外一个编写测试 ``StackTest`` 的方式。在这个方式中,不再重用基境本身,而是重用建立基境的代码。首先声明一个实例变量,``$stack``,用来替代方法内的局部变量。然后把 ``array`` 基境的建立放到 ``setUp()`` 方法中。最后,从测试方法中去除冗余代码,在 ``assertSame()`` 断言方法中使用新引入的实例变量 ``$this->stack`` 替代方法内的局部变量 ``$stack``。 16 | 17 | .. code-block:: php 18 | :caption: 用 setUp() 来创建堆栈基境 19 | :name: fixtures.examples.StackTest.php 20 | 21 | stack = []; 31 | } 32 | 33 | public function testEmpty(): void 34 | { 35 | $this->assertTrue(empty($this->stack)); 36 | } 37 | 38 | public function testPush(): void 39 | { 40 | array_push($this->stack, 'foo'); 41 | 42 | $this->assertSame('foo', $this->stack[count($this->stack)-1]); 43 | $this->assertFalse(empty($this->stack)); 44 | } 45 | 46 | public function testPop(): void 47 | { 48 | array_push($this->stack, 'foo'); 49 | 50 | $this->assertSame('foo', array_pop($this->stack)); 51 | $this->assertTrue(empty($this->stack)); 52 | } 53 | } 54 | 55 | 测试类的每个测试方法都会运行一次 ``setUp()`` 和 ``tearDown()`` 模板方法(同时,每个测试方法都是在一个全新的测试类实例上运行的)。 56 | 57 | 另外,``setUpBeforeClass()`` 与 ``tearDownAfterClass()`` 模板方法将分别在测试用例类的第一个测试运行之前和测试用例类的最后一个测试运行之后调用。 58 | 59 | 下面这个例子中展示了测试用例类中所有可用的模板方法。 60 | 61 | .. code-block:: php 62 | :caption: 展示所有可用模板方法的示例 63 | :name: fixtures.examples.TemplateMethodsTest.php 64 | 65 | assertTrue(true); 89 | } 90 | 91 | public function testTwo(): void 92 | { 93 | fwrite(STDOUT, __METHOD__ . "\n"); 94 | $this->assertTrue(false); 95 | } 96 | 97 | protected function assertPostConditions(): void 98 | { 99 | fwrite(STDOUT, __METHOD__ . "\n"); 100 | } 101 | 102 | protected function tearDown(): void 103 | { 104 | fwrite(STDOUT, __METHOD__ . "\n"); 105 | } 106 | 107 | public static function tearDownAfterClass(): void 108 | { 109 | fwrite(STDOUT, __METHOD__ . "\n"); 110 | } 111 | 112 | protected function onNotSuccessfulTest(Throwable $t): void 113 | { 114 | fwrite(STDOUT, __METHOD__ . "\n"); 115 | throw $t; 116 | } 117 | } 118 | 119 | .. parsed-literal:: 120 | 121 | $ phpunit TemplateMethodsTest 122 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 123 | 124 | TemplateMethodsTest::setUpBeforeClass 125 | TemplateMethodsTest::setUp 126 | TemplateMethodsTest::assertPreConditions 127 | TemplateMethodsTest::testOne 128 | TemplateMethodsTest::assertPostConditions 129 | TemplateMethodsTest::tearDown 130 | .TemplateMethodsTest::setUp 131 | TemplateMethodsTest::assertPreConditions 132 | TemplateMethodsTest::testTwo 133 | TemplateMethodsTest::tearDown 134 | TemplateMethodsTest::onNotSuccessfulTest 135 | FTemplateMethodsTest::tearDownAfterClass 136 | 137 | Time: 0 seconds, Memory: 5.25Mb 138 | 139 | There was 1 failure: 140 | 141 | 1) TemplateMethodsTest::testTwo 142 | Failed asserting that is true. 143 | /home/sb/TemplateMethodsTest.php:30 144 | 145 | FAILURES! 146 | Tests: 2, Assertions: 2, Failures: 1. 147 | 148 | .. _fixtures.more-setup-than-teardown: 149 | 150 | setUp() 多、tearDown() 少 151 | ############################ 152 | 153 | 理论上说,``setUp()`` 和 ``tearDown()`` 是精确对称的,但是实践中并非如此。实际上,只有在 ``setUp()`` 中分配了诸如文件或套接字之类的外部资源时才需要实现 ``tearDown()`` 。如果 ``setUp()`` 中只创建纯 PHP 对象,通常可以略过 ``tearDown()``。不过,如果在 ``setUp()`` 中创建了大量对象,你可能想要在 ``tearDown()`` 中 ``unset()`` 指向这些对象的变量,这样它们就可以被垃圾回收机制回收掉。对测试用例对象的垃圾回收动作则是不可预知的。 154 | 155 | .. _fixtures.variations: 156 | 157 | 变体 158 | ########## 159 | 160 | 如果拥有两个测试,它们的基境建立工作略有不同,该怎么办?有两种可能: 161 | 162 | - 163 | 164 | 如果两个 ``setUp()`` 代码仅有微小差异,把有差异的代码内容从 ``setUp()`` 移到测试方法内。 165 | 166 | - 167 | 168 | 如果两个 ``setUp()`` 是确实不一样,那么需要另外一个测试用例类。参考基境建立工作的不同之处来命名这个类。 169 | 170 | .. _fixtures.sharing-fixture: 171 | 172 | 基境共享 173 | ############### 174 | 175 | 有几个好的理由来在测试之间共享基境,但是大部分情况下,在测试之间共享基境的需求都源于某个未解决的设计问题。 176 | 177 | 一个有实际意义的多测试间共享基境的例子是数据库连接:只登录数据库一次,然后重用此连接,而不是每个测试都建立一个新的数据库连接。这样能加快测试的运行。 178 | 179 | :numref:`fixtures.sharing-fixture.examples.DatabaseTest.php` 中用 ``setUpBeforeClass()`` 和 ``tearDownAfterClass()`` 模板方法来分别在测试用例类的第一个测试之前和最后一个测试之后连接与断开数据库。 180 | 181 | .. code-block:: php 182 | :caption: 在同一个测试套件内的不同测试之间共享基境 183 | :name: fixtures.sharing-fixture.examples.DatabaseTest.php 184 | 185 | `_\ 使用全局变量的代码也一样。通常情况下,欲测代码和全局变量之间会强烈耦合,并且其创建无法控制。另外一个问题是,一个测试对全局变量的改变可能会破坏另外一个测试。 211 | 212 | 在 PHP 中,全局变量是这样运作的: 213 | 214 | - 215 | 216 | 全局变量 ``$foo = 'bar';`` 实际上是存储为 ``$GLOBALS['foo'] = 'bar';`` 的。 217 | 218 | - 219 | 220 | ``$GLOBALS``\ 这个变量是一种被称为\ *超全局*\ 变量的变量。 221 | 222 | - 223 | 224 | 超全局变量是一种在任何变量作用域中都总是可用的内建变量。 225 | 226 | - 227 | 228 | 在函数或者方法的变量作用域中,要访问全局变量 ``$foo``,可以直接访问 ``$GLOBALS['foo']``,或者用 ``global $foo;`` 来创建一个引用全局变量的局部变量。 229 | 230 | 除了全局变量,类的静态属性也是一种全局状态。 231 | 232 | 在版本 6 之前,默认情况下,PHPUnit 用一种更改全局变量与超全局变量(``$GLOBALS``、\ ``$_ENV``、\ ``$_POST``、\ ``$_GET``、\ ``$_COOKIE``、\ ``$_SERVER``、\ ``$_FILES``、\ ``$_REQUEST``)不会影响到其他测试的方式来运行所有测试。 233 | 234 | 在版本 6 中,默认情况下 PHPUnit 不再对全局变量和超全局变量进行这种备份与恢复的操作。可以用 ``--globals-backup`` 选项或在 XML 配置文件中用 ``backupGlobals="true"`` 将其激活。 235 | 236 | 通过用 ``--static-backup`` 选项或在 XML 配置文件中设置 ``backupStaticAttributes="true"``,可以将此隔离扩展到类的静态属性。 237 | 238 | .. admonition:: 注 239 | 240 | 对全局变量和类的静态属性的备份与还原操作使用了 ``serialize()`` 与 ``unserialize()``。 241 | 242 | 某些类的实例对象(比如 ``PDO``)无法序列化,因此如果把这样一个对象存放在比如说 ``$GLOBALS`` 数组内时,备份操作就会出问题。 243 | 244 | 在 :ref:`appendixes.annotations.backupGlobals` 中所讨论的 ``@backupGlobals`` 标注可以用来控制对全局变量的备份与还原操作。另外,还可以提供一个全局变量的名单,名单中的全局变量将被排除于备份与还原操作之外,就像这样: 245 | 246 | .. code-block:: php 247 | 248 | final class MyTest extends TestCase 249 | { 250 | protected $backupGlobalsExcludeList = ['globalVariable']; 251 | 252 | // ... 253 | } 254 | 255 | .. admonition:: 注 256 | 257 | 在方法(例如 ``setUp()`` 方法)内对 ``$backupGlobalsBlacklist`` 属性进行设置是无效的。 258 | 259 | 在 :ref:`appendixes.annotations.backupStaticAttributes` 中提到的 ``@backupStaticAttributes`` 标注可以用于在每个测试之前备份所有已声明类的静态属性值并在其后恢复。 260 | 261 | 它所处理的并不只是测试类自身,而是在测试开始时已声明的所有类。它只作用于静态类属性,不作用于函数内声明的静态变量。 262 | 263 | .. admonition:: 注 264 | 265 | 只有启用了 ``@backupStaticAttributes`` 的测试方法才会在方法之前执行此操作。如果在此之前运行的某个没有启用 ``@backupStaticAttributes`` 的测试方法改变了静态属性的值,那么被备份及还原的将会是这个改变后的值——而非初始声明时提供的默认值。PHP 并不额外记录任何静态变量的声明时提供的初始默认值。 266 | 267 | 同样的情况也发生于测试内部新加载/声明的类的静态属性上。它们也无法在测试结束之后复原为声明时提供的原始默认值,因为无从得知这些默认值。这些被修改过的值会泄漏到后继测试中。 268 | 269 | 对单元测试而言,推荐在 ``setUp()`` 中显式的重置测试中使用到的静态属性(最好同时在 ``tearDown()`` 中执行重置,这样就保证不会影响到后继的测试)。 270 | 271 | 可以提供名单来将静态属性从备份与还原操作中排除出去: 272 | 273 | .. code-block:: php 274 | 275 | final class MyTest extends TestCase 276 | { 277 | protected $backupStaticAttributesExcludeList = [ 278 | 'className' => ['attributeName'] 279 | ]; 280 | 281 | // ... 282 | } 283 | 284 | .. admonition:: 注 285 | 286 | 在方法(例如 ``setUp()`` 方法)内对 ``$backupStaticAttributesExcludeList`` 属性进行设置是无效的。 287 | 288 | 289 | -------------------------------------------------------------------------------- /src/incomplete-and-skipped-tests.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. _incomplete-and-skipped-tests: 4 | 5 | ============================ 6 | 未完成的测试与跳过的测试 7 | ============================ 8 | 9 | .. _incomplete-and-skipped-tests.incomplete-tests: 10 | 11 | 未完成的测试 12 | ################ 13 | 14 | 开始写新的测试用例类时,可能想从写下空测试方法开始,比如: 15 | 16 | .. code-block:: php 17 | 18 | public function testSomething(): void 19 | { 20 | } 21 | 22 | 以此来跟踪需要编写的测试。空测试的问题是 PHPUnit 框架会将它们解读为成功。这种错误解读导致错误报告变得毫无用处——无法分辨出测试是真的成功了还是根本就未编写实现。在未实现的测试中调用 ``$this->fail()`` 同样没啥帮助,因为测试将被解读为失败。这和将未实现的测试解读为成功是一样的错误。 23 | 24 | 假如把成功的测试视为绿灯、测试失败视为红灯,那么还额外需要黄灯来将测试标记为未完成或尚未实现。``PHPUnit\Framework\IncompleteTest`` 是一个标记接口,用于将测试方法抛出的异常标记为测试未完成或目前尚未实现而导致的结果。``PHPUnit\Framework\IncompleteTestError`` 是这个接口的标准实现。 25 | 26 | :numref:`incomplete-and-skipped-tests.incomplete-tests.examples.SampleTest.php` 展示了一个测试用例类 ``SampleTest``,它有一个测试方法 ``testSomething()``。通过在测试方法中调用便捷方法 ``markTestIncomplete()``(会自动抛出一个 ``PHPUnit\Framework\IncompleteTestError`` 异常)将这个测试标记为未完成。 27 | 28 | .. code-block:: php 29 | :caption: 将测试标记为不完整 30 | :name: incomplete-and-skipped-tests.incomplete-tests.examples.SampleTest.php 31 | 32 | assertTrue(true, 'This should already work.'); 41 | 42 | // 在这里停止,并将此测试标记为未完成。 43 | $this->markTestIncomplete( 44 | 'This test has not been implemented yet.' 45 | ); 46 | } 47 | } 48 | 49 | 在 PHPUnit 命令行测试执行器的输出中,未完成的测试记为 ``I``,如下例所示: 50 | 51 | .. parsed-literal:: 52 | 53 | $ phpunit --verbose SampleTest 54 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 55 | 56 | I 57 | 58 | Time: 0 seconds, Memory: 3.95Mb 59 | 60 | There was 1 incomplete test: 61 | 62 | 1) SampleTest::testSomething 63 | This test has not been implemented yet. 64 | 65 | /home/sb/SampleTest.php:12 66 | OK, but incomplete or skipped tests! 67 | Tests: 1, Assertions: 1, Incomplete: 1. 68 | 69 | :numref:`incomplete-and-skipped-tests.incomplete-tests.tables.api` 列举了用于将测试标记为未完成的 API。 70 | 71 | .. rst-class:: table 72 | .. list-table:: 用于不完整的测试的 API 73 | :name: incomplete-and-skipped-tests.incomplete-tests.tables.api 74 | :header-rows: 1 75 | 76 | * - 方法 77 | - 含义 78 | * - ``void markTestIncomplete()`` 79 | - 将当前测试标记为未完成。 80 | * - ``void markTestIncomplete(string $message)`` 81 | - 将当前测试标记为未完成,并用 ``$message`` 作为说明信息。 82 | 83 | .. _incomplete-and-skipped-tests.skipping-tests: 84 | 85 | 跳过测试 86 | ############## 87 | 88 | 并非所有测试都能在任何环境中运行。比如说,考虑这样一种情况:一个数据库抽象层,针对其所支持的各种数据库系统有多个不同的驱动程序。针对 MySQL 驱动程序的测试只在 MySQL 服务器可用才能运行。 89 | 90 | :numref:`incomplete-and-skipped-tests.skipping-tests.examples.DatabaseTest.php` 展示了一个测试用例类 ``DatabaseTest``,它有一个测试方法 ``testConnection()``。在测试用例类的 ``setUp()`` 模板方法中,检查了 MySQLi 扩展是否可用,并且在扩展不可用时用 ``markTestSkipped()`` 方法来跳过此测试。 91 | 92 | .. code-block:: php 93 | :caption: 跳过测试 94 | :name: incomplete-and-skipped-tests.skipping-tests.examples.DatabaseTest.php 95 | 96 | markTestSkipped( 105 | 'The MySQLi extension is not available.' 106 | ); 107 | } 108 | } 109 | 110 | public function testConnection(): void 111 | { 112 | // ... 113 | } 114 | } 115 | 116 | 在 PHPUnit 命令行测试执行器的输出中,被跳过的测试记为 ``S``,如下例所示: 117 | 118 | .. parsed-literal:: 119 | 120 | $ phpunit --verbose DatabaseTest 121 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 122 | 123 | S 124 | 125 | Time: 0 seconds, Memory: 3.95Mb 126 | 127 | There was 1 skipped test: 128 | 129 | 1) DatabaseTest::testConnection 130 | The MySQLi extension is not available. 131 | 132 | /home/sb/DatabaseTest.php:9 133 | OK, but incomplete or skipped tests! 134 | Tests: 1, Assertions: 0, Skipped: 1. 135 | 136 | :numref:`incomplete-and-skipped-tests.skipped-tests.tables.api` 列举了用于跳过测试的 API。 137 | 138 | .. rst-class:: table 139 | .. list-table:: 用于跳过测试的 API 140 | :name: incomplete-and-skipped-tests.skipped-tests.tables.api 141 | :header-rows: 1 142 | 143 | * - 方法 144 | - 含义 145 | * - ``void markTestSkipped()`` 146 | - 将当前测试标记为已跳过。 147 | * - ``void markTestSkipped(string $message)`` 148 | - 将当前测试标记为已跳过,并用 ``$message`` 作为说明信息。 149 | 150 | .. _incomplete-and-skipped-tests.skipping-tests-using-requires: 151 | 152 | 用 @requires 来跳过测试 153 | ############################## 154 | 155 | 除了上述方法,还可以用 ``@requires`` 标注来表达测试用例的一些常见前提条件。 156 | 157 | .. rst-class:: table 158 | .. list-table:: 可能的 @requires 用法 159 | :name: incomplete-and-skipped-tests.requires.tables.api 160 | :header-rows: 1 161 | 162 | * - 类型 163 | - 可能值 164 | - 示例 165 | - 其他示例 166 | * - ``PHP`` 167 | - 任意 PHP 版本号以及可选的运算符 168 | - @requires PHP 7.1.20 169 | - @requires PHP >= 7.2 170 | * - ``PHPUnit`` 171 | - 任意 PHPUnit 版本号以及可选的运算符 172 | - @requires PHPUnit 7.3.1 173 | - @requires PHPUnit < 8 174 | * - ``OS`` 175 | - 与 `PHP_OS `_ 匹配的正则表达式 176 | - @requires OS Linux 177 | - @requires OS WIN32|WINNT 178 | * - ``OSFAMILY`` 179 | - 任意 `OS family `_ 180 | - @requires OSFAMILY Solaris 181 | - @requires OSFAMILY Windows 182 | * - ``function`` 183 | - 任意 `function_exists `_ 的有效参数 184 | - @requires function imap_open 185 | - @requires function ReflectionMethod::setAccessible 186 | * - ``extension`` 187 | - 任意扩展名以及可选的版本号和可选的运算符 188 | - @requires extension mysqli 189 | - @requires extension redis >= 2.2.0 190 | 191 | PHP、PHPUnit 和扩展的版本约束支持以下运算符:``<``、``<=``、``>``、``>=``、``=``、``==``、``!=``、``<>``。 192 | 193 | 版本是用 PHP 的 `version_compare `_ 函数进行比较的。除了其他事情之外,这意味着 ``=`` 和 ``==`` 运算符只能用于完整的 ``X.Y.Z`` 版本号,只用 ``X.Y`` 是不行的。 194 | 195 | .. code-block:: php 196 | :caption: 用 @requires 跳过测试 197 | :name: incomplete-and-skipped-tests.skipping-tests.examples.DatabaseClassSkippingTest.php 198 | 199 | = 5.3 209 | */ 210 | public function testConnection(): void 211 | { 212 | // 测试需要 mysqli 扩展,并且要求 PHP >= 5.3 213 | } 214 | 215 | // ... 其他需要 mysqli 扩展的测试 216 | } 217 | 218 | 如果使用了某种在特定版本的 PHP 下无法编译的语法,请参考\ :ref:`appendixes.configuration.testsuites`\ 中 XML 配置信息里关于版本依赖的部分。 219 | 220 | 221 | -------------------------------------------------------------------------------- /src/index.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | PHPUnit 手册 3 | ============== 4 | 5 | 此版本对应于 PHPUnit |version|。最后更新于 |today|。 6 | 7 | Sebastian Bergmann 8 | 9 | 本作品依据 Creative Commons Attribution 3.0 Unported 许可协议进行授权。 10 | 11 | 内容: 12 | 13 | .. toctree:: 14 | :maxdepth: 3 15 | :numbered: 1 16 | 17 | installation 18 | writing-tests-for-phpunit 19 | textui 20 | fixtures 21 | organizing-tests 22 | risky-tests 23 | incomplete-and-skipped-tests 24 | test-doubles 25 | code-coverage-analysis 26 | extending-phpunit 27 | 28 | .. toctree:: 29 | :caption: Appendix 30 | :maxdepth: 2 31 | :numbered: 1 32 | 33 | assertions 34 | annotations 35 | configuration 36 | bibliography 37 | copyright 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/installation.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. _installation: 4 | 5 | ================== 6 | 安装 PHPUnit 7 | ================== 8 | 9 | .. _installation.requirements: 10 | 11 | 需求 12 | ############ 13 | 14 | PHPUnit |version| 需要 PHP 7.3,强烈推荐使用最新版本的 PHP。 15 | 16 | PHPUnit 需要使用 `dom `_ 和 `json `_ 扩展,它们通常是默认启用的。 17 | 18 | PHPUnit 还需要 `pcre `_、`reflection `_ 和 `spl `_ 扩展。这些标准扩展默认启用,并且除非修改 PHP 的构建系统和 C 源代码,否则无法禁用它们。 19 | 20 | 代码覆盖率分析报告功能需要 `Xdebug `_\ (2.7.0 或以上)和 `tokenizer `_ 扩展。生成 XML 格式的报告需要有 `xmlwriter `_ 扩展。 21 | 22 | .. _installation.phar: 23 | 24 | PHP 档案包(PHAR) 25 | ################## 26 | 27 | 要获取 PHPUnit,最简单的方法是下载 PHPUnit 的 `PHP 档案包(PHAR) `_,它将 PHPUnit 所需要的所有必要组件(以及某些可选组件)捆绑在单个文件中: 28 | 29 | 要使用 PHP 档案包(PHAR)需要有 `phar `_ 扩展。 30 | 31 | 如果启用了 `Suhosin `_ 扩展,需要在 ``php.ini`` 中允许执行 PHAR: 32 | 33 | .. code-block:: bash 34 | 35 | suhosin.executor.include.whitelist = phar 36 | 37 | 可以在下载后立即使用 PHPUnit PHAR: 38 | 39 | .. parsed-literal:: 40 | 41 | $ wget https://phar.phpunit.de/phpunit-|version|.phar 42 | $ php phpunit-|version|.phar --version 43 | PHPUnit x.y.z by Sebastian Bergmann and contributors. 44 | 45 | 让 PHAR 可执行是种常见做法: 46 | 47 | .. parsed-literal:: 48 | 49 | $ wget https://phar.phpunit.de/phpunit-|version|.phar 50 | $ chmod +x phpunit-|version|.phar 51 | $ ./phpunit-|version|.phar --version 52 | PHPUnit x.y.z by Sebastian Bergmann and contributors. 53 | 54 | .. _installation.phar.verification: 55 | 56 | 校验 PHPUnit PHAR 发行包 57 | =============================== 58 | 59 | 由 PHPUnit 项目分发的所有官方代码发行包都由发行包管理器进行签名。在 `phar.phpunit.de `_ 上有 PGP 签名和 SHA256 散列值可用于校验。 60 | 61 | 下面的例子详细说明了如何对发行包进行校验。首先下载 :file:`phpunit.phar` 和与之对应的单独 PGP 签名 :file:`phpunit.phar.asc`: 62 | 63 | .. parsed-literal:: 64 | 65 | $ wget https://phar.phpunit.de/phpunit-|version|.phar 66 | $ wget https://phar.phpunit.de/phpunit-|version|.phar.asc 67 | 68 | 用单独的签名(:file:`phpunit-x.y.phar`)对 PHPUnit 的 PHP 档案包(:file:`phpunit-x.y.phar.asc`)进行校验: 69 | 70 | .. parsed-literal:: 71 | 72 | $ gpg phpunit-|version|.phar.asc 73 | gpg: Signature made Sat 19 Jul 2014 01:28:02 PM CEST using RSA key ID 6372C20A 74 | gpg: Can't check signature: public key not found 75 | 76 | 在本地系统中没有发行包管理器的公钥(``6372C20A``)。为了能进行校验,必须从某个密钥服务器上取得发行包管理器的公钥。其中一个服务器是 :file:`pgp.uni-mainz.de`。所有密钥服务器是链接在一起的,因此连接到任一密钥服务器都可以。 77 | 78 | .. parsed-literal:: 79 | 80 | $ curl --silent https://sebastian-bergmann.de/gpg.asc | gpg --import 81 | gpg: key 4AA394086372C20A: 452 signatures not checked due to missing keys 82 | gpg: /root/.gnupg/trustdb.gpg: trustdb created 83 | gpg: key 4AA394086372C20A: public key "Sebastian Bergmann " imported 84 | gpg: Total number processed: 1 85 | gpg: imported: 1 86 | gpg: no ultimately trusted keys found 87 | 88 | 现在已经取得了条目名称为“Sebastian Bergmann ”的公钥。不过,无法检验这个密钥确实是由名叫 Sebastian Bergmann 的人创建的。但是可以先试着校验发行包的签名:为运行的测试以 HTML 或纯文本格式生成敏捷文档 89 | 90 | .. parsed-literal:: 91 | 92 | $ gpg phpunit-|version|.phar.asc 93 | gpg: Signature made Sat 19 Jul 2014 01:28:02 PM CEST using RSA key ID 6372C20A 94 | gpg: Good signature from "Sebastian Bergmann " 95 | gpg: aka "Sebastian Bergmann " 96 | gpg: aka "Sebastian Bergmann " 97 | gpg: aka "Sebastian Bergmann " 98 | gpg: aka "Sebastian Bergmann " 99 | gpg: aka "[jpeg image of size 40635]" 100 | gpg: WARNING: This key is not certified with a trusted signature! 101 | gpg: There is no indication that the signature belongs to the owner. 102 | Primary key fingerprint: D840 6D0D 8294 7747 2937 7831 4AA3 9408 6372 C20A 103 | 104 | 此时,签名已经没问题了,但是这个公钥还不能信任。签名没问题意味着文件未被篡改。可是由于公钥加密系统的性质,还需要再校验密钥 ``6372C20A`` 确实是由真正的 Sebastian Bergmann 创建的。 105 | 106 | 任何攻击者都能创建公钥并将其上传到公钥服务器。他们可以建立一个带恶意的发行包,并用这个假密钥进行签名。这样,如果尝试对这个损坏了的发行包进行签名校验,由于密钥是“真”密钥,校验将成功完成。因此,需要对这个密钥的真实性进行校验。如何对公钥的真实性进行校验已经超出了本文档的范畴。 107 | 108 | 用 GPG 来手工验证 PHPUnit PHAR 的真实性和完整性是很繁琐的。这就是 PHAR 安装与校验环境 PHIVE 创建的原因。你可以在其\ `网站 `_\ 上了解 PHIVE。 109 | 110 | .. _installation.composer: 111 | 112 | Composer 113 | ######## 114 | 115 | 如果用 `Composer `_ 来管理项目的依赖关系,只要在项目的 ``composer.json`` 文件中加上对 ``phpunit/phpunit`` 的(开发时)依赖关系即可: 116 | 117 | .. parsed-literal:: 118 | 119 | composer require --dev phpunit/phpunit ^\ |version| 120 | 121 | .. _installation.global: 122 | 123 | 全局安装 124 | ################### 125 | 126 | 请注意,并不推荐全局安装 PHPUnit,比如说放在 ``/usr/bin/phpunit`` 或 ``/usr/local/bin/phpunit``。 127 | 128 | 相反,PHPUnit 应该作为项目本地依赖项进行管理。 129 | 130 | 可以将你所需的特定 PHPUnit 版本的 PHAR 放入项目的 ``tools`` 目录(这目录应当是由 PHIVE 管理的)或者,如果使用 Composer,则取决于在项目的 ``composer.json`` 中指定的所需特定 PHPUnit 版本。 131 | 132 | Web 服务器 133 | ############# 134 | 135 | PHPUnit 是用于编写测试的框架,也是用于运行测试的命令行工具。编写和运行测试是开发时的活动。没有理由要将 PHPUnit 安装在 Web 服务器上。 136 | 137 | **如果将 PHPUnit 上传到 Web 服务器,则部署过程会中断。一般而言,如果** ``vendor`` **目录在 Web 服务器上可公开访问,则您的部署过程也会中断。** 138 | 139 | 请注意,如果将 PHPUnit 上传到 Web 服务器,则可能会发生“坏事”。\ `已经警告过你了。 `_ 140 | -------------------------------------------------------------------------------- /src/organizing-tests.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. _organizing-tests: 4 | 5 | ================ 6 | 组织测试 7 | ================ 8 | 9 | PHPUnit 的目标之一是测试应当可组合:我们希望能将任意数量的测试以任意组合方式运行,例如,整个项目的所有测试,或者项目中的某个组件内的所有类的测试,又或者仅仅某单个类的测试。 10 | 11 | PHPUnit 支持好几种不同的方式来组织测试以及将它们编排组合成测试套件。本章介绍了最常用的方法。 12 | 13 | .. _organizing-tests.filesystem: 14 | 15 | 用文件系统来编排测试套件 16 | ########################################### 17 | 18 | 编排测试套件的各种方式中,最简单的大概就是把所有测试用例源文件放在一个测试目录中。通过对测试目录进行递归遍历,PHPUnit 能自动发现并运行测试。 19 | 20 | 现在来看看 `sebastianbergmann/money `_ 这个库的测试套件。在这个项目的目录结构中,可以看到 :file:`tests` 目录下的测试用例类镜像了 :file:`src` 目录下被测系统(SUT,System Under Test)的包(package)与类(class)的结构: 21 | 22 | .. code-block:: none 23 | 24 | src tests 25 | `-- Currency.php `-- CurrencyTest.php 26 | `-- IntlFormatter.php `-- IntlFormatterTest.php 27 | `-- Money.php `-- MoneyTest.php 28 | `-- autoload.php 29 | 30 | 要运行这个库的全部测试,将 PHPUnit 命令行测试执行器指向测试目录: 31 | 32 | .. parsed-literal:: 33 | 34 | $ phpunit --bootstrap src/autoload.php tests 35 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 36 | 37 | ................................. 38 | 39 | Time: 636 ms, Memory: 3.50Mb 40 | 41 | OK (33 tests, 52 assertions) 42 | 43 | .. admonition:: 注 44 | 45 | 当 PHPUnit 命令行测试执行器指向一个目录时,它会在目录下查找 :file:`*Test.php` 文件。 46 | 47 | 如果只想运行在 ``CurrencyTest`` 文件中的 :file:`tests/CurrencyTest.php` 测试用例类中声明的测试,可以使用如下命令: 48 | 49 | .. parsed-literal:: 50 | 51 | $ phpunit --bootstrap src/autoload.php tests/CurrencyTest.php 52 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 53 | 54 | ........ 55 | 56 | Time: 280 ms, Memory: 2.75Mb 57 | 58 | OK (8 tests, 8 assertions) 59 | 60 | 如果想要对运行哪些测试有更细粒度的控制,可以使用 ``--filter`` 选项: 61 | 62 | .. parsed-literal:: 63 | 64 | $ phpunit --bootstrap src/autoload.php --filter testObjectCanBeConstructedForValidConstructorArgument tests 65 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 66 | 67 | .. 68 | 69 | Time: 167 ms, Memory: 3.00Mb 70 | 71 | OK (2 test, 2 assertions) 72 | 73 | .. admonition:: 注 74 | 75 | 这种方法的缺点是无法控制测试的运行顺序。这可能导致测试的依赖关系方面的问题,参见\ :ref:`writing-tests-for-phpunit.test-dependencies`。在下一节中,可以看到如何用 XML 配置文件来明确指定测试的执行顺序。 76 | 77 | .. _organizing-tests.xml-configuration: 78 | 79 | 用 XML 配置来编排测试套件 80 | ############################################## 81 | 82 | PHPUnit的 XML 配置文件(:ref:`appendixes.configuration`)也可以用于编排测试套件。:numref:`organizing-tests.xml-configuration.examples.phpunit.xml` 展示了一个最小化的 :file:`phpunit.xml` 例子,它将在递归遍历 :file:`tests` 时添加所有在 :file:`*Test.php` 文件中找到的 ``*Test`` 类。 83 | 84 | .. code-block:: xml 85 | :caption: 用 XML 配置来编排测试套件 86 | :name: organizing-tests.xml-configuration.examples.phpunit.xml 87 | 88 | 89 | 90 | 91 | tests 92 | 93 | 94 | 95 | 96 | 要运行测试套件,用 ``--testsuite`` 选项: 97 | 98 | .. parsed-literal:: 99 | 100 | $ phpunit --bootstrap src/autoload.php --testsuite money 101 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 102 | 103 | .. 104 | 105 | Time: 167 ms, Memory: 3.00Mb 106 | 107 | OK (2 test, 2 assertions) 108 | 109 | 如果 :file:`phpunit.xml` 或 :file:`phpunit.xml.dist`\ (按此顺序)存在于当前工作目录并且\ *未*\ 使用 ``--configuration``\ ,将自动从此文件中读取配置。 110 | 111 | 可以明确指定测试的执行顺序: 112 | 113 | .. code-block:: xml 114 | :caption: 用 XML 配置来编排测试套件 115 | :name: organizing-tests.xml-configuration.examples.phpunit.xml2 116 | 117 | 118 | 119 | 120 | tests/IntlFormatterTest.php 121 | tests/MoneyTest.php 122 | tests/CurrencyTest.php 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /src/risky-tests.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. _risky-tests: 4 | 5 | ============== 6 | 有风险的测试 7 | ============== 8 | 9 | 在执行测试时,PHPUnit 可以进行一些额外的检查,见下文。 10 | 11 | .. _risky-tests.useless-tests: 12 | 13 | 无用测试 14 | ############# 15 | 16 | 默认情况下,PHPUnit 会更严格地对待事实上不测试任何内容的测试。此项检查可以用\ :ref:`命令行 `\ 选项 ``--dont-report-useless-tests`` 或在 PHPUnit 的\ :ref:`配置文件 `\ 中设置 ``beStrictAboutTestsThatDoNotTestAnything="false"`` 来禁用。 17 | 18 | 在启用本项检查后,如果某个测试未进行任何断言,它将被标记为有风险。仿件对象中的预期同样视为断言。 19 | 20 | .. _risky-tests.unintentionally-covered-code: 21 | 22 | 意外的代码覆盖 23 | ############################ 24 | 25 | PHPUnit 可以更严格对待意外的代码覆盖。此项检查可以用\ :ref:`命令行 `\ 选项 ``--strict-coverage`` 或在 PHPUnit 的\ :ref:`配置文件 `\ 中设置 ``beStrictAboutCoversAnnotation="true"`` 来启用。 26 | 27 | 在启用本项检查后,如果某个带有 :ref:`@covers ` 标注的测试执行了未在 :ref:`@covers ` 或 :ref:`@uses ` 标注中列出的代码,它将被标记为有风险。 28 | 29 | 此外,通过在 PHPUnit 的\ :ref:`配置文件 `\ 中设置 ``forceCoversAnnotation="true"``,可以将没有 :ref:`@covers ` 标注的测试标记为有风险。 30 | 31 | .. _risky-tests.output-during-test-execution: 32 | 33 | 测试执行期间产生的输出 34 | ############################ 35 | 36 | PHPUnit 可以更严格对待测试执行期间产生的输出。 此项检查可以用\ :ref:`命令行 `\ 选项 ``--disallow-test-output`` 或在 PHPUnit 的\ :ref:`配置文件 `\ 中设置 ``beStrictAboutOutputDuringTests="true"`` 来启用。 37 | 38 | 在启用本项检查后,如果某个测试产生了输出,例如,在测试代码或被测代码中调用了 print,它将被标记为有风险。 39 | 40 | .. _risky-tests.test-execution-timeout: 41 | 42 | 测试执行时长的超时限制 43 | ###################### 44 | 45 | 如果安装了 ``PHP_Invoker`` 包并且 ``pcntl`` 扩展可用,那么可以对测试的执行时长进行限制。此时间限制可以用\ :ref:`命令行 `\ 选项 ``--enforce-time-limit`` 或在 PHPUnit 的\ :ref:`配置文件 `\ 中设置 ``enforceTimeLimit="true"`` 来启用。 46 | 47 | 带有 ``@large`` 标注的测试如果执行时间超过 60 秒将视为失败。此超时限制可以通过\ :ref:`配置文件 `\ 中的 ``timeoutForLargeTests`` 属性进行配置。 48 | 49 | 带有 ``@medium`` 标注的测试如果执行时间超过 10 秒将视为失败。此超时限制可以通过\ :ref:`配置文件 `\ 中的 ``timeoutForMediumTests`` 属性进行配置。 50 | 51 | 带有 ``@small`` 标注的测试如果执行时间超过 1 秒将视为失败。此超时限制可以通过\ :ref:`配置文件 `\ 中的 ``timeoutForSmallTests`` 属性进行配置。 52 | 53 | .. admonition:: 注 54 | 55 | 需要启用运行时间限制的测试必须显式地标注为 ``@small``、``@medium`` 或 ``@large``。 56 | 57 | 58 | .. _risky-tests.global-state-manipulation: 59 | 60 | 全局状态篡改 61 | ######################### 62 | 63 | PHPUnit 可以更严格对待篡改全局状态的测试。此项检查可以用\ :ref:`命令行 `\ 选项 ``--strict-global-state`` 或在 PHPUnit 的\ :ref:`配置文件 `\ 中设置 ``beStrictAboutChangesToGlobalState="true"`` 来启用。 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/test-doubles.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. _test-doubles: 4 | 5 | ============ 6 | 测试替身 7 | ============ 8 | 9 | Gerard Meszaros 在 :ref:`Meszaros2007 ` 中介绍了测试替身的概念: 10 | 11 | *Gerard Meszaros*: 12 | 13 | 有时候对被测系统(SUT)进行测试是很困难的,因为它依赖于其他无法在测试环境中使用的组件。这有可能是因为这些组件不可用,它们不会返回测试所需要的结果,或者执行它们会有不良副作用。在其他情况下,我们的测试策略要求对被测系统的内部行为有更多控制或更多可见性。 14 | 15 | 如果在编写测试时无法使用(或选择不使用)实际的依赖组件(DOC),可以用测试替身来代替。测试替身不需要和真正的依赖组件有完全一样的的行为方式;它只需要提供和真正的组件同样的 API 即可,这样被测系统就会以为它是真正的组件! 16 | 17 | PHPUnit 提供的 ``createStub($type)``、``createMock($type)`` 和 ``getMockBuilder($type)`` 方法可以在测试中用来自动生成对象,此对象可以充当任意指定原版类型(接口或类名)的测试替身。在任何预期或要求使用原版类的实例对象的上下文中都可以使用这个测试替身对象来代替。 18 | 19 | ``createStub($type)`` 和 ``createMock($type)`` 方法直接返回指定类型(接口或类)的测试替身对象实例。此测试替身的创建使用了最佳实践默认方案。原始类的 ``__construct()`` 和 ``__clone()`` 方法不会执行,且不对传递给测试替身的方法的参数进行克隆。如果这些默认值非你所需,可以用 ``getMockBuilder($type)`` 方法并使用流畅式接口来定制测试替身的生成过程。 20 | 21 | 在默认情况下,原版类的所有方法都会被替换为返回 ``null`` 的伪实现(其中不会调用原版方法)。使用诸如 ``will($this->returnValue())`` 之类的方法可以对这些伪实现在被调用时应当返回什么值做出配置。 22 | 23 | .. admonition:: 局限性:final、private、与 static 方法 24 | 25 | 请注意,``final``、``private`` 和 ``static`` 方法无法制作相应桩件(Stub)或仿件(Mock)。PHPUnit 的测试替身功能将会忽略它们,并维持它们的原始行为,``static`` 方法例外,它会被替换为一个会抛出 ``\PHPUnit\Framework\MockObject\BadMethodCallException`` 异常的方法。 26 | 27 | 28 | .. _test-doubles.stubs: 29 | 30 | Stubs(桩件) 31 | ############# 32 | 33 | 将对象替换为(可选地)返回配置好的返回值的测试替身的实践方法称为\ *打桩(stubbing)*\ 。可以用\ *桩件(Stub)*\ 来“替换掉被测系统所依赖的实际组件,这样测试就有了对被测系统的间接输入的控制点。这使得测试能强制安排被测系统的执行路径,否则被测系统可能无法执行”。 34 | 35 | :numref:`test-doubles.stubs.examples.StubTest.php` 展示了如何对方法的调用进行上桩以及如何设定返回值。首先用 ``PHPUnit\Framework\TestCase`` 类提供的 ``createStub()`` 方法来建立一个桩件对象,它表面看起来像是 ``SomeClass`` 类(:numref:`test-doubles.stubs.examples.SomeClass.php`\ )的实例。随后用 PHPUnit 提供的\ `流畅式接口 `_\ 来指定桩件的行为。本质上,这意味着不需要建立多个临时对象然后再把它们捆到一起。取而代之的是范例中所示的链式方法调用。这使得代码更加易读并更加“流畅”。 36 | 37 | .. code-block:: php 38 | :caption: 想要上桩的类 39 | :name: test-doubles.stubs.examples.SomeClass.php 40 | 41 | createStub(SomeClass::class); 63 | 64 | // 配置桩件。 65 | $stub->method('doSomething') 66 | ->willReturn('foo'); 67 | 68 | // 现在调用 $stub->doSomething() 会返回 'foo'。 69 | $this->assertSame('foo', $stub->doSomething()); 70 | } 71 | 72 | 73 | .. admonition:: 局限性:名字为“method”的方法 74 | 75 | 仅当原始类中不包含名字为“method”的方法时,以上范例才能正常运行。 76 | 77 | 如果原始类包含名为“method”的方法,就必须用 ``$stub->expects($this->any())->method('doSomething')->willReturn('foo');``。 78 | 79 | “在幕后”,当使用了 ``createStub()`` 方法时, PHPUnit 自动生成了一个新的 PHP 类来实现想要的行为。 80 | 81 | 请注意:``createStub()`` 会自动递归地基于方法的返回类型对返回值进行上桩。考虑以下示例: 82 | 83 | .. code-block:: php 84 | :caption: 带有返回类型声明的方法 85 | :name: test-doubles.stubs.examples.returnTypeDeclaration.php 86 | 87 | getMockBuilder(SomeClass::class) 115 | ->disableOriginalConstructor() 116 | ->disableOriginalClone() 117 | ->disableArgumentCloning() 118 | ->disallowMockingUnknownTypes() 119 | ->getMock(); 120 | 121 | // 配置桩件。 122 | $stub->method('doSomething') 123 | ->willReturn('foo'); 124 | 125 | // 现在调用 $stub->doSomething() 会返回 'foo'。 126 | $this->assertSame('foo', $stub->doSomething()); 127 | } 128 | 129 | 130 | 在之前的例子中,用 ``willReturn($value)`` 返回简单值。这个简短的语法相当于 ``will($this->returnValue($value))``。而在这个长点的语法中,可以使用变量,从而实现更复杂的上桩行为。 131 | 132 | 有时想要将(未改变的)方法调用时所使用的参数之一作为桩件的方法的调用结果来返回。:numref:`test-doubles.stubs.examples.StubTest3.php` 展示了如何用 ``returnArgument()`` 代替 ``returnValue()`` 来做到这点。 133 | 134 | .. code-block:: php 135 | :caption: 对某个方法的调用进行上桩,返回参数之一 136 | :name: test-doubles.stubs.examples.StubTest3.php 137 | 138 | createStub(SomeClass::class); 147 | 148 | // 配置桩件。 149 | $stub->method('doSomething') 150 | ->will($this->returnArgument(0)); 151 | 152 | // $stub->doSomething('foo') 返回 'foo' 153 | $this->assertSame('foo', $stub->doSomething('foo')); 154 | 155 | // $stub->doSomething('bar') 返回 'bar' 156 | $this->assertSame('bar', $stub->doSomething('bar')); 157 | } 158 | } 159 | 160 | 在用流畅式接口进行测试时,让某个已上桩的方法返回对桩件对象的引用有时会很有用。:numref:`test-doubles.stubs.examples.StubTest4.php` 展示了如何用 ``returnSelf()`` 来做到这点。 161 | 162 | .. code-block:: php 163 | :caption: 对方法的调用进行上桩,返回对桩件对象的引用 164 | :name: test-doubles.stubs.examples.StubTest4.php 165 | 166 | createStub(SomeClass::class); 175 | 176 | // 配置桩件。 177 | $stub->method('doSomething') 178 | ->will($this->returnSelf()); 179 | 180 | // $stub->doSomething() 返回 $stub 181 | $this->assertSame($stub, $stub->doSomething()); 182 | } 183 | } 184 | 185 | 有时候,上桩的方法需要根据预定义的参数清单来返回不同的值。可以用 ``returnValueMap()`` 方法将参数和相应的返回值关联起来建立映射。示例参见\ :numref:`test-doubles.stubs.examples.StubTest5.php`。 186 | 187 | .. code-block:: php 188 | :caption: 对方法的调用进行上桩,按照映射确定返回值 189 | :name: test-doubles.stubs.examples.StubTest5.php 190 | 191 | createStub(SomeClass::class); 200 | 201 | // Create a map of arguments to return values. 202 | $map = [ 203 | ['a', 'b', 'c', 'd'], 204 | ['e', 'f', 'g', 'h'] 205 | ]; 206 | 207 | // 配置桩件。 208 | $stub->method('doSomething') 209 | ->will($this->returnValueMap($map)); 210 | 211 | // $stub->doSomething() 根据提供的参数返回不同的值。 212 | $this->assertSame('d', $stub->doSomething('a', 'b', 'c')); 213 | $this->assertSame('h', $stub->doSomething('e', 'f', 'g')); 214 | } 215 | } 216 | 217 | 如果上桩的方法需要返回计算得到的值而不是固定值(参见 ``returnValue()``)或某个(未改变的)参数(参见 ``returnArgument()``),可以用 ``returnCallback()`` 来让上桩的方法返回回调函数或方法的结果。示例参见\ :numref:`test-doubles.stubs.examples.StubTest6.php`。 218 | 219 | .. code-block:: php 220 | :caption: 对方法的调用进行上桩,由回调生成返回值 221 | :name: test-doubles.stubs.examples.StubTest6.php 222 | 223 | createStub(SomeClass::class); 232 | 233 | // 配置桩件。 234 | $stub->method('doSomething') 235 | ->will($this->returnCallback('str_rot13')); 236 | 237 | // $stub->doSomething($argument) 返回 str_rot13($argument) 238 | $this->assertSame('fbzrguvat', $stub->doSomething('something')); 239 | } 240 | } 241 | 242 | 相比于建立回调方法,有一个更简单的选择是直接给出期望返回值的列表。可以用 ``onConsecutiveCalls()`` 方法来做到这个。示例参见\ :numref:`test-doubles.stubs.examples.StubTest7.php`。 243 | 244 | .. code-block:: php 245 | :caption: 对方法的调用上桩,按照指定顺序返回列表中的值 246 | :name: test-doubles.stubs.examples.StubTest7.php 247 | 248 | createStub(SomeClass::class); 257 | 258 | // 配置桩件。 259 | $stub->method('doSomething') 260 | ->will($this->onConsecutiveCalls(2, 3, 5, 7)); 261 | 262 | // $stub->doSomething() 每次都会返回不同的值 263 | $this->assertSame(2, $stub->doSomething()); 264 | $this->assertSame(3, $stub->doSomething()); 265 | $this->assertSame(5, $stub->doSomething()); 266 | } 267 | } 268 | 269 | 除了返回一个值之外,上桩的方法还能抛出一个异常。:numref:`test-doubles.stubs.examples.StubTest8.php` 展示了如何用 ``throwException()`` 做到这点。 270 | 271 | .. code-block:: php 272 | :caption: 对方法的调用进行上桩,抛出异常 273 | :name: test-doubles.stubs.examples.StubTest8.php 274 | 275 | createStub(SomeClass::class); 284 | 285 | // 配置桩件。 286 | $stub->method('doSomething') 287 | ->will($this->throwException(new Exception)); 288 | 289 | // $stub->doSomething() 抛出异常 290 | $stub->doSomething(); 291 | } 292 | } 293 | 294 | 另外,也可以自行编写桩件,并在此过程中改善设计。在系统中被广泛使用的资源是通过单个外观(facade)来访问的,因此就能用桩件替换掉资源。例如,将散落在代码各处的对数据库的直接调用替换为单个 ``Database`` 对象,这个对象实现了 ``IDatabase`` 接口。接下来,就可以创建实现了 ``IDatabase`` 的桩件并在测试中使用之。甚至可以创建一个选项来控制是用桩件还是用真实数据库来运行测试,这样测试就既能在开发过程中用作本地测试,又能在实际数据库环境中进行集成测试。 295 | 296 | 需要上桩的功能往往集中在同一个对象中,这就改善了内聚度。将功能通过单一且一致的接口呈现出来,就降低了这部分与系统其他部分之间的耦合度。 297 | 298 | .. _test-doubles.mock-objects: 299 | 300 | 仿件对象(Mock Object) 301 | ####################### 302 | 303 | 将对象替换为能验证预期行为(例如断言某个方法必会被调用)的测试替身的实践方法称为\ *模仿(mocking)*\ 。 304 | 305 | 可以用\ *仿件对象(mock object)*\ “作为观察点来核实被测试系统在测试中的间接输出。通常,仿件对象还需要包括桩件的功能,因为如果测试尚未失败则仿件对象需要向被测系统返回一些值,但是其重点还是在对间接输出的核实上。因此,仿件对象远不止是桩件加断言,它是以一种从根本上完全不同的方式来使用的”(Gerard Meszaros)。 306 | 307 | .. admonition:: 局限性:对预期的自动校验 308 | 309 | PHPUnit 只会对在某个测试的作用域内生成的仿件对象进行自动校验。诸如在数据供给器内生成或用 ``@depends`` 标注注入测试的仿件对象,PHPUnit 并不会自动对其进行校验。 310 | 311 | 这有个例子:假设需要测试的当前方法,在例子中是 ``update()``,确实在一个观察着另外一个对象的对象中上被调用了。:numref:`test-doubles.mock-objects.examples.SUT.php` 展示了被测系统(SUT)中 ``Subject`` 和 ``Observer`` 两个类的代码。 312 | 313 | .. code-block:: php 314 | :caption: 被测系统(SUT)中 Subject 与 Observer 类的代码 315 | :name: test-doubles.mock-objects.examples.SUT.php 316 | 317 | name = $name; 328 | } 329 | 330 | public function getName() 331 | { 332 | return $this->name; 333 | } 334 | 335 | public function attach(Observer $observer) 336 | { 337 | $this->observers[] = $observer; 338 | } 339 | 340 | public function doSomething() 341 | { 342 | // 随便做点什么。 343 | // ... 344 | 345 | // 通知观察者我们做了点什么。 346 | $this->notify('something'); 347 | } 348 | 349 | public function doSomethingBad() 350 | { 351 | foreach ($this->observers as $observer) { 352 | $observer->reportError(42, 'Something bad happened', $this); 353 | } 354 | } 355 | 356 | protected function notify($argument) 357 | { 358 | foreach ($this->observers as $observer) { 359 | $observer->update($argument); 360 | } 361 | } 362 | 363 | // 其他方法。 364 | } 365 | 366 | class Observer 367 | { 368 | public function update($argument) 369 | { 370 | // 随便做点什么。 371 | } 372 | 373 | public function reportError($errorCode, $errorMessage, Subject $subject) 374 | { 375 | // 随便做点什么 376 | } 377 | 378 | // 其他方法。 379 | } 380 | 381 | :numref:`test-doubles.mock-objects.examples.SubjectTest.php` 展示了如何用仿件对象来测试 ``Subject`` 和 ``Observer`` 对象之间的互动。 382 | 383 | 首先用 ``PHPUnit\Framework\TestCase`` 类提供的 ``createMock()`` 方法来为 ``Observer`` 建立仿件对象。 384 | 385 | 由于关注的是检验某个方法是否被调用,以及调用时具体所使用的参数,因此引入 ``expects()`` 与 ``with()`` 方法来指明此交互应该是什么样的。 386 | 387 | .. code-block:: php 388 | :caption: 测试某个方法会以特定参数被调用一次 389 | :name: test-doubles.mock-objects.examples.SubjectTest.php 390 | 391 | createMock(Observer::class); 401 | 402 | // 为 update() 方法建立预期: 403 | // 只会以字符串 'something' 为参数调用一次。 404 | $observer->expects($this->once()) 405 | ->method('update') 406 | ->with($this->equalTo('something')); 407 | 408 | // 建立 Subject 对象并且将模仿的 Observer 对象附加其上。 409 | $subject = new Subject('My subject'); 410 | $subject->attach($observer); 411 | 412 | // 在 $subject 上调用 doSomething() 方法, 413 | // 我们预期会以字符串 'something' 调用模仿的 Observer 414 | // 对象的 update() 方法。 415 | $subject->doSomething(); 416 | } 417 | } 418 | 419 | ``with()`` 方法可以携带任何数量的参数,对应于被模仿的方法的参数数量。可以对方法的参数指定更加高等的约束而不仅是简单的匹配。 420 | 421 | .. code-block:: php 422 | :caption: 测试某个方法将会以特定数量的参数进行调用,并且对各个参数以多种方式进行约束 423 | :name: test-doubles.mock-objects.examples.SubjectTest2.php 424 | 425 | createMock(Observer::class); 434 | 435 | $observer->expects($this->once()) 436 | ->method('reportError') 437 | ->with( 438 | $this->greaterThan(0), 439 | $this->stringContains('Something'), 440 | $this->anything() 441 | ); 442 | 443 | $subject = new Subject('My subject'); 444 | $subject->attach($observer); 445 | 446 | // doSomethingBad() 方法应当会通过 447 | // reportError() 方法向 observer 报告错误。 448 | $subject->doSomethingBad(); 449 | } 450 | } 451 | 452 | ``withConsecutive()`` 方法可以接受任意多个数组作为参数,具体数量取决于欲测试的调用。每个数组都都是对被仿方法的相应参数的一组约束,就像 ``with()`` 中那样。 453 | 454 | .. code-block:: php 455 | :caption: 测试某个方法将会以特定参数被调用两次。 456 | :name: test-doubles.mock-objects.examples.with-consecutive.php 457 | 458 | getMockBuilder(stdClass::class) 466 | ->setMethods(['set']) 467 | ->getMock(); 468 | 469 | $mock->expects($this->exactly(2)) 470 | ->method('set') 471 | ->withConsecutive( 472 | [$this->equalTo('foo'), $this->greaterThan(0)], 473 | [$this->equalTo('bar'), $this->greaterThan(0)] 474 | ); 475 | 476 | $mock->set('foo', 21); 477 | $mock->set('bar', 48); 478 | } 479 | } 480 | 481 | ``callback()`` 约束用来进行更加复杂的参数校验。此约束的唯一参数是一个 PHP 回调项(callback)。此 PHP 回调项接受需要校验的参数作为其唯一参数,并应当在参数通过校验时返回 ``true``,否则返回 ``false``。 482 | 483 | .. code-block:: php 484 | :caption: 更复杂的参数校验 485 | :name: test-doubles.mock-objects.examples.SubjectTest3.php 486 | 487 | createMock(Observer::class); 496 | 497 | $observer->expects($this->once()) 498 | ->method('reportError') 499 | ->with( 500 | $this->greaterThan(0), 501 | $this->stringContains('Something'), 502 | $this->callback(function($subject) 503 | { 504 | return is_callable([$subject, 'getName']) && 505 | $subject->getName() == 'My subject'; 506 | } 507 | )); 508 | 509 | $subject = new Subject('My subject'); 510 | $subject->attach($observer); 511 | 512 | // doSomethingBad() 方法应当会通过 513 | // reportError() 方法向 observer 报告错误。 514 | $subject->doSomethingBad(); 515 | } 516 | } 517 | 518 | .. code-block:: php 519 | :caption: 测试某个方法将会被调用一次,并且以某个特定对象作为参数。 520 | :name: test-doubles.mock-objects.examples.clone-object-parameters-usecase.php 521 | 522 | getMockBuilder(stdClass::class) 532 | ->setMethods(['foo']) 533 | ->getMock(); 534 | 535 | $mock->expects($this->once()) 536 | ->method('foo') 537 | ->with($this->identicalTo($expectedObject)); 538 | 539 | $mock->foo($expectedObject); 540 | } 541 | } 542 | 543 | .. code-block:: php 544 | :caption: 创建仿件对象时启用参数克隆 545 | :name: test-doubles.mock-objects.examples.enable-clone-object-parameters.php 546 | 547 | getMockBuilder(stdClass::class) 557 | ->enableArgumentCloning() 558 | ->getMock(); 559 | 560 | // 现在仿件会克隆参数,因此 identicalTo 约束会失败。 561 | } 562 | } 563 | 564 | :ref:`appendixes.assertions.assertThat.tables.constraints`\ 中列出了可以应用于方法参数的各种约束,:numref:`test-doubles.mock-objects.tables.matchers` 中列出了可以用于指定调用次数的各种匹配器。 565 | 566 | .. rst-class:: table 567 | .. list-table:: 匹配器 568 | :name: test-doubles.mock-objects.tables.matchers 569 | :header-rows: 1 570 | 571 | * - 匹配器 572 | - 含义 573 | * - ``PHPUnit\Framework\MockObject\Matcher\AnyInvokedCount any()`` 574 | - 返回一个匹配器,当被评定的方法执行0次或更多次(即任意次数)时匹配成功。 575 | * - ``PHPUnit\Framework\MockObject\Matcher\InvokedCount never()`` 576 | - 返回一个匹配器,当被评定的方法从未执行时匹配成功。 577 | * - ``PHPUnit\Framework\MockObject\Matcher\InvokedAtLeastOnce atLeastOnce()`` 578 | - 返回一个匹配器,当被评定的方法执行至少一次时匹配成功。 579 | * - ``PHPUnit\Framework\MockObject\Matcher\InvokedCount once()`` 580 | - 返回一个匹配器,当被评定的方法执行恰好一次时匹配成功。 581 | * - ``PHPUnit\Framework\MockObject\Matcher\InvokedCount exactly(int $count)`` 582 | - 返回一个匹配器,当被评定的方法执行恰好 ``$count`` 次时匹配成功。 583 | * - ``PHPUnit\Framework\MockObject\Matcher\InvokedAtIndex at(int $index)`` 584 | - 返回一个匹配器,当被评定的方法是第 ``$index`` 个执行的方法时匹配成功。 585 | 586 | .. admonition:: 注 587 | 588 | ``at()`` 匹配器的 ``$index`` 参数指的是对给定仿件对象的\ *所有方法的调用*\ 的索引,从零开始。使用这个匹配器要谨慎,因为它可能导致测试由于与具体的实现细节过分紧密绑定而变得脆弱。 589 | 590 | 如一开始提到的,如果 ``createStub()`` 和 ``createMock()`` 方法在生成测试替身时所使用的默认值不符合你的要求,则可以通过 ``getMockBuilder($type)`` 方法来用流畅式接口定制测试替身的生成过程。以下是仿件生成器所提供的方法列表: 591 | 592 | - 593 | 594 | ``setMethods(array $methods)`` 可以在仿件生成器对象上调用,来指定哪些方法将被替换为可配置的测试替身。其他方法的行为不会有所改变。如果调用 ``setMethods(null)``,那么没有方法会被替换。 595 | 596 | - 597 | 598 | 可以在仿件生成器对象上调用 ``setMethodsExcept(array $methods)`` 来指定哪些方法不被替换为可配置的测试替身,与此同时所有其他 public 方法都会被替换。``setMethods()`` 的作用则相反。 599 | 600 | - 601 | 602 | ``setConstructorArgs(array $args)`` 可用于向原版类的构造函数(默认情况下不会被替换为伪实现)提供参数数组。 603 | 604 | - 605 | 606 | ``setMockClassName($name)`` 可用于指定生成的测试替身类的类名。 607 | 608 | - 609 | 610 | ``disableOriginalConstructor()`` 参数可用于禁用对原版类的构造方法的调用。 611 | 612 | - 613 | 614 | ``disableOriginalClone()`` 可用于禁用对原版类的克隆方法的调用。 615 | 616 | - 617 | 618 | ``disableAutoload()`` 可用于在测试替身类的生成期间禁用 ``__autoload()``。 619 | 620 | .. _test-doubles.mocking-traits-and-abstract-classes: 621 | 622 | 对特质(Trait)与抽象类进行模仿 623 | ################################### 624 | 625 | ``getMockForTrait()`` 方法返回一个使用了特定特质(trait)的仿件对象。给定特质的所有抽象方法将都被模仿。这样就能对特质的具体方法进行测试。 626 | 627 | .. code-block:: php 628 | :caption: 测试特质的具体方法 629 | :name: test-doubles.mock-objects.examples.TraitClassTest.php 630 | 631 | abstractMethod(); 639 | } 640 | 641 | public abstract function abstractMethod(); 642 | } 643 | 644 | final class TraitClassTest extends TestCase 645 | { 646 | public function testConcreteMethod(): void 647 | { 648 | $mock = $this->getMockForTrait(AbstractTrait::class); 649 | 650 | $mock->expects($this->any()) 651 | ->method('abstractMethod') 652 | ->will($this->returnValue(true)); 653 | 654 | $this->assertTrue($mock->concreteMethod()); 655 | } 656 | } 657 | 658 | ``getMockForAbstractClass()`` 方法返回一个抽象类的仿件对象。给定抽象类的所有抽象方法将都被模仿。这样就能对抽象类的具体方法进行测试。 659 | 660 | .. code-block:: php 661 | :caption: 测试抽象类的具体方法 662 | :name: test-doubles.mock-objects.examples.AbstractClassTest.php 663 | 664 | abstractMethod(); 672 | } 673 | 674 | public abstract function abstractMethod(); 675 | } 676 | 677 | final class AbstractClassTest extends TestCase 678 | { 679 | public function testConcreteMethod(): void 680 | { 681 | $stub = $this->getMockForAbstractClass(AbstractClass::class); 682 | 683 | $stub->expects($this->any()) 684 | ->method('abstractMethod') 685 | ->will($this->returnValue(true)); 686 | 687 | $this->assertTrue($stub->concreteMethod()); 688 | } 689 | } 690 | 691 | .. _test-doubles.stubbing-and-mocking-web-services: 692 | 693 | 对 Web 服务(Web Services)进行上桩或模仿 694 | ########################################### 695 | 696 | 当应用程序需要和 web 服务进行交互时,会想要在不与 web 服务进行实际交互的情况下对其进行测试。为了给 web 服务创建桩件或仿件,可以像使用 ``getMock()``\ (见上文)那样使用 ``getMockFromWsdl()``\ 。唯一的区别是 ``getMockFromWsdl()`` 所返回的桩件或者仿件是基于以 WSDL 描述的 web 服务,而 ``getMock()`` 返回的桩件或者仿件是基于 PHP 类或接口的。 697 | 698 | :numref:`test-doubles.stubbing-and-mocking-web-services.examples.GoogleTest.php` 展示了如何用 ``getMockFromWsdl()`` 来对(例如)\ :file:`GoogleSearch.wsdl` 中描述的 web 服务上桩。 699 | 700 | .. code-block:: php 701 | :caption: 给 web 服务上桩 702 | :name: test-doubles.stubbing-and-mocking-web-services.examples.GoogleTest.php 703 | 704 | getMockFromWsdl( 712 | 'GoogleSearch.wsdl', 'GoogleSearch' 713 | ); 714 | 715 | $directoryCategory = new stdClass; 716 | $directoryCategory->fullViewableName = ''; 717 | $directoryCategory->specialEncoding = ''; 718 | 719 | $element = new stdClass; 720 | $element->summary = ''; 721 | $element->URL = 'https://phpunit.de/'; 722 | $element->snippet = '...'; 723 | $element->title = 'PHPUnit'; 724 | $element->cachedSize = '11k'; 725 | $element->relatedInformationPresent = true; 726 | $element->hostName = 'phpunit.de'; 727 | $element->directoryCategory = $directoryCategory; 728 | $element->directoryTitle = ''; 729 | 730 | $result = new stdClass; 731 | $result->documentFiltering = false; 732 | $result->searchComments = ''; 733 | $result->estimatedTotalResultsCount = 3.9000; 734 | $result->estimateIsExact = false; 735 | $result->resultElements = [$element]; 736 | $result->searchQuery = 'PHPUnit'; 737 | $result->startIndex = 1; 738 | $result->endIndex = 1; 739 | $result->searchTips = ''; 740 | $result->directoryCategories = []; 741 | $result->searchTime = 0.248822; 742 | 743 | $googleSearch->expects($this->any()) 744 | ->method('doGoogleSearch') 745 | ->will($this->returnValue($result)); 746 | 747 | /** 748 | * $googleSearch->doGoogleSearch() 现在会返回桩结果, 749 | * 并不会调用 web 服务的 doGoogleSearch() 方法。 750 | */ 751 | $this->assertEquals( 752 | $result, 753 | $googleSearch->doGoogleSearch( 754 | '00000000000000000000000000000000', 755 | 'PHPUnit', 756 | 0, 757 | 1, 758 | false, 759 | '', 760 | false, 761 | '', 762 | '', 763 | '' 764 | ) 765 | ); 766 | } 767 | } 768 | -------------------------------------------------------------------------------- /src/textui.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. _textui: 4 | 5 | ============================ 6 | 命令行测试执行器 7 | ============================ 8 | 9 | PHPUnit 命令行测试执行器可通过 :file:`phpunit` 命令调用。下面的代码展示了如何用 PHPUnit 命令行测试执行器来运行测试: 10 | 11 | .. parsed-literal:: 12 | 13 | $ phpunit ArrayTest 14 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 15 | 16 | .. 17 | 18 | Time: 0 seconds 19 | 20 | OK (2 tests, 2 assertions) 21 | 22 | 上面这个调用例子中,PHPUnit 命令行测试执行器将在当前工作目录中寻找 :file:`ArrayTest.php` 源文件并加载之。而在此源文件中应当能找到 ``ArrayTest`` 测试用例类,此类中的测试将被执行。 23 | 24 | 对于每个测试的运行,PHPUnit 命令行工具输出一个字符来指示进展: 25 | 26 | ``.`` 27 | 28 | 当测试成功时输出。 29 | 30 | ``F`` 31 | 32 | 当测试方法运行过程中一个断言失败时输出。 33 | 34 | ``E`` 35 | 36 | 当测试方法运行过程中产生一个错误时输出。 37 | 38 | ``R`` 39 | 40 | 当测试被标记为有风险时输出(参见\ :ref:`risky-tests`)。 41 | 42 | ``S`` 43 | 44 | 当测试被跳过时输出(参见\ :ref:`incomplete-and-skipped-tests`)。 45 | 46 | ``I`` 47 | 48 | 当测试被标记为不完整或未实现时输出(参见\ :ref:`incomplete-and-skipped-tests`)。 49 | 50 | PHPUnit 区分\ *失败(failure)*\ 与\ *错误(error)*\ 。失败指的是被违背了的 PHPUnit 断言,例如一个失败的 ``assertSame()`` 调用。错误指的是意料之外的异常(exception)或 PHP 错误。这种差异已被证明在某些时候是非常有用的,因为错误往往比失败更容易修复。如果得到了一个非常长的问题列表,那么最好先对付错误,当错误全部修复了之后再试一次瞧瞧还有没有失败。 51 | 52 | .. _textui.clioptions: 53 | 54 | 命令行选项 55 | #################### 56 | 57 | 让我们来瞧瞧以下代码中命令行测试执行器的各种选项: 58 | 59 | .. parsed-literal:: 60 | 61 | $ phpunit --help 62 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 63 | 64 | Usage: 65 | phpunit [options] UnitTest.php 66 | phpunit [options] 67 | 68 | Code Coverage Options: 69 | --coverage-clover Generate code coverage report in Clover XML format 70 | --coverage-crap4j Generate code coverage report in Crap4J XML format 71 | --coverage-html Generate code coverage report in HTML format 72 | --coverage-php Export PHP_CodeCoverage object to file 73 | --coverage-text Generate code coverage report in text format [default: standard output] 74 | --coverage-xml Generate code coverage report in PHPUnit XML format 75 | --coverage-cache Cache static analysis results 76 | --warm-coverage-cache Warm static analysis cache 77 | --coverage-filter Include in code coverage analysis 78 | --path-coverage Perform path coverage analysis 79 | --disable-coverage-ignore Disable annotations for ignoring code coverage 80 | --no-coverage Ignore code coverage configuration 81 | 82 | Logging Options: 83 | --log-junit Log test execution in JUnit XML format to file 84 | --log-teamcity Log test execution in TeamCity format to file 85 | --testdox-html Write agile documentation in HTML format to file 86 | --testdox-text Write agile documentation in Text format to file 87 | --testdox-xml Write agile documentation in XML format to file 88 | --reverse-list Print defects in reverse order 89 | --no-logging Ignore logging configuration 90 | 91 | Test Selection Options: 92 | --filter Filter which tests to run 93 | --testsuite Filter which testsuite to run 94 | --group Only runs tests from the specified group(s) 95 | --exclude-group Exclude tests from the specified group(s) 96 | --list-groups List available test groups 97 | --list-suites List available test suites 98 | --list-tests List available tests 99 | --list-tests-xml List available tests in XML format 100 | --test-suffix Only search for test in files with specified suffix(es). Default: Test.php,.phpt 101 | 102 | Test Execution Options: 103 | --dont-report-useless-tests Do not report tests that do not test anything 104 | --strict-coverage Be strict about @covers annotation usage 105 | --strict-global-state Be strict about changes to global state 106 | --disallow-test-output Be strict about output during tests 107 | --disallow-resource-usage Be strict about resource usage during small tests 108 | --enforce-time-limit Enforce time limit based on test size 109 | --default-time-limit Timeout in seconds for tests without @small, @medium or @large 110 | --disallow-todo-tests Disallow @todo-annotated tests 111 | 112 | --process-isolation Run each test in a separate PHP process 113 | --globals-backup Backup and restore $GLOBALS for each test 114 | --static-backup Backup and restore static attributes for each test 115 | 116 | --colors Use colors in output ("never", "auto" or "always") 117 | --columns Number of columns to use for progress output 118 | --columns max Use maximum number of columns for progress output 119 | --stderr Write to STDERR instead of STDOUT 120 | --stop-on-defect Stop execution upon first not-passed test 121 | --stop-on-error Stop execution upon first error 122 | --stop-on-failure Stop execution upon first error or failure 123 | --stop-on-warning Stop execution upon first warning 124 | --stop-on-risky Stop execution upon first risky test 125 | --stop-on-skipped Stop execution upon first skipped test 126 | --stop-on-incomplete Stop execution upon first incomplete test 127 | --fail-on-incomplete Treat incomplete tests as failures 128 | --fail-on-risky Treat risky tests as failures 129 | --fail-on-skipped Treat skipped tests as failures 130 | --fail-on-warning Treat tests with warnings as failures 131 | -v|--verbose Output more verbose information 132 | --debug Display debugging information 133 | 134 | --repeat Runs the test(s) repeatedly 135 | --teamcity Report test execution progress in TeamCity format 136 | --testdox Report test execution progress in TestDox format 137 | --testdox-group Only include tests from the specified group(s) 138 | --testdox-exclude-group Exclude tests from the specified group(s) 139 | --no-interaction Disable TestDox progress animation 140 | --printer TestListener implementation to use 141 | 142 | --order-by Run tests in order: default|defects|duration|no-depends|random|reverse|size 143 | --random-order-seed Use a specific random seed for random order 144 | --cache-result Write test results to cache file 145 | --do-not-cache-result Do not write test results to cache file 146 | 147 | Configuration Options: 148 | --prepend A PHP script that is included as early as possible 149 | --bootstrap A PHP script that is included before the tests run 150 | -c|--configuration Read configuration from XML file 151 | --no-configuration Ignore default configuration file (phpunit.xml) 152 | --extensions A comma separated list of PHPUnit extensions to load 153 | --no-extensions Do not load PHPUnit extensions 154 | --include-path Prepend PHP's include_path with given path(s) 155 | -d Sets a php.ini value 156 | --cache-result-file Specify result cache path and filename 157 | --generate-configuration Generate configuration file with suggested settings 158 | --migrate-configuration Migrate configuration file to current format 159 | 160 | Miscellaneous Options: 161 | -h|--help Prints this usage information 162 | --version Prints the version and exits 163 | --atleast-version Checks that version is greater than min and exits 164 | --check-version Check whether PHPUnit is the latest version 165 | 166 | ``phpunit UnitTest`` 167 | 168 | 运行由 ``UnitTest`` 类提供的测试。这个类应当在 :file:`UnitTest.php` 源文件中声明。 169 | 170 | ``UnitTest`` 这个类必须满足以下二个条件之一:要么它继承自 ``PHPUnit\Framework\TestCase``\ ;要么它提供 ``public static suite()`` 方法,这个方法返回一个 ``PHPUnit\Framework\Test`` 对象,比如,一个 ``PHPUnit\Framework\TestSuite`` 类的实例。 171 | 172 | ``phpunit UnitTest UnitTest.php`` 173 | 174 | 运行由 ``UnitTest`` 类提供的测试。这个类应当在指定的源文件中声明。 175 | 176 | ``--coverage-clover`` 177 | 178 | 为运行的测试生成带有代码覆盖率信息的 XML 格式的日志文件。更多细节参见\ :ref:`code-coverage-analysis`\ 。 179 | 180 | ``--coverage-crap4j`` 181 | 182 | 生成 Crap4j 格式的代码覆盖率报告。更多细节请参见\ :ref:`code-coverage-analysis`\ 。 183 | 184 | ``--coverage-html`` 185 | 186 | 生成 HTML 格式的代码覆盖率报告。更多细节请参见\ :ref:`code-coverage-analysis`\ 。 187 | 188 | ``--coverage-php`` 189 | 190 | 生成一个序列化后的 PHP_CodeCoverage 对象,此对象含有代码覆盖率信息。 191 | 192 | ``--coverage-text`` 193 | 194 | 为运行的测试以人们可读的格式生成带有代码覆盖率信息的日志文件或命令行输出。 195 | 196 | ``--log-junit`` 197 | 198 | 为运行的测试生成 JUnit XML 格式的日志文件。 199 | 200 | ``--testdox-html`` 和 ``--testdox-text`` 201 | 202 | 为运行的测试以 HTML 或纯文本格式生成敏捷文档(参见 :ref:`textui.testdox`\ )。 203 | 204 | ``--filter`` 205 | 206 | 只运行名称与给定模式匹配的测试。如果模式未用定界符包住,PHPUnit 将用 ``/`` 定界符来将其包住。 207 | 208 | 测试名称将以以下格式之一进行匹配: 209 | 210 | ``TestNamespace\TestCaseClass::testMethod`` 211 | 212 | 默认的测试名称格式等价于在测试方法内使用 ``__METHOD__`` 魔术常量。 213 | 214 | ``TestNamespace\TestCaseClass::testMethod with data set #0`` 215 | 216 | 当测试拥有数据供给器时,数据的每轮迭代都会将其当前索引附加在默认测试名称结尾处。 217 | 218 | ``TestNamespace\TestCaseClass::testMethod with data set "my named data"`` 219 | 220 | 当测试拥有使用命名数据集的数据供给器时,数据的每轮迭代都会将当前名称附加在默认测试名称结尾处。命名数据集的例子参见\ :numref:`textui.examples.TestCaseClass.php`。 221 | 222 | .. code-block:: php 223 | :caption: 命名数据集 224 | :name: textui.examples.TestCaseClass.php 225 | 226 | assertTrue($data); 239 | } 240 | 241 | public function provider() 242 | { 243 | return [ 244 | 'my named data' => [true], 245 | 'my data' => [true] 246 | ]; 247 | } 248 | } 249 | 250 | ``/path/to/my/test.phpt`` 251 | 252 | 对于 PHPT 测试,其测试名称是文件系统路径。 253 | 254 | 有效的过滤器模式例子参见\ :numref:`textui.examples.filter-patterns`\ 。 255 | 256 | .. code-block:: shell 257 | :caption: 过滤器模式示例 258 | :name: textui.examples.filter-patterns 259 | 260 | --filter 'TestNamespace\\TestCaseClass::testMethod' 261 | --filter 'TestNamespace\\TestCaseClass' 262 | --filter TestNamespace 263 | --filter TestCaseClase 264 | --filter testMethod 265 | --filter '/::testMethod .*"my named data"/' 266 | --filter '/::testMethod .*#5$/' 267 | --filter '/::testMethod .*#(5|6|7)$/' 268 | 269 | 在匹配数据供给器时有一些额外的快捷方式,参见\ :numref:`textui.examples.filter-shortcuts`\ 。 270 | 271 | .. code-block:: shell 272 | :caption: 过滤器快捷方式 273 | :name: textui.examples.filter-shortcuts 274 | 275 | --filter 'testMethod#2' 276 | --filter 'testMethod#2-4' 277 | --filter '#2' 278 | --filter '#2-4' 279 | --filter 'testMethod@my named data' 280 | --filter 'testMethod@my.*data' 281 | --filter '@my named data' 282 | --filter '@my.*data' 283 | 284 | ``--testsuite`` 285 | 286 | 只运行名称与给定模式匹配的测试套件。 287 | 288 | ``--group`` 289 | 290 | 只运行来自指定分组(可以多个)的测试。可以用 ``@group`` 标注为测试标记其所属的分组。 291 | 292 | ``@author`` 和 ``@ticket`` 标注都是 ``@group`` 的别名,分别允许基于作者和事务 ID 筛选测试。 293 | 294 | ``--exclude-group`` 295 | 296 | 排除来自指定分组(可以多个)的测试。可以用 ``@group`` 标注为测试标记其所属的分组。 297 | 298 | ``--list-groups`` 299 | 300 | 列出所有有效的测试分组。 301 | 302 | ``--test-suffix`` 303 | 304 | 只查找文件名以指定后缀(可以多个)结尾的测试文件。 305 | 306 | ``--dont-report-useless-tests`` 307 | 308 | 不报告事实上不测试任何内容的测试。详情参见\ :ref:`risky-tests`\ 。 309 | 310 | ``--strict-coverage`` 311 | 312 | 更严格对待意外的代码覆盖。详情参见\ :ref:`risky-tests`\ 。 313 | 314 | ``--strict-global-state`` 315 | 316 | 更严格对待全局状态篡改。详情参见\ :ref:`risky-tests`\ 。 317 | 318 | ``--disallow-test-output`` 319 | 320 | 更严格对待测试执行期间产生的输出。详情参见\ :ref:`risky-tests`\ 。 321 | 322 | ``--disallow-todo-tests`` 323 | 324 | 不执行文档注释块中含有 ``@todo`` 标注的测试。 325 | 326 | ``--enforce-time-limit`` 327 | 328 | 根据测试规模对其加上执行时长限制。详情参见\ :ref:`risky-tests`\ 。 329 | 330 | ``--process-isolation`` 331 | 332 | 每个测试都在独立的 PHP 进程中运行。 333 | 334 | ``--no-globals-backup`` 335 | 336 | 不要备份与还原 $GLOBALS。更多细节请参见\ :ref:`fixtures.global-state`\ 。 337 | 338 | ``--static-backup`` 339 | 340 | 备份与还原用户定义的类中的静态属性。更多细节请参见\ :ref:`fixtures.global-state`\ 。 341 | 342 | ``--colors`` 343 | 344 | 使用彩色输出。在 Windows 上,用 `ANSICON `_ 或 `ConEmu `_\ 。 345 | 346 | 本选项有三个可能的值: 347 | 348 | - 349 | 350 | ``never``\ :完全不使用彩色输出。当未使用 ``--colors`` 选项时,这是默认值。 351 | 352 | - 353 | 354 | ``auto``:如果当前终端不支持彩色、或者输出被管道输出至其他命令、或输出被重定向至文件时,不使用彩色输出,其余情况使用彩色。 355 | 356 | - 357 | 358 | ``always``:总是使用彩色输出,即使当前终端不支持彩色、输出被管道输出至其他命令、或输出被重定向至文件。 359 | 360 | 当使用了 ``--colors`` 选项但未指定任何值时,将选择 ``auto`` 做为其值。 361 | 362 | ``--columns`` 363 | 364 | 定义输出所使用的列数。如果将其值定义为 ``max``,则使用当前终端所支持的最大列数。 365 | 366 | ``--stderr`` 367 | 368 | 选择输出到 ``STDERR`` 而非 ``STDOUT``\ 。 369 | 370 | ``--stop-on-error`` 371 | 372 | 首次错误出现后停止执行。 373 | 374 | ``--stop-on-failure`` 375 | 376 | 首次错误或失败出现后停止执行。 377 | 378 | ``--stop-on-risky`` 379 | 380 | 首次碰到有风险的测试时停止执行。 381 | 382 | ``--stop-on-skipped`` 383 | 384 | 首次碰到跳过的测试时停止执行。 385 | 386 | ``--stop-on-incomplete`` 387 | 388 | 首次碰到不完整的测试时停止执行。 389 | 390 | ``--verbose`` 391 | 392 | 输出更详尽的信息,例如不完整或者跳过的测试的名称。 393 | 394 | ``--debug`` 395 | 396 | 输出调试信息,例如当一个测试开始执行时输出其名称。 397 | 398 | ``--loader`` 399 | 400 | 指定要使用的 ``PHPUnit\Runner\TestSuiteLoader`` 实现。 401 | 402 | 标准的测试套件加载器将在当前工作目录和 PHP 的 ``include_path`` 配置指令中指定的每个目录内查找源文件。诸如 ``Project_Package_Class`` 这样的类名对应的源文件名为 :file:`Project/Package/Class.php`\ 。 403 | 404 | ``--repeat`` 405 | 406 | 将测试重复运行指定次数。 407 | 408 | ``--testdox`` 409 | 410 | 以 TestDox 格式报告测试进度。(参见 :ref:`textui.testdox`\ )。 411 | 412 | ``--printer`` 413 | 414 | 指定要使用的结果输出器(printer)。输出器类必须扩展 ``PHPUnit\Util\Printer`` 并且实现 ``PHPUnit\Framework\TestListener`` 接口。 415 | 416 | ``--bootstrap`` 417 | 418 | 在测试前先运行一个 "bootstrap" PHP 文件。 419 | 420 | ``--configuration``\ 、\ ``-c`` 421 | 422 | 从 XML 文件中读取配置信息。更多细节请参见 :ref:`appendixes.configuration`\ 。 423 | 424 | 如果 :file:`phpunit.xml` 或 :file:`phpunit.xml.dist`\ (按此顺序)存在于当前工作目录并且\ *未*\ 使用 ``--configuration``\ ,将自动从此文件中读取配置。 425 | 426 | 如果指定了目录且在此目录中存在 :file:`phpunit.xml` 或 :file:`phpunit.xml.dist`\ (按此顺序)将自动从此文件中读取配置。 427 | 428 | ``--no-configuration`` 429 | 430 | 忽略当前工作目录下的 :file:`phpunit.xml` 与 :file:`phpunit.xml.dist`。 431 | 432 | ``--include-path`` 433 | 434 | 向 PHP 的 `include_path` 开头添加指定路径(可以多个)。 435 | 436 | ``-d`` 437 | 438 | 设置指定的 PHP 配置选项的值。 439 | 440 | .. admonition:: 注 441 | 442 | 请注意,选项不能放在参数之后。 443 | 444 | .. _textui.testdox: 445 | 446 | TestDox 447 | ####### 448 | 449 | PHPUnit 的 TestDox 功能着眼于测试类及其所有测试方法的名称,将它们驼峰式大小写(camel case)(或蛇式大小写(snake_case))拼写的 PHP 名称转换为句子:\ ``testBalanceIsInitiallyZero()``\ (或 ``test_balance_is_initially_zero()``\ )转化为“Balance is initially zero”。如果有多个测试方法的名字互相之间的差异只是一个或多个数字的后缀,例如 ``testBalanceCannotBecomeNegative()`` 和 ``testBalanceCannotBecomeNegative2()``,假如所有这些测试都成功,句子“Balance cannot become negative”只会出现一次。 450 | 451 | 来看一下从 ``BankAccount`` 类生成的敏捷文档: 452 | 453 | .. parsed-literal:: 454 | 455 | $ phpunit --testdox BankAccountTest.php 456 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 457 | 458 | BankAccount 459 | ✔ Balance is initially zero 460 | ✔ Balance cannot become negative 461 | 462 | 另外,敏捷文档也能以 HTML 或纯文本格式生成并写入文件中,用 ``--testdox-html`` 和 ``--testdox-text`` 参数即可。 463 | 464 | 敏捷文档可以用于将对项目所使用的外部包所做出的假设文档化。使用外部包,你就暴露于这个包的行为与你所预期的不同的风险中,并且包的未来版本可能在你所不知道的情况下有微妙的改变并破坏你的代码。每次做出假设时就编写一个对应的测试可以处理这些风险。如果测试成功,那么假设就有效。如果所有的假设都通过测试来文档化,外部包在未来发布新版本就不会引起忧虑:如果测试成功,那么系统就应当能继续正常运作。 465 | 466 | -------------------------------------------------------------------------------- /src/writing-tests-for-phpunit.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | .. _writing-tests-for-phpunit: 4 | 5 | ========================= 6 | 编写 PHPUnit 测试 7 | ========================= 8 | 9 | :numref:`writing-tests-for-phpunit.examples.StackTest.php` 展示了如何用 PHPUnit 编写测试来对 PHP 数组操作进行测试。此示例介绍了用 PHPUnit 编写测试的基本惯例与步骤: 10 | 11 | #. 12 | 13 | 针对类 ``Class`` 的测试写在类 ``ClassTest`` 中。 14 | 15 | #. 16 | 17 | ``ClassTest``\ (通常)继承自 ``PHPUnit\Framework\TestCase``。 18 | 19 | #. 20 | 21 | 测试都是命名为 ``test*`` 的公用方法。 22 | 23 | 也可以在方法的文档注释块(docblock)中使用 ``@test`` 标注将其标记为测试方法。 24 | 25 | #. 26 | 27 | 在测试方法内,类似于 ``assertSame()``\ (参见\ :ref:`appendixes.assertions`)这样的断言方法用来对实际值与预期值的匹配做出断言。 28 | 29 | .. code-block:: php 30 | :caption: 用 PHPUnit 测试数组操作 31 | :name: writing-tests-for-phpunit.examples.StackTest.php 32 | 33 | assertSame(0, count($stack)); 42 | 43 | array_push($stack, 'foo'); 44 | $this->assertSame('foo', $stack[count($stack)-1]); 45 | $this->assertSame(1, count($stack)); 46 | 47 | $this->assertSame('foo', array_pop($stack)); 48 | $this->assertSame(0, count($stack)); 49 | } 50 | } 51 | 52 | | 53 | *Martin Fowler*: 54 | 55 | 当你想把一些东西写到 ``print`` 语句或者调试表达式中时,别这么做,改为将其写成测试。 56 | 57 | .. _writing-tests-for-phpunit.test-dependencies: 58 | 59 | 测试的依赖关系 60 | ################# 61 | 62 | *Adrian Kuhn et. al.*: 63 | 64 | 单元测试主要是作为一种良好实践来编写的,它能帮助开发人员识别并修复 bug、重构代码,还可以看作被测软件单元的文档。要实现这些好处,理想的单元测试应当覆盖程序中所有可能的路径。一个单元测试通常覆盖一个函数或方法中的一个特定路径。但是,测试方法不一定是封装良好的独立实体。测试方法之间经常有隐含的依赖关系暗藏在测试的实现方案中。 65 | 66 | PHPUnit支持对测试方法之间的显式依赖关系进行声明。这种依赖关系并不是定义在测试方法的执行顺序中,而是允许生产者(producer)返回一个测试基境(fixture)的实例,并将此实例传递给依赖于它的消费者(consumer)们。 67 | 68 | - 69 | 70 | 生产者(producer),是能生成被测单元并将其作为返回值的测试方法。 71 | 72 | - 73 | 74 | 消费者(consumer),是依赖于一个或多个生产者及其返回值的测试方法。 75 | 76 | :numref:`writing-tests-for-phpunit.examples.StackTest2.php` 展示了如何用 ``@depends`` 标注来表示测试方法之间的依赖关系。 77 | 78 | .. code-block:: php 79 | :caption: 用 ``@depends`` 标注来表示依赖关系 80 | :name: writing-tests-for-phpunit.examples.StackTest2.php 81 | 82 | assertEmpty($stack); 91 | 92 | return $stack; 93 | } 94 | 95 | /** 96 | * @depends testEmpty 97 | */ 98 | public function testPush(array $stack): array 99 | { 100 | array_push($stack, 'foo'); 101 | $this->assertSame('foo', $stack[count($stack)-1]); 102 | $this->assertNotEmpty($stack); 103 | 104 | return $stack; 105 | } 106 | 107 | /** 108 | * @depends testPush 109 | */ 110 | public function testPop(array $stack): void 111 | { 112 | $this->assertSame('foo', array_pop($stack)); 113 | $this->assertEmpty($stack); 114 | } 115 | } 116 | 117 | 在上例中,第一个测试\ ``testEmpty()`` 创建了一个新数组,并断言其为空。随后,此测试将此基境作为结果返回。第二个测试 ``testPush()`` 依赖于 ``testEmpty()``,并将所依赖的测试之结果作为参数传入。最后,``testPop()`` 依赖于 ``testPush()``。 118 | 119 | .. admonition:: 注 120 | 121 | 默认情况下,生产者所产生的返回值将“原样”传递给相应的消费者。这意味着,如果生产者返回的是一个对象,那么传递给消费者的将是指向此对象的引用。但同样也可以(a)通过 ``@depends clone`` 来传递指向(深)拷贝对象的引用,或(b)通过 ``@depends shallowClone`` 来传递指向(正常浅)克隆对象(基于 PHP 关键字 ``clone``)的引用。 122 | 123 | 为了定位缺陷,我们希望把注意力集中于相关的失败测试上。这就是为什么当某个测试所依赖的测试失败时,PHPUnit 会跳过这个测试。利用测试之间的依赖关系可以改进缺陷定位,如\ :numref:`writing-tests-for-phpunit.examples.DependencyFailureTest.php` 所示。 124 | 125 | .. code-block:: php 126 | :caption: 利用测试之间的依赖关系 127 | :name: writing-tests-for-phpunit.examples.DependencyFailureTest.php 128 | 129 | assertTrue(false); 137 | } 138 | 139 | /** 140 | * @depends testOne 141 | */ 142 | public function testTwo(): void 143 | { 144 | } 145 | } 146 | 147 | .. parsed-literal:: 148 | 149 | $ phpunit --verbose DependencyFailureTest 150 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 151 | 152 | FS 153 | 154 | Time: 0 seconds, Memory: 5.00Mb 155 | 156 | There was 1 failure: 157 | 158 | 1) DependencyFailureTest::testOne 159 | Failed asserting that false is true. 160 | 161 | /home/sb/DependencyFailureTest.php:6 162 | 163 | There was 1 skipped test: 164 | 165 | 1) DependencyFailureTest::testTwo 166 | This test depends on "DependencyFailureTest::testOne" to pass. 167 | 168 | FAILURES! 169 | Tests: 1, Assertions: 1, Failures: 1, Skipped: 1. 170 | 171 | 测试可以使用多个 ``@depends`` 标注。PHPUnit 不会更改测试的运行顺序,因此你需要自行保证某个测试所依赖的所有测试均出现于这个测试之前。 172 | 173 | 拥有多个 ``@depends`` 标注的测试,其第一个参数是第一个生产者提供的基境,第二个参数是第二个生产者提供的基境,以此类推。参见\ :numref:`writing-tests-for-phpunit.examples.MultipleDependencies.php` 174 | 175 | .. code-block:: php 176 | :caption: 有多个依赖项的测试 177 | :name: writing-tests-for-phpunit.examples.MultipleDependencies.php 178 | 179 | assertTrue(true); 187 | 188 | return 'first'; 189 | } 190 | 191 | public function testProducerSecond(): string 192 | { 193 | $this->assertTrue(true); 194 | 195 | return 'second'; 196 | } 197 | 198 | /** 199 | * @depends testProducerFirst 200 | * @depends testProducerSecond 201 | */ 202 | public function testConsumer(string $a, string $b): void 203 | { 204 | $this->assertSame('first', $a); 205 | $this->assertSame('second', $b); 206 | } 207 | } 208 | 209 | .. parsed-literal:: 210 | 211 | $ phpunit --verbose MultipleDependenciesTest 212 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 213 | 214 | ... 215 | 216 | Time: 0 seconds, Memory: 3.25Mb 217 | 218 | OK (3 tests, 4 assertions) 219 | 220 | .. _writing-tests-for-phpunit.data-providers: 221 | 222 | 数据供给器 223 | ############## 224 | 225 | 测试方法可以接受任意参数。这些参数由一个或多个数据供给器方法(在\ :numref:`writing-tests-for-phpunit.data-providers.examples.DataTest.php` 中,是 ``additionProvider()`` 方法)提供。用 ``@dataProvider`` 标注来指定要使用的数据供给器方法。 226 | 227 | 数据供给器方法必须声明为 ``public``,其返回值要么是一个数组,其每个元素也是数组;要么是一个实现了 ``Iterator`` 接口的对象,在对它进行迭代时每步产生一个数组。每个数组都是测试数据集的一部分,将以它的内容作为参数来调用测试方法。 228 | 229 | .. code-block:: php 230 | :caption: 使用返回数组的数组的数据供给器 231 | :name: writing-tests-for-phpunit.data-providers.examples.DataTest.php 232 | 233 | assertSame($expected, $a + $b); 244 | } 245 | 246 | public function additionProvider(): array 247 | { 248 | return [ 249 | [0, 0, 0], 250 | [0, 1, 1], 251 | [1, 0, 1], 252 | [1, 1, 3] 253 | ]; 254 | } 255 | } 256 | 257 | .. parsed-literal:: 258 | 259 | $ phpunit DataTest 260 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 261 | 262 | ...F 263 | 264 | Time: 0 seconds, Memory: 5.75Mb 265 | 266 | There was 1 failure: 267 | 268 | 1) DataTest::testAdd with data set #3 (1, 1, 3) 269 | Failed asserting that 2 is identical to 3. 270 | 271 | /home/sb/DataTest.php:9 272 | 273 | FAILURES! 274 | Tests: 4, Assertions: 4, Failures: 1. 275 | 276 | 当使用到大量数据集时,最好逐个用字符串键名对其命名,避免用默认的数字键名。这样输出信息会更加详细些,其中将包含打断测试的数据集所对应的名称。 277 | 278 | .. code-block:: php 279 | :caption: 将数据供给器与命名数据集一起使用 280 | :name: writing-tests-for-phpunit.data-providers.examples.DataTest1.php 281 | 282 | assertSame($expected, $a + $b); 293 | } 294 | 295 | public function additionProvider(): array 296 | { 297 | return [ 298 | 'adding zeros' => [0, 0, 0], 299 | 'zero plus one' => [0, 1, 1], 300 | 'one plus zero' => [1, 0, 1], 301 | 'one plus one' => [1, 1, 3] 302 | ]; 303 | } 304 | } 305 | 306 | .. parsed-literal:: 307 | 308 | $ phpunit DataTest 309 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 310 | 311 | ...F 312 | 313 | Time: 0 seconds, Memory: 5.75Mb 314 | 315 | There was 1 failure: 316 | 317 | 1) DataTest::testAdd with data set "one plus one" (1, 1, 3) 318 | Failed asserting that 2 is identical to 3. 319 | 320 | /home/sb/DataTest.php:9 321 | 322 | FAILURES! 323 | Tests: 4, Assertions: 4, Failures: 1. 324 | 325 | .. code-block:: php 326 | :caption: 使用返回 Iterator 对象的数据供给器 327 | :name: writing-tests-for-phpunit.data-providers.examples.DataTest2.php 328 | 329 | assertSame($expected, $a + $b); 340 | } 341 | 342 | public function additionProvider(): CsvFileIterator 343 | { 344 | return new CsvFileIterator('data.csv'); 345 | } 346 | } 347 | 348 | .. parsed-literal:: 349 | 350 | $ phpunit DataTest 351 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 352 | 353 | ...F 354 | 355 | Time: 0 seconds, Memory: 5.75Mb 356 | 357 | There was 1 failure: 358 | 359 | 1) DataTest::testAdd with data set #3 ('1', '1', '3') 360 | Failed asserting that 2 is identical to 3. 361 | 362 | /home/sb/DataTest.php:11 363 | 364 | FAILURES! 365 | Tests: 4, Assertions: 4, Failures: 1. 366 | 367 | .. code-block:: php 368 | :caption: CsvFileIterator 类 369 | :name: writing-tests-for-phpunit.data-providers.examples.CsvFileIterator.php 370 | 371 | file = fopen($file, 'r'); 383 | } 384 | 385 | public function __destruct() 386 | { 387 | fclose($this->file); 388 | } 389 | 390 | public function rewind(): void 391 | { 392 | rewind($this->file); 393 | 394 | $this->current = fgetcsv($this->file); 395 | $this->key = 0; 396 | } 397 | 398 | public function valid(): bool 399 | { 400 | return !feof($this->file); 401 | } 402 | 403 | public function key(): int 404 | { 405 | return $this->key; 406 | } 407 | 408 | public function current(): array 409 | { 410 | return $this->current; 411 | } 412 | 413 | public function next(): void 414 | { 415 | $this->current = fgetcsv($this->file); 416 | 417 | $this->key++; 418 | } 419 | } 420 | 421 | 如果测试同时从 ``@dataProvider`` 方法和一个或多个 ``@depends`` 测试接收数据,那么来自于数据供给器的参数将先于来自所依赖的测试的。来自于所依赖的测试的参数对于每个数据集都是一样的。参见\ :numref:`writing-tests-for-phpunit.data-providers.examples.DependencyAndDataProviderCombo.php` 422 | 423 | .. code-block:: php 424 | :caption: 在同一个测试中组合 @depends 和 @dataProvider 425 | :name: writing-tests-for-phpunit.data-providers.examples.DependencyAndDataProviderCombo.php 426 | 427 | assertTrue(true); 440 | 441 | return 'first'; 442 | } 443 | 444 | public function testProducerSecond(): void 445 | { 446 | $this->assertTrue(true); 447 | 448 | return 'second'; 449 | } 450 | 451 | /** 452 | * @depends testProducerFirst 453 | * @depends testProducerSecond 454 | * @dataProvider provider 455 | */ 456 | public function testConsumer(): void 457 | { 458 | $this->assertSame( 459 | ['provider1', 'first', 'second'], 460 | func_get_args() 461 | ); 462 | } 463 | } 464 | 465 | .. parsed-literal:: 466 | 467 | $ phpunit --verbose DependencyAndDataProviderComboTest 468 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 469 | 470 | ...F 471 | 472 | Time: 0 seconds, Memory: 3.50Mb 473 | 474 | There was 1 failure: 475 | 476 | 1) DependencyAndDataProviderComboTest::testConsumer with data set #1 ('provider2') 477 | Failed asserting that two arrays are identical. 478 | --- Expected 479 | +++ Actual 480 | @@ @@ 481 | Array &0 ( 482 | - 0 => 'provider1' 483 | + 0 => 'provider2' 484 | 1 => 'first' 485 | 2 => 'second' 486 | ) 487 | /home/sb/DependencyAndDataProviderComboTest.php:32 488 | 489 | FAILURES! 490 | Tests: 4, Assertions: 4, Failures: 1. 491 | 492 | .. code-block:: php 493 | :caption: 对单个测试使用多个数据供给器 494 | :name: writing-tests-for-phpunit.data-providers.examples2.DataTest.php 495 | 496 | assertSame($expected, $a + $b); 508 | } 509 | 510 | public function additionWithNonNegativeNumbersProvider(): void 511 | { 512 | return [ 513 | [0, 1, 1], 514 | [1, 0, 1], 515 | [1, 1, 3] 516 | ]; 517 | } 518 | 519 | public function additionWithNegativeNumbersProvider(): array 520 | { 521 | return [ 522 | [-1, 1, 0], 523 | [-1, -1, -2], 524 | [1, -1, 0] 525 | ]; 526 | } 527 | } 528 | 529 | .. parsed-literal:: 530 | 531 | $ phpunit DataTest 532 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 533 | 534 | ..F... 6 / 6 (100%) 535 | 536 | Time: 0 seconds, Memory: 5.75Mb 537 | 538 | There was 1 failure: 539 | 540 | 1) DataTest::testAdd with data set #3 (1, 1, 3) 541 | Failed asserting that 2 is identical to 3. 542 | 543 | /home/sb/DataTest.php:12 544 | 545 | FAILURES! 546 | Tests: 6, Assertions: 6, Failures: 1. 547 | 548 | .. admonition:: 注 549 | 550 | 如果一个测试依赖于另外一个使用了数据供给器的测试,仅当被依赖的测试至少能在一组数据上成功时,依赖于它的测试才会运行。使用了数据供给器的测试,其运行结果是无法注入到依赖于此测试的其他测试中的。 551 | 552 | .. admonition:: 注 553 | 554 | 所有数据供给器方法的执行都是在对 ``setUpBeforeClass()`` 静态方法的调用和第一次对 ``setUp()`` 方法的调用之前完成的。因此,无法在数据供给器中使用创建于这两个方法内的变量。这是必须的,这样 PHPUnit 才能计算测试的总数量。 555 | 556 | .. _writing-tests-for-phpunit.exceptions: 557 | 558 | 对异常进行测试 559 | ################## 560 | 561 | :numref:`writing-tests-for-phpunit.exceptions.examples.ExceptionTest.php` 展示了如何用 ``@expectException`` 标注来测试被测代码中是否抛出了异常。 562 | 563 | .. code-block:: php 564 | :caption: 使用 expectException() 方法 565 | :name: writing-tests-for-phpunit.exceptions.examples.ExceptionTest.php 566 | 567 | expectException(InvalidArgumentException::class); 575 | } 576 | } 577 | 578 | .. parsed-literal:: 579 | 580 | $ phpunit ExceptionTest 581 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 582 | 583 | F 584 | 585 | Time: 0 seconds, Memory: 4.75Mb 586 | 587 | There was 1 failure: 588 | 589 | 1) ExceptionTest::testException 590 | Failed asserting that exception of type "InvalidArgumentException" is thrown. 591 | 592 | FAILURES! 593 | Tests: 1, Assertions: 1, Failures: 1. 594 | 595 | 除了 ``expectException()`` 方法外,还有 ``expectExceptionCode()``、``expectExceptionMessage()`` 和 ``expectExceptionMessageMatches()`` 方法可以用于为被测代码所抛出的异常建立预期。 596 | 597 | .. admonition:: 注 598 | 599 | 注意 ``expectExceptionMessage()`` 断言的是 ``$actual`` 讯息包含有 ``$expected`` 讯息,并不执行精确的字符串比较。 600 | 601 | .. _writing-tests-for-phpunit.errors: 602 | 603 | 对 PHP 错误、警告和通知进行测试 604 | ######################################### 605 | 606 | 默认情况下,PHPUnit 将测试在执行中触发的 PHP 错误、警告、通知都转换为异常。先不说其他好处,这样就可以预期在测试中会触发 PHP 错误、警告或通知,如\ :numref:`writing-tests-for-phpunit.exceptions.examples.ErrorTest.php` 所示。 607 | 608 | .. admonition:: 注 609 | 610 | PHP 的 ``error_reporting`` 运行时配置会对 PHPUnit 将哪些错误转换为异常有所限制。如果在这个特性上碰到问题,请确认 PHP 的配置中没有抑制你所关注的错误类型。 611 | 612 | .. code-block:: php 613 | :caption: 预期会出现 PHP 错误、警告和通知 614 | :name: writing-tests-for-phpunit.exceptions.examples.ErrorTest.php 615 | 616 | expectDeprecation(); 624 | 625 | // (可选)测试讯息和某个字符串相等 626 | $this->expectDeprecationMessage('foo'); 627 | 628 | // 或者(可选)测试讯息和某个正则表达式匹配 629 | $this->expectDeprecationMessageMatches('/foo/'); 630 | 631 | \trigger_error('foo', \E_USER_DEPRECATED); 632 | } 633 | 634 | public function testNoticeCanBeExpected(): void 635 | { 636 | $this->expectNotice(); 637 | 638 | // (可选)测试讯息和某个字符串相等 639 | $this->expectNoticeMessage('foo'); 640 | 641 | // 或者(可选)测试讯息和某个正则表达式匹配 642 | $this->expectNoticeMessageMatches('/foo/'); 643 | 644 | \trigger_error('foo', \E_USER_NOTICE); 645 | } 646 | 647 | public function testWarningCanBeExpected(): void 648 | { 649 | $this->expectWarning(); 650 | 651 | // (可选)测试讯息和某个字符串相等 652 | $this->expectWarningMessage('foo'); 653 | 654 | // 或者(可选)测试讯息和某个正则表达式匹配 655 | $this->expectWarningMessageMatches('/foo/'); 656 | 657 | \trigger_error('foo', \E_USER_WARNING); 658 | } 659 | 660 | public function testErrorCanBeExpected(): void 661 | { 662 | $this->expectError(); 663 | 664 | // (可选)测试讯息和某个字符串相等 665 | $this->expectErrorMessage('foo'); 666 | 667 | // 或者(可选)测试讯息和某个正则表达式匹配 668 | $this->expectErrorMessageMatches('/foo/'); 669 | 670 | \trigger_error('foo', \E_USER_ERROR); 671 | } 672 | } 673 | 674 | 如果测试代码使用了会触发错误的 PHP 内建函数,比如 ``fopen``,有时候在测试中使用错误抑制符会很有用。通过抑制住错误通知,就能对返回值进行检查,否则错误通知将会导致 PHPUnit 的错误处理程序抛出异常。 675 | 676 | .. code-block:: php 677 | :caption: 对会引发PHP 错误的代码的返回值进行测试 678 | :name: writing-tests-for-phpunit.exceptions.examples.TriggerErrorReturnValue.php 679 | 680 | assertFalse(@$writer->write('/is-not-writeable/file', 'stuff')); 690 | } 691 | } 692 | 693 | final class FileWriter 694 | { 695 | public function write($file, $content) 696 | { 697 | $file = fopen($file, 'w'); 698 | 699 | if ($file === false) { 700 | return false; 701 | } 702 | 703 | // ... 704 | } 705 | } 706 | 707 | .. parsed-literal:: 708 | 709 | $ phpunit ErrorSuppressionTest 710 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 711 | 712 | . 713 | 714 | Time: 1 seconds, Memory: 5.25Mb 715 | 716 | OK (1 test, 1 assertion) 717 | 718 | 如果不使用错误抑制符,此测试将会失败,并报告 ``fopen(/is-not-writeable/file): failed to open stream: No such file or directory``。 719 | 720 | .. _writing-tests-for-phpunit.output: 721 | 722 | 对输出进行测试 723 | ############## 724 | 725 | 有时候,想要断言(比如说)某方法的运行过程中生成了预期的输出(例如,通过 ``echo`` 或 ``print``)。\ ``PHPUnit\Framework\TestCase`` 类使用 PHP 的\ `输出缓冲 `_\ 特性来为此提供必要的功能支持。 726 | 727 | :numref:`writing-tests-for-phpunit.output.examples.OutputTest.php` 展示了如何用 ``expectOutputString()`` 方法来设定所预期的输出。如果没有产生预期的输出,测试将计为失败。 728 | 729 | .. code-block:: php 730 | :caption: 对函数或方法的输出进行测试 731 | :name: writing-tests-for-phpunit.output.examples.OutputTest.php 732 | 733 | expectOutputString('foo'); 741 | 742 | print 'foo'; 743 | } 744 | 745 | public function testExpectBarActualBaz(): void 746 | { 747 | $this->expectOutputString('bar'); 748 | 749 | print 'baz'; 750 | } 751 | } 752 | 753 | .. parsed-literal:: 754 | 755 | $ phpunit OutputTest 756 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 757 | 758 | .F 759 | 760 | Time: 0 seconds, Memory: 5.75Mb 761 | 762 | There was 1 failure: 763 | 764 | 1) OutputTest::testExpectBarActualBaz 765 | Failed asserting that two strings are equal. 766 | --- Expected 767 | +++ Actual 768 | @@ @@ 769 | -'bar' 770 | +'baz' 771 | 772 | FAILURES! 773 | Tests: 2, Assertions: 2, Failures: 1. 774 | 775 | :numref:`writing-tests-for-phpunit.output.tables.api` 776 | 中列举了用于对输出进行测试的各种方法 777 | 778 | .. rst-class:: table 779 | .. list-table:: 测试输出的方法 780 | :name: writing-tests-for-phpunit.output.tables.api 781 | :header-rows: 1 782 | 783 | * - 方法 784 | - 含义 785 | * - ``void expectOutputRegex(string $regularExpression)`` 786 | - 设置输出预期为输出应当匹配正则表达式 ``$regularExpression``。 787 | * - ``void expectOutputString(string $expectedString)`` 788 | - 设置输出预期为输出应当与 ``$expectedString`` 相等。 789 | * - ``bool setOutputCallback(callable $callback)`` 790 | - 设置回调函数,用来做诸如将实际输出规范化之类的动作。 791 | * - ``string getActualOutput()`` 792 | - 获取实际输出。 793 | 794 | .. admonition:: 注 795 | 796 | 在严格模式下,本身产生输出的测试将会失败。 797 | 798 | .. _writing-tests-for-phpunit.error-output: 799 | 800 | 错误相关信息的输出 801 | ################### 802 | 803 | 当有测试失败时,PHPUnit 全力提供尽可能多的有助于找出问题所在的上下文信息。 804 | 805 | .. code-block:: php 806 | :caption: 数组比较失败时生成的错误输出 807 | :name: writing-tests-for-phpunit.error-output.examples.ArrayDiffTest.php 808 | 809 | assertSame( 817 | [1, 2, 3, 4, 5, 6], 818 | [1, 2, 33, 4, 5, 6] 819 | ); 820 | } 821 | } 822 | 823 | .. parsed-literal:: 824 | 825 | $ phpunit ArrayDiffTest 826 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 827 | 828 | F 829 | 830 | Time: 0 seconds, Memory: 5.25Mb 831 | 832 | There was 1 failure: 833 | 834 | 1) ArrayDiffTest::testEquality 835 | Failed asserting that two arrays are identical. 836 | --- Expected 837 | +++ Actual 838 | @@ @@ 839 | Array ( 840 | 0 => 1 841 | 1 => 2 842 | - 2 => 3 843 | + 2 => 33 844 | 3 => 4 845 | 4 => 5 846 | 5 => 6 847 | ) 848 | 849 | /home/sb/ArrayDiffTest.php:7 850 | 851 | FAILURES! 852 | Tests: 1, Assertions: 1, Failures: 1. 853 | 854 | 在这个例子中,数组中只有一个值不同,但其他值也都同时显示出来,以提供关于错误发生的位置的上下文信息。 855 | 856 | 当生成的输出很长而难以阅读时,PHPUnit 将对其进行分割,并在每个差异附近提供少数几行上下文信息。 857 | 858 | .. code-block:: php 859 | :caption: 长数组的数组比较失败时的错误输出 860 | :name: writing-tests-for-phpunit.error-output.examples.LongArrayDiffTest.php 861 | 862 | assertSame( 870 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6], 871 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 33, 4, 5, 6] 872 | ); 873 | } 874 | } 875 | 876 | .. parsed-literal:: 877 | 878 | $ phpunit LongArrayDiffTest 879 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 880 | 881 | F 882 | 883 | Time: 0 seconds, Memory: 5.25Mb 884 | 885 | There was 1 failure: 886 | 887 | 1) LongArrayDiffTest::testEquality 888 | Failed asserting that two arrays are identical. 889 | --- Expected 890 | +++ Actual 891 | @@ @@ 892 | 11 => 0 893 | 12 => 1 894 | 13 => 2 895 | - 14 => 3 896 | + 14 => 33 897 | 15 => 4 898 | 16 => 5 899 | 17 => 6 900 | ) 901 | 902 | /home/sb/LongArrayDiffTest.php:7 903 | 904 | FAILURES! 905 | Tests: 1, Assertions: 1, Failures: 1. 906 | 907 | .. _writing-tests-for-phpunit.error-output.edge-cases: 908 | 909 | 边缘情况 910 | ========== 911 | 912 | 当比较失败时,PHPUnit 为输入值建立文本表示,然后以此进行对比。这种实现导致在差异指示中显示出来的问题可能比实际上存在的多。 913 | 914 | 这种情况只出现在对数组或者对象使用 ``assertEquals()`` 或其他“弱”比较函数时。 915 | 916 | .. code-block:: php 917 | :caption: 使用弱比较时在差异生成过程中的边缘情况 918 | :name: writing-tests-for-phpunit.error-output.edge-cases.examples.ArrayWeakComparisonTest.php 919 | 920 | assertEquals( 928 | [1, 2, 3, 4, 5, 6], 929 | ['1', 2, 33, 4, 5, 6] 930 | ); 931 | } 932 | } 933 | 934 | .. parsed-literal:: 935 | 936 | $ phpunit ArrayWeakComparisonTest 937 | PHPUnit |version|.0 by Sebastian Bergmann and contributors. 938 | 939 | F 940 | 941 | Time: 0 seconds, Memory: 5.25Mb 942 | 943 | There was 1 failure: 944 | 945 | 1) ArrayWeakComparisonTest::testEquality 946 | Failed asserting that two arrays are equal. 947 | --- Expected 948 | +++ Actual 949 | @@ @@ 950 | Array ( 951 | - 0 => 1 952 | + 0 => '1' 953 | 1 => 2 954 | - 2 => 3 955 | + 2 => 33 956 | 3 => 4 957 | 4 => 5 958 | 5 => 6 959 | ) 960 | 961 | /home/sb/ArrayWeakComparisonTest.php:7 962 | 963 | FAILURES! 964 | Tests: 1, Assertions: 1, Failures: 1. 965 | 966 | 在这个例子中,第一个索引项中的 ``1`` 和 ``'1'`` 在报告中被视为不同,虽然 ``assertEquals()`` 认为这两个值是匹配的。 967 | 968 | 969 | --------------------------------------------------------------------------------