├── .gitignore ├── LICENSE.txt ├── Makefile ├── README.rst ├── _templates └── layout.html ├── conf.py ├── index.rst ├── locale ├── README.rst └── zh_TW │ └── LC_MESSAGES │ ├── README.po │ ├── index.po │ └── python │ ├── example_00 │ └── README.po │ ├── example_01 │ └── README.po │ ├── example_02 │ └── README.po │ ├── example_03 │ └── README.po │ ├── example_04 │ └── README.po │ ├── example_05 │ └── README.po │ ├── example_06 │ └── README.po │ ├── example_07 │ └── README.po │ ├── example_08 │ ├── README.po │ └── README_str.po │ ├── example_09 │ └── README.po │ ├── example_10 │ └── README.po │ ├── example_11 │ └── README.po │ └── example_12 │ └── README.po ├── python ├── README.rst ├── example_00 │ ├── README.rst │ ├── sandwich │ │ ├── __init__.py │ │ ├── control.py │ │ └── ham │ │ │ ├── __init__.py │ │ │ └── ham.py │ └── tests │ │ ├── __init__.py │ │ └── test_control.py ├── example_01 │ ├── README.rst │ ├── sandwich │ │ ├── __init__.py │ │ ├── control.py │ │ └── ham │ │ │ ├── __init__.py │ │ │ └── ham.py │ └── tests │ │ ├── __init__.py │ │ └── test_control.py ├── example_02 │ ├── README.rst │ ├── flaky.py │ └── test_flaky.py ├── example_03 │ ├── README.rst │ ├── hello.py │ ├── hello2.py │ ├── test_hello.py │ └── test_hello2.py ├── example_04 │ ├── README.rst │ ├── hello.py │ ├── myparser.py │ └── test_hello.py ├── example_05 │ ├── README.rst │ ├── hello.py │ ├── hello2.py │ ├── test_hello.py │ └── test_hello2.py ├── example_06 │ ├── README.rst │ ├── hello.py │ ├── hello2.py │ ├── test_hello.py │ └── test_hello2.py ├── example_07 │ ├── README.rst │ ├── sandwich.py │ └── tests.py ├── example_08 │ ├── README.rst │ ├── README_str.rst │ ├── modes.py │ ├── selinux.py │ ├── selinux_str.py │ ├── tests.py │ └── tests_str.py ├── example_09 │ ├── README.rst │ ├── roads.py │ ├── roads2.py │ ├── test1.py │ ├── test2.py │ └── test3.py ├── example_10 │ ├── README.rst │ ├── boolops1.py │ ├── boolops2.py │ ├── boolops3.py │ ├── boolops4.py │ ├── test1.py │ ├── test2.py │ ├── test3.py │ └── test4.py ├── example_11 │ ├── README.rst │ ├── example1.py │ ├── example2.py │ ├── test1.py │ └── test2.py └── example_12 │ ├── README.rst │ ├── percent.py │ ├── test1.py │ └── test2.py └── rtd_requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | __pycache__/ 3 | *.pyc 4 | *.json 5 | *.mo 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Attribution-ShareAlike 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More_considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution-ShareAlike 4.0 International Public 58 | License 59 | 60 | By exercising the Licensed Rights (defined below), You accept and agree 61 | to be bound by the terms and conditions of this Creative Commons 62 | Attribution-ShareAlike 4.0 International Public License ("Public 63 | License"). To the extent this Public License may be interpreted as a 64 | contract, You are granted the Licensed Rights in consideration of Your 65 | acceptance of these terms and conditions, and the Licensor grants You 66 | such rights in consideration of benefits the Licensor receives from 67 | making the Licensed Material available under these terms and 68 | conditions. 69 | 70 | 71 | Section 1 -- Definitions. 72 | 73 | a. Adapted Material means material subject to Copyright and Similar 74 | Rights that is derived from or based upon the Licensed Material 75 | and in which the Licensed Material is translated, altered, 76 | arranged, transformed, or otherwise modified in a manner requiring 77 | permission under the Copyright and Similar Rights held by the 78 | Licensor. For purposes of this Public License, where the Licensed 79 | Material is a musical work, performance, or sound recording, 80 | Adapted Material is always produced where the Licensed Material is 81 | synched in timed relation with a moving image. 82 | 83 | b. Adapter's License means the license You apply to Your Copyright 84 | and Similar Rights in Your contributions to Adapted Material in 85 | accordance with the terms and conditions of this Public License. 86 | 87 | c. BY-SA Compatible License means a license listed at 88 | creativecommons.org/compatiblelicenses, approved by Creative 89 | Commons as essentially the equivalent of this Public License. 90 | 91 | d. Copyright and Similar Rights means copyright and/or similar rights 92 | closely related to copyright including, without limitation, 93 | performance, broadcast, sound recording, and Sui Generis Database 94 | Rights, without regard to how the rights are labeled or 95 | categorized. For purposes of this Public License, the rights 96 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 97 | Rights. 98 | 99 | e. Effective Technological Measures means those measures that, in the 100 | absence of proper authority, may not be circumvented under laws 101 | fulfilling obligations under Article 11 of the WIPO Copyright 102 | Treaty adopted on December 20, 1996, and/or similar international 103 | agreements. 104 | 105 | f. Exceptions and Limitations means fair use, fair dealing, and/or 106 | any other exception or limitation to Copyright and Similar Rights 107 | that applies to Your use of the Licensed Material. 108 | 109 | g. License Elements means the license attributes listed in the name 110 | of a Creative Commons Public License. The License Elements of this 111 | Public License are Attribution and ShareAlike. 112 | 113 | h. Licensed Material means the artistic or literary work, database, 114 | or other material to which the Licensor applied this Public 115 | License. 116 | 117 | i. Licensed Rights means the rights granted to You subject to the 118 | terms and conditions of this Public License, which are limited to 119 | all Copyright and Similar Rights that apply to Your use of the 120 | Licensed Material and that the Licensor has authority to license. 121 | 122 | j. Licensor means the individual(s) or entity(ies) granting rights 123 | under this Public License. 124 | 125 | k. Share means to provide material to the public by any means or 126 | process that requires permission under the Licensed Rights, such 127 | as reproduction, public display, public performance, distribution, 128 | dissemination, communication, or importation, and to make material 129 | available to the public including in ways that members of the 130 | public may access the material from a place and at a time 131 | individually chosen by them. 132 | 133 | l. Sui Generis Database Rights means rights other than copyright 134 | resulting from Directive 96/9/EC of the European Parliament and of 135 | the Council of 11 March 1996 on the legal protection of databases, 136 | as amended and/or succeeded, as well as other essentially 137 | equivalent rights anywhere in the world. 138 | 139 | m. You means the individual or entity exercising the Licensed Rights 140 | under this Public License. Your has a corresponding meaning. 141 | 142 | 143 | Section 2 -- Scope. 144 | 145 | a. License grant. 146 | 147 | 1. Subject to the terms and conditions of this Public License, 148 | the Licensor hereby grants You a worldwide, royalty-free, 149 | non-sublicensable, non-exclusive, irrevocable license to 150 | exercise the Licensed Rights in the Licensed Material to: 151 | 152 | a. reproduce and Share the Licensed Material, in whole or 153 | in part; and 154 | 155 | b. produce, reproduce, and Share Adapted Material. 156 | 157 | 2. Exceptions and Limitations. For the avoidance of doubt, where 158 | Exceptions and Limitations apply to Your use, this Public 159 | License does not apply, and You do not need to comply with 160 | its terms and conditions. 161 | 162 | 3. Term. The term of this Public License is specified in Section 163 | 6(a). 164 | 165 | 4. Media and formats; technical modifications allowed. The 166 | Licensor authorizes You to exercise the Licensed Rights in 167 | all media and formats whether now known or hereafter created, 168 | and to make technical modifications necessary to do so. The 169 | Licensor waives and/or agrees not to assert any right or 170 | authority to forbid You from making technical modifications 171 | necessary to exercise the Licensed Rights, including 172 | technical modifications necessary to circumvent Effective 173 | Technological Measures. For purposes of this Public License, 174 | simply making modifications authorized by this Section 2(a) 175 | (4) never produces Adapted Material. 176 | 177 | 5. Downstream recipients. 178 | 179 | a. Offer from the Licensor -- Licensed Material. Every 180 | recipient of the Licensed Material automatically 181 | receives an offer from the Licensor to exercise the 182 | Licensed Rights under the terms and conditions of this 183 | Public License. 184 | 185 | b. Additional offer from the Licensor -- Adapted Material. 186 | Every recipient of Adapted Material from You 187 | automatically receives an offer from the Licensor to 188 | exercise the Licensed Rights in the Adapted Material 189 | under the conditions of the Adapter's License You apply. 190 | 191 | c. No downstream restrictions. You may not offer or impose 192 | any additional or different terms or conditions on, or 193 | apply any Effective Technological Measures to, the 194 | Licensed Material if doing so restricts exercise of the 195 | Licensed Rights by any recipient of the Licensed 196 | Material. 197 | 198 | 6. No endorsement. Nothing in this Public License constitutes or 199 | may be construed as permission to assert or imply that You 200 | are, or that Your use of the Licensed Material is, connected 201 | with, or sponsored, endorsed, or granted official status by, 202 | the Licensor or others designated to receive attribution as 203 | provided in Section 3(a)(1)(A)(i). 204 | 205 | b. Other rights. 206 | 207 | 1. Moral rights, such as the right of integrity, are not 208 | licensed under this Public License, nor are publicity, 209 | privacy, and/or other similar personality rights; however, to 210 | the extent possible, the Licensor waives and/or agrees not to 211 | assert any such rights held by the Licensor to the limited 212 | extent necessary to allow You to exercise the Licensed 213 | Rights, but not otherwise. 214 | 215 | 2. Patent and trademark rights are not licensed under this 216 | Public License. 217 | 218 | 3. To the extent possible, the Licensor waives any right to 219 | collect royalties from You for the exercise of the Licensed 220 | Rights, whether directly or through a collecting society 221 | under any voluntary or waivable statutory or compulsory 222 | licensing scheme. In all other cases the Licensor expressly 223 | reserves any right to collect such royalties. 224 | 225 | 226 | Section 3 -- License Conditions. 227 | 228 | Your exercise of the Licensed Rights is expressly made subject to the 229 | following conditions. 230 | 231 | a. Attribution. 232 | 233 | 1. If You Share the Licensed Material (including in modified 234 | form), You must: 235 | 236 | a. retain the following if it is supplied by the Licensor 237 | with the Licensed Material: 238 | 239 | i. identification of the creator(s) of the Licensed 240 | Material and any others designated to receive 241 | attribution, in any reasonable manner requested by 242 | the Licensor (including by pseudonym if 243 | designated); 244 | 245 | ii. a copyright notice; 246 | 247 | iii. a notice that refers to this Public License; 248 | 249 | iv. a notice that refers to the disclaimer of 250 | warranties; 251 | 252 | v. a URI or hyperlink to the Licensed Material to the 253 | extent reasonably practicable; 254 | 255 | b. indicate if You modified the Licensed Material and 256 | retain an indication of any previous modifications; and 257 | 258 | c. indicate the Licensed Material is licensed under this 259 | Public License, and include the text of, or the URI or 260 | hyperlink to, this Public License. 261 | 262 | 2. You may satisfy the conditions in Section 3(a)(1) in any 263 | reasonable manner based on the medium, means, and context in 264 | which You Share the Licensed Material. For example, it may be 265 | reasonable to satisfy the conditions by providing a URI or 266 | hyperlink to a resource that includes the required 267 | information. 268 | 269 | 3. If requested by the Licensor, You must remove any of the 270 | information required by Section 3(a)(1)(A) to the extent 271 | reasonably practicable. 272 | 273 | b. ShareAlike. 274 | 275 | In addition to the conditions in Section 3(a), if You Share 276 | Adapted Material You produce, the following conditions also apply. 277 | 278 | 1. The Adapter's License You apply must be a Creative Commons 279 | license with the same License Elements, this version or 280 | later, or a BY-SA Compatible License. 281 | 282 | 2. You must include the text of, or the URI or hyperlink to, the 283 | Adapter's License You apply. You may satisfy this condition 284 | in any reasonable manner based on the medium, means, and 285 | context in which You Share Adapted Material. 286 | 287 | 3. You may not offer or impose any additional or different terms 288 | or conditions on, or apply any Effective Technological 289 | Measures to, Adapted Material that restrict exercise of the 290 | rights granted under the Adapter's License You apply. 291 | 292 | 293 | Section 4 -- Sui Generis Database Rights. 294 | 295 | Where the Licensed Rights include Sui Generis Database Rights that 296 | apply to Your use of the Licensed Material: 297 | 298 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 299 | to extract, reuse, reproduce, and Share all or a substantial 300 | portion of the contents of the database; 301 | 302 | b. if You include all or a substantial portion of the database 303 | contents in a database in which You have Sui Generis Database 304 | Rights, then the database in which You have Sui Generis Database 305 | Rights (but not its individual contents) is Adapted Material, 306 | 307 | including for purposes of Section 3(b); and 308 | c. You must comply with the conditions in Section 3(a) if You Share 309 | all or a substantial portion of the contents of the database. 310 | 311 | For the avoidance of doubt, this Section 4 supplements and does not 312 | replace Your obligations under this Public License where the Licensed 313 | Rights include other Copyright and Similar Rights. 314 | 315 | 316 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 317 | 318 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 319 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 320 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 321 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 322 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 323 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 324 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 325 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 326 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 327 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 328 | 329 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 330 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 331 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 332 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 333 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 334 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 335 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 336 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 337 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 338 | 339 | c. The disclaimer of warranties and limitation of liability provided 340 | above shall be interpreted in a manner that, to the extent 341 | possible, most closely approximates an absolute disclaimer and 342 | waiver of all liability. 343 | 344 | 345 | Section 6 -- Term and Termination. 346 | 347 | a. This Public License applies for the term of the Copyright and 348 | Similar Rights licensed here. However, if You fail to comply with 349 | this Public License, then Your rights under this Public License 350 | terminate automatically. 351 | 352 | b. Where Your right to use the Licensed Material has terminated under 353 | Section 6(a), it reinstates: 354 | 355 | 1. automatically as of the date the violation is cured, provided 356 | it is cured within 30 days of Your discovery of the 357 | violation; or 358 | 359 | 2. upon express reinstatement by the Licensor. 360 | 361 | For the avoidance of doubt, this Section 6(b) does not affect any 362 | right the Licensor may have to seek remedies for Your violations 363 | of this Public License. 364 | 365 | c. For the avoidance of doubt, the Licensor may also offer the 366 | Licensed Material under separate terms or conditions or stop 367 | distributing the Licensed Material at any time; however, doing so 368 | will not terminate this Public License. 369 | 370 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 371 | License. 372 | 373 | 374 | Section 7 -- Other Terms and Conditions. 375 | 376 | a. The Licensor shall not be bound by any additional or different 377 | terms or conditions communicated by You unless expressly agreed. 378 | 379 | b. Any arrangements, understandings, or agreements regarding the 380 | Licensed Material not stated herein are separate from and 381 | independent of the terms and conditions of this Public License. 382 | 383 | 384 | Section 8 -- Interpretation. 385 | 386 | a. For the avoidance of doubt, this Public License does not, and 387 | shall not be interpreted to, reduce, limit, restrict, or impose 388 | conditions on any use of the Licensed Material that could lawfully 389 | be made without permission under this Public License. 390 | 391 | b. To the extent possible, if any provision of this Public License is 392 | deemed unenforceable, it shall be automatically reformed to the 393 | minimum extent necessary to make it enforceable. If the provision 394 | cannot be reformed, it shall be severed from this Public License 395 | without affecting the enforceability of the remaining terms and 396 | conditions. 397 | 398 | c. No term or condition of this Public License will be waived and no 399 | failure to comply consented to unless expressly agreed to by the 400 | Licensor. 401 | 402 | d. Nothing in this Public License constitutes or may be interpreted 403 | as a limitation upon, or waiver of, any privileges and immunities 404 | that apply to the Licensor or You, including from the legal 405 | processes of any jurisdiction or authority. 406 | 407 | 408 | ======================================================================= 409 | 410 | Creative Commons is not a party to its public 411 | licenses. Notwithstanding, Creative Commons may elect to apply one of 412 | its public licenses to material it publishes and in those instances 413 | will be considered the “Licensor.” The text of the Creative Commons 414 | public licenses is dedicated to the public domain under the CC0 Public 415 | Domain Dedication. Except for the limited purpose of indicating that 416 | material is shared under a Creative Commons public license or as 417 | otherwise permitted by the Creative Commons policies published at 418 | creativecommons.org/policies, Creative Commons does not authorize the 419 | use of the trademark "Creative Commons" or any other trademark or logo 420 | of Creative Commons without its prior written consent including, 421 | without limitation, in connection with any unauthorized modifications 422 | to any of its public licenses or any other arrangements, 423 | understandings, or agreements concerning use of licensed material. For 424 | the avoidance of doubt, this paragraph does not form part of the 425 | public licenses. 426 | 427 | Creative Commons may be contacted at creativecommons.org. 428 | 429 | -------------------------------------------------------------------------------- /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 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help 18 | help: 19 | @echo "Please use \`make ' where is one of" 20 | @echo " html to make standalone HTML files" 21 | @echo " dirhtml to make HTML files named index.html in directories" 22 | @echo " singlehtml to make a single large HTML file" 23 | @echo " pickle to make pickle files" 24 | @echo " json to make JSON files" 25 | @echo " htmlhelp to make HTML files and a HTML help project" 26 | @echo " qthelp to make HTML files and a qthelp project" 27 | @echo " applehelp to make an Apple Help Book" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " epub3 to make an epub3" 31 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 32 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 33 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 34 | @echo " text to make text files" 35 | @echo " man to make manual pages" 36 | @echo " texinfo to make Texinfo files" 37 | @echo " info to make Texinfo files and run them through makeinfo" 38 | @echo " gettext to make PO message catalogs" 39 | @echo " changes to make an overview of all changed/added/deprecated items" 40 | @echo " xml to make Docutils-native XML files" 41 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 42 | @echo " linkcheck to check all external links for integrity" 43 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 44 | @echo " coverage to run coverage check of the documentation (if enabled)" 45 | @echo " dummy to check syntax errors of document sources" 46 | 47 | .PHONY: clean 48 | clean: 49 | rm -rf $(BUILDDIR)/* 50 | 51 | .PHONY: html 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | .PHONY: dirhtml 58 | dirhtml: 59 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 60 | @echo 61 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 62 | 63 | .PHONY: singlehtml 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | .PHONY: pickle 70 | pickle: 71 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 72 | @echo 73 | @echo "Build finished; now you can process the pickle files." 74 | 75 | .PHONY: json 76 | json: 77 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 78 | @echo 79 | @echo "Build finished; now you can process the JSON files." 80 | 81 | .PHONY: htmlhelp 82 | htmlhelp: 83 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 84 | @echo 85 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 86 | ".hhp project file in $(BUILDDIR)/htmlhelp." 87 | 88 | .PHONY: qthelp 89 | qthelp: 90 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 91 | @echo 92 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 93 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 94 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/MutationTestinginPatterns.qhcp" 95 | @echo "To view the help file:" 96 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/MutationTestinginPatterns.qhc" 97 | 98 | .PHONY: applehelp 99 | applehelp: 100 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 101 | @echo 102 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 103 | @echo "N.B. You won't be able to view it unless you put it in" \ 104 | "~/Library/Documentation/Help or install it in your application" \ 105 | "bundle." 106 | 107 | .PHONY: devhelp 108 | devhelp: 109 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 110 | @echo 111 | @echo "Build finished." 112 | @echo "To view the help file:" 113 | @echo "# mkdir -p $$HOME/.local/share/devhelp/MutationTestinginPatterns" 114 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/MutationTestinginPatterns" 115 | @echo "# devhelp" 116 | 117 | .PHONY: epub 118 | epub: 119 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 120 | @echo 121 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 122 | 123 | .PHONY: epub3 124 | epub3: 125 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 126 | @echo 127 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 128 | 129 | .PHONY: latex 130 | latex: 131 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 132 | @echo 133 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 134 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 135 | "(use \`make latexpdf' here to do that automatically)." 136 | 137 | .PHONY: latexpdf 138 | latexpdf: 139 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 140 | @echo "Running LaTeX files through pdflatex..." 141 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 142 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 143 | 144 | .PHONY: latexpdfja 145 | latexpdfja: 146 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 147 | @echo "Running LaTeX files through platex and dvipdfmx..." 148 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 149 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 150 | 151 | .PHONY: text 152 | text: 153 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 154 | @echo 155 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 156 | 157 | .PHONY: man 158 | man: 159 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 160 | @echo 161 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 162 | 163 | .PHONY: texinfo 164 | texinfo: 165 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 166 | @echo 167 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 168 | @echo "Run \`make' in that directory to run these through makeinfo" \ 169 | "(use \`make info' here to do that automatically)." 170 | 171 | .PHONY: info 172 | info: 173 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 174 | @echo "Running Texinfo files through makeinfo..." 175 | make -C $(BUILDDIR)/texinfo info 176 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 177 | 178 | .PHONY: gettext 179 | gettext: 180 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 181 | @echo 182 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 183 | 184 | .PHONY: changes 185 | changes: 186 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 187 | @echo 188 | @echo "The overview file is in $(BUILDDIR)/changes." 189 | 190 | .PHONY: linkcheck 191 | linkcheck: 192 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 193 | @echo 194 | @echo "Link check complete; look for any errors in the above output " \ 195 | "or in $(BUILDDIR)/linkcheck/output.txt." 196 | 197 | .PHONY: doctest 198 | doctest: 199 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 200 | @echo "Testing of doctests in the sources finished, look at the " \ 201 | "results in $(BUILDDIR)/doctest/output.txt." 202 | 203 | .PHONY: coverage 204 | coverage: 205 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 206 | @echo "Testing of coverage in the sources finished, look at the " \ 207 | "results in $(BUILDDIR)/coverage/python.txt." 208 | 209 | .PHONY: xml 210 | xml: 211 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 212 | @echo 213 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 214 | 215 | .PHONY: pseudoxml 216 | pseudoxml: 217 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 218 | @echo 219 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 220 | 221 | .PHONY: dummy 222 | dummy: 223 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 224 | @echo 225 | @echo "Build finished. Dummy builder generates no files." 226 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Mutation Testing in Patterns 2 | **************************** 3 | 4 | .. image:: https://readthedocs.org/projects/mutation-testing-patterns/badge/?version=latest 5 | :target: http://mutation-testing-patterns.readthedocs.io/en/latest/?badge=latest 6 | :alt: Documentation Status 7 | 8 | Mutation testing is a technique used to evaluate the quality of existing software 9 | tests. Mutation testing involves modifying a program in small ways, for example 10 | replacing ``True`` constants with ``False`` and re-running its test suite. 11 | When the test suite fails the *mutant* is *killed*. This tells us how good the 12 | test suite is. The goal of this paper is to describe different software and 13 | testing patterns related using practical examples. 14 | 15 | Some of them are language specific so please see the relevant sections for 16 | information about installing and running the necessary tools and examples. 17 | 18 | .. toctree:: 19 | :maxdepth: 2 20 | 21 | Mutation testing tools 22 | ====================== 23 | 24 | This is a list of mutation testing tools which are under active use and 25 | maintenance from the community: 26 | 27 | * Python 28 | * `Cosmic Ray `_ 29 | * `mutmut `_ 30 | * Ruby - `Mutant `_ 31 | * Java - `Pitest `_ 32 | * JavaScript - `Stryker `_ 33 | * PHP - `Humbug `_ 34 | 35 | For LLVM-based languages such as C, C++, Rust and Objective-C checkout 36 | [mull](https://github.com/mull-project/mull), which looks like a nice project 37 | but may not be ready for production use! 38 | 39 | 40 | Make sure your tools work 41 | ========================= 42 | 43 | Mutation testing relies on dynamically modifying program modules and 44 | loading the mutated instance from memory. Depending on the language specifics 45 | there may be several ways to refer to the same module. In Python 46 | the following are equivalent 47 | 48 | .. code-block:: python 49 | 50 | import sandwich.ham.ham 51 | obj = sandwich.ham.ham.SomeClass() 52 | 53 | from sandwich.ham import ham 54 | obj = ham.SomeClass() 55 | 56 | .. note:: 57 | 58 | The equivalency here is in terms of having access to the same module API. 59 | 60 | When we mutation test the right-most ``ham`` module our tools may not 61 | be able to resolve to the same module if various importing styles are used. 62 | For example see :doc:`python/example_00/README`. 63 | 64 | Another possible issue is with programs that load modules dynamically or 65 | change the module search path at runtime. Depending on how the 66 | mutation testing tool works these operations may interfere with it. 67 | For example see :doc:`python/example_01/README`. 68 | 69 | 70 | Make sure your tests work 71 | ========================= 72 | 73 | Mutation testing relies on the fact that your test suite will fail when a 74 | mutation is introduced. In turn any kind of failure will kill the mutant! 75 | The mutation test tool has no way of knowing whether your test suite failed 76 | because the mutant tripped one of the assertions or whether it failed due 77 | to other reasons. 78 | 79 | 80 | Make sure your test suite is robust and doesn't randomly fail due to 81 | external factors! 82 | For example see :doc:`python/example_02/README`. 83 | 84 | 85 | 86 | Divide and conquer 87 | ================== 88 | 89 | The basic mutation test algorithm is this 90 | 91 | .. code-block:: python 92 | 93 | for operator in mutation-operators: 94 | for site in operator.sites(code): 95 | operator.mutate(site) 96 | run_tests() 97 | 98 | - **mutation-operators** are the things that make small changes to your code 99 | - **operator.sites** are the places in your code where operators can be 100 | applied 101 | 102 | 103 | As you can see mutation testing is a very expensive operation. For example 104 | the `pykickstart `_ project 105 | started with 5523 possible mutations and 347 tests, which took on average 106 | 100 seconds to execute. A full mutation testing execution needs more than 107 | 6 days to complete! 108 | 109 | In practice however not all tests are related to, or even make use of 110 | all program modules. This means that mutated operators are only tested via 111 | subset of the entire test suite. This fact can be used to reduce 112 | execution time by scheduling mutation tests against each individual 113 | file/module using only the tests which are related to it. 114 | The best case scenario is when your source file names map directly to 115 | test file names. 116 | 117 | For example something like this 118 | 119 | .. code-block:: bash 120 | 121 | for f in `find ./src -type f -name "*.py" | sort`; do 122 | TEST_NAME="tests/$f" 123 | runTests $f $TEST_NAME 124 | done 125 | 126 | Where **runTests** executes the mutation testing tool against a single file 127 | and executes only the test which is related to this file. 128 | For *pykickstart* this approach reduced the entire execution time to little 129 | over 6 hours! 130 | 131 | .. note:: 132 | 133 | Other tools and languages may have a convention of how tests are organized or 134 | which tests are executed by the mutation testing tool. For example in Ruby the 135 | convention is to have all tests under `spec/*_spec.rb` which maps with the idea 136 | proposed above. Mutant, the Ruby mutation testing tool, uses this convention to 137 | find the tests it needs. For Python, on the other hand, the user needs to manually 138 | specify which tests should be executed! 139 | 140 | 141 | Fail fast 142 | ========= 143 | 144 | Mutation testing relies on your test suite failing when it detects a 145 | faulty mutation. It doesn't matter which particular test has failed because 146 | most of the tools have no way of telling whether or not the failed test is 147 | related to the mutated code. That means it also doesn't matter if there are 148 | more than one failing tests so you can use this to your advantage. 149 | 150 | Whenever your test tools and framework support the **fail fast** option 151 | make use of it to reduce test execution time even more! 152 | 153 | Refactor comparison to empty string 154 | =================================== 155 | 156 | Comparison operators may be mutated with each other which gives, 157 | depending on the langauge, about 10 possible mutations. 158 | 159 | Every time ``S`` is not an empty string the following 3 variants 160 | are evaluated to ``True``: 161 | 162 | * ``if S != ""`` 163 | * ``if S > ""`` 164 | * ``if S not in ""`` 165 | 166 | The existing test cases pass and these mutations are never killed. 167 | In languages like Python, non-empty sequences are evaluated to `True` in boolean 168 | context and you don't need to use comparisons. This reduces the number of 169 | possible mutations. 170 | 171 | For Python you may use the *emptystring* extension of pylint 172 | 173 | .. code-block:: bash 174 | 175 | pylint a.py --load-plugins=pylint.extensions.emptystring 176 | 177 | See `pylint #1183 `_ for more info and 178 | :doc:`python/example_03/README` for an example. 179 | 180 | .. warning:: 181 | 182 | In some cases empty string is an acceptable value and refactoring will 183 | change the behavior of the program! Be careful when doing this. 184 | 185 | 186 | Refactor comparison to zero 187 | =========================== 188 | 189 | This is similar to the previous section but for integer values. For Python use 190 | the *comparetozero* extension to detect possible offenses. 191 | 192 | .. code-block:: bash 193 | 194 | pylint a.py --load-plugins=pylint.extensions.comparetozero 195 | 196 | See `pylint #1243 `_ for more info. 197 | 198 | 199 | Python: Refactor len(X) comparisons to zero 200 | =========================================== 201 | 202 | Every time ``X`` is not an empty sequence the following variants 203 | are evaluated to ``True`` and result in surviving mutants: 204 | 205 | * ``if len(X) != 0`` 206 | * ``if len(X) > 0`` 207 | 208 | Additionally if we don't have a test to validate the ``if`` body, 209 | for example that it raises an exception, then the following mutation 210 | will also survive: 211 | 212 | * ``if len(X) < 0`` 213 | 214 | Refactoring this to :: 215 | 216 | if X: 217 | do_something() 218 | 219 | 220 | is the best way to go about it. This also reduces the total number of 221 | possible mutations. A more 222 | complicated example, using two lists and boolean operation can be 223 | seen below. 224 | 225 | .. code-block:: diff 226 | 227 | - if len(self.disabled) == 0 and len(self.enabled) == 0: 228 | + if not (self.disabled or self.enabled): 229 | 230 | 231 | Consider the following example 232 | 233 | .. code-block:: python 234 | 235 | # All the port:proto strings go into a comma-separated list. 236 | portstr = ",".join(filteredPorts) 237 | if len(portstr) > 0: 238 | portstr = " --port=" + portstr 239 | else: 240 | portstr = "" 241 | 242 | Similar to previous examples the ``len() > 0`` expression can be refactored. 243 | Since joining an empty list will produce an empty string the ``else`` block 244 | is not necessary. The example can be re-written as 245 | 246 | .. code-block:: python 247 | 248 | # All the port:proto strings go into a comma-separated list. 249 | portstr = ",".join(filteredPorts) 250 | if portstr: 251 | portstr = " --port=" + portstr 252 | 253 | In pylint 2.0 there is a new checker called *len-as-condition* which will 254 | warn you about code snippets that compare the result of a `len()` call to zero. 255 | For more information see 256 | `pylint #1154 `_. 257 | 258 | For practical example see :doc:`python/example_05/README`. 259 | 260 | 261 | Python: Refactor if len(list) == 1 262 | ================================== 263 | 264 | The following code 265 | 266 | .. code-block:: python 267 | 268 | if len(ns.password) == 1: 269 | self.password = ns.password[0] 270 | else: 271 | self.password = "" 272 | 273 | 274 | can be refactored into this 275 | 276 | .. code-block:: python 277 | 278 | if ns.password: 279 | self.password = ns.password[0] 280 | else: 281 | self.password = "" 282 | 283 | .. warning:: 284 | 285 | This refactoring may have side effects when the list length is greater 286 | than 1, e.g. 2. Depending on your program this may ot may-not be the case. 287 | 288 | 289 | Testing for X != 1 290 | ================== 291 | 292 | When testing the not equals condition we need at least 3 test cases: 293 | 294 | * Test with value smaller than the condition 295 | * Test with value that equals the condition 296 | * Test with value greater than the condition 297 | 298 | Most often we do test with value that equals the condition (the golden scenario) 299 | and either one of the other bordering values but not both. This 300 | leads to mutations which are not killed. 301 | 302 | Example :doc:`python/example_04/README`. 303 | 304 | 305 | Python: Refactor if X is None 306 | ============================= 307 | 308 | When X has a value of None the following mutations are equivalent 309 | are will survive: 310 | 311 | * ``if X is None:`` 312 | * ``if X == None:`` 313 | 314 | in addition static analyzers may report comparison to None 315 | as an offence. To handle this refactor 316 | ``if X is None:`` to ``if not X:`` when possible. 317 | 318 | 319 | For example see :doc:`python/example_06/README`. 320 | 321 | 322 | Python: Refactor if X is not None 323 | ================================= 324 | 325 | This is the opposite of the previous section. 326 | Refactor ``if X is not None:`` to ``if X:``. 327 | For example see :doc:`python/example_11/README`. 328 | 329 | 330 | 331 | Python: Testing __eq__ and __ne__ 332 | ================================= 333 | 334 | When objects are compared by comparing their attributes then full 335 | mutation test coverage can be achieved by comparing the object to itself, 336 | comparing to None, comparing two objects with the same attribute values 337 | and then test by changing the attributes one by one. 338 | 339 | For example see :doc:`python/example_07/README`. 340 | 341 | Consider if there is the following mistake in the example: 342 | 343 | .. code-block:: python 344 | 345 | def __eq__(self, other): 346 | if not y: 347 | return False 348 | 349 | return self.device and self.device == y.device 350 | 351 | Notice the redundant `self.device and` in the expression above! 352 | When `self.device` contains a value (string in this case) the expression is 353 | equivalent to `self.device == other.device`. On the other hand when 354 | `self.device` is `None` or an empty string the expression will always return `False`! 355 | 356 | If we have all of the above tests (which mutation testing has identified) 357 | then our test suite will fail and properly detect the defect :: 358 | 359 | $ python -m nose -- tests.py 360 | F..... 361 | ====================================================================== 362 | FAIL: Newly created objects with the same attribute values 363 | ---------------------------------------------------------------------- 364 | Traceback (most recent call last): 365 | File "~/example_07/tests.py", line 15, in test_default_objects_are_always_equal 366 | self.assertEqual(self.sandwich_1, self.sandwich_2) 367 | AssertionError: != 368 | 369 | ---------------------------------------------------------------------- 370 | Ran 6 tests in 0.001s 371 | 372 | FAILED (failures=1) 373 | 374 | .. note:: 375 | 376 | At the time of writing *Cosmic Ray* did not fail if there was a failure 377 | during the baseline test execution and all mutations would be reported as killed 378 | because, well the test suite failed! This was reported in 379 | `CR#111 `_ and fixed in 380 | `CR#181 `_. 381 | 382 | 383 | Python: Testing sequence of if == int 384 | ===================================== 385 | 386 | To completely test the following pattern 387 | 388 | .. code-block:: python 389 | 390 | if X == int_1: 391 | pass 392 | elif X == int_2: 393 | pass 394 | elif X == int_3: 395 | pass 396 | 397 | you need to test with all descrete values plus values outside the allowed set. 398 | For example see :doc:`python/example_08/README` 399 | 400 | 401 | Python: Testing sequence of if == string 402 | ======================================== 403 | 404 | To fully test the following pattern 405 | 406 | .. code-block:: python 407 | 408 | if X == "string_1": 409 | pass 410 | elif X == "string_2": 411 | pass 412 | elif X == "string_3": 413 | pass 414 | 415 | you need to test with all possible string values as well as with values 416 | outside the allowed set. For example see :doc:`python/example_08/README_str`. 417 | 418 | 419 | Python: Missing or extra parameters 420 | =================================== 421 | 422 | Depending on how your method signature is defined it is possible to either 423 | accept additional parameters which are not needed or forget to pass along 424 | parameters which control internal behavior. Mutation testing helps you 425 | identify those cases and adjust your code accordingly. 426 | 427 | For example see :doc:`python/example_09/README`. 428 | 429 | 430 | Python: Testing for 0 <= X < 100 431 | ================================= 432 | 433 | 434 | When testing numerical ranges we need at least 4 tests: 435 | 436 | * Test with both border values 437 | * Test with values outside the range, ideally +1/-1 438 | * Testing with a value in the middle of the range is not required 439 | for full mutation coverage! 440 | 441 | For example see :doc:`python/example_12/README`. 442 | 443 | 444 | Python: On boolean expressions 445 | ============================== 446 | 447 | When dealing with non-trivial boolean expressions mutation testing often helps 448 | put things into perspective. It causes you to rethink the expression which often 449 | leads to refactoring and killing mutants. 450 | For example see :doc:`python/example_10/README`. 451 | 452 | 453 | Refactor multiple boolean expressions 454 | ===================================== 455 | 456 | Consider the following code where the expression left of ``and`` 457 | is always the same 458 | 459 | .. code-block:: python 460 | 461 | if name == "method": 462 | self._clear_seen() 463 | 464 | if name == "method" and value == "cdrom": 465 | setattr(self.handler.cdrom, "seen", True) 466 | elif name == "method" and value == "harddrive": 467 | setattr(self.handler.harddrive, "seen", True) 468 | elif name == "method" and value == "nfs": 469 | setattr(self.handler.nfs, "seen", True) 470 | elif name == "method" and value == "url": 471 | setattr(self.handler.url, "seen", True) 472 | 473 | This can easily be refactored by removing the 474 | ``name == "method"`` expression and making the subsequent if 475 | statements nested under the first one. 476 | 477 | .. code-block:: python 478 | 479 | if name == "method": 480 | self._clear_seen() 481 | 482 | if value == "cdrom": 483 | setattr(self.handler.cdrom, "seen", True) 484 | elif value == "harddrive": 485 | setattr(self.handler.harddrive, "seen", True) 486 | elif value == "nfs": 487 | setattr(self.handler.nfs, "seen", True) 488 | elif value == "url": 489 | setattr(self.handler.url, "seen", True) 490 | 491 | The refactored code is shorter and provides less mutation sites thus 492 | reducing overall mutation test execution time. 493 | This code can be refactored even more aggressively into 494 | 495 | .. code-block:: python 496 | 497 | if name == "method": 498 | self._clear_seen() 499 | 500 | if value in ["cdrom", "harddrive", "nfs", "url"]: 501 | setattr(getattr(self.handler, value), "seen", True) 502 | 503 | 504 | 505 | Indices and tables 506 | ================== 507 | 508 | * :ref:`genindex` 509 | * :ref:`modindex` 510 | * :ref:`search` 511 | -------------------------------------------------------------------------------- /_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | {% block extrabody %} 3 |
4 | 5 | Fork me on GitHub 6 | 7 |
8 | {{ super() }} 9 | {% endblock %} 10 | 11 | {% block comments %} 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 |
29 | 37 | 38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Mutation Testing in Patterns documentation build configuration file, created by 5 | # sphinx-quickstart on Wed Aug 17 12:12:56 2016. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | # import os 21 | # import sys 22 | # sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | # i18n translate settings 26 | locale_dirs = ['locale/'] 27 | gettext_compact = False 28 | 29 | # If your documentation needs a minimal Sphinx version, state it here. 30 | # 31 | needs_sphinx = '1.5.2' 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 35 | # ones. 36 | extensions = [ 37 | 'sphinx.ext.autodoc', 38 | 'sphinx.ext.githubpages', 39 | 'sphinx.ext.viewcode', 40 | ] 41 | 42 | # Add any paths that contain templates here, relative to this directory. 43 | templates_path = ['_templates'] 44 | 45 | # The suffix(es) of source filenames. 46 | # You can specify multiple suffix as a list of string: 47 | # 48 | # source_suffix = ['.rst', '.md'] 49 | source_suffix = '.rst' 50 | 51 | # The encoding of source files. 52 | # 53 | # source_encoding = 'utf-8-sig' 54 | 55 | # The master toctree document. 56 | master_doc = 'index' 57 | 58 | # General information about the project. 59 | project = 'Mutation Testing in Patterns' 60 | copyright = 'CC-BY-SA 2016-2017, Alexander Todorov & contributors' 61 | author = 'Alexander Todorov' 62 | 63 | # The version info for the project you're documenting, acts as replacement for 64 | # |version| and |release|, also used in various other places throughout the 65 | # built documents. 66 | # 67 | # The short X.Y version. 68 | version = '1.0' 69 | # The full version, including alpha/beta/rc tags. 70 | release = version 71 | 72 | # The language for content autogenerated by Sphinx. Refer to documentation 73 | # for a list of supported languages. 74 | # 75 | # This is also used if you do content translation via gettext catalogs. 76 | # Usually you set "language" from the command line for these cases. 77 | language = None 78 | 79 | # There are two options for replacing |today|: either, you set today to some 80 | # non-false value, then it is used: 81 | # 82 | # today = '' 83 | # 84 | # Else, today_fmt is used as the format for a strftime call. 85 | # 86 | # today_fmt = '%B %d, %Y' 87 | 88 | # List of patterns, relative to source directory, that match files and 89 | # directories to ignore when looking for source files. 90 | # This patterns also effect to html_static_path and html_extra_path 91 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 92 | 93 | # The reST default role (used for this markup: `text`) to use for all 94 | # documents. 95 | # 96 | # default_role = None 97 | 98 | # If true, '()' will be appended to :func: etc. cross-reference text. 99 | # 100 | # add_function_parentheses = True 101 | 102 | # If true, the current module name will be prepended to all description 103 | # unit titles (such as .. function::). 104 | # 105 | # add_module_names = True 106 | 107 | # If true, sectionauthor and moduleauthor directives will be shown in the 108 | # output. They are ignored by default. 109 | # 110 | # show_authors = False 111 | 112 | # The name of the Pygments (syntax highlighting) style to use. 113 | pygments_style = 'sphinx' 114 | 115 | # A list of ignored prefixes for module index sorting. 116 | # modindex_common_prefix = [] 117 | 118 | # If true, keep warnings as "system message" paragraphs in the built documents. 119 | # keep_warnings = False 120 | 121 | # If true, `todo` and `todoList` produce output, else they produce nothing. 122 | todo_include_todos = False 123 | 124 | 125 | # -- Options for HTML output ---------------------------------------------- 126 | 127 | # The theme to use for HTML and HTML Help pages. See the documentation for 128 | # a list of builtin themes. 129 | # 130 | html_theme = 'sphinx_rtd_theme' 131 | 132 | # Theme options are theme-specific and customize the look and feel of a theme 133 | # further. For a list of options available for each theme, see the 134 | # documentation. 135 | # 136 | # html_theme_options = {} 137 | 138 | # Add any paths that contain custom themes here, relative to this directory. 139 | # html_theme_path = [] 140 | 141 | # The name for this set of Sphinx documents. 142 | # " v documentation" by default. 143 | # 144 | # html_title = 'Mutation Testing in Patterns v1.0' 145 | 146 | # A shorter title for the navigation bar. Default is the same as html_title. 147 | # 148 | # html_short_title = None 149 | 150 | # The name of an image file (relative to this directory) to place at the top 151 | # of the sidebar. 152 | # 153 | # html_logo = None 154 | 155 | # The name of an image file (relative to this directory) to use as a favicon of 156 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 157 | # pixels large. 158 | # 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 | # 170 | # html_extra_path = [] 171 | 172 | # If not None, a 'Last updated on:' timestamp is inserted at every page 173 | # bottom, using the given strftime format. 174 | # The empty string is equivalent to '%b %d, %Y'. 175 | # 176 | # html_last_updated_fmt = None 177 | 178 | # If true, SmartyPants will be used to convert quotes and dashes to 179 | # typographically correct entities. 180 | # 181 | # html_use_smartypants = True 182 | 183 | # Custom sidebar templates, maps document names to template names. 184 | # 185 | # html_sidebars = {} 186 | 187 | # Additional templates that should be rendered to pages, maps page names to 188 | # template names. 189 | # 190 | # html_additional_pages = {} 191 | 192 | # If false, no module index is generated. 193 | # 194 | # html_domain_indices = True 195 | 196 | # If false, no index is generated. 197 | # 198 | # html_use_index = True 199 | 200 | # If true, the index is split into individual pages for each letter. 201 | # 202 | # html_split_index = False 203 | 204 | # If true, links to the reST sources are added to the pages. 205 | # 206 | # html_show_sourcelink = True 207 | 208 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 209 | # 210 | # html_show_sphinx = True 211 | 212 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 213 | # 214 | # html_show_copyright = True 215 | 216 | # If true, an OpenSearch description file will be output, and all pages will 217 | # contain a tag referring to it. The value of this option must be the 218 | # base URL from which the finished HTML is served. 219 | # 220 | # html_use_opensearch = '' 221 | 222 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 223 | # html_file_suffix = None 224 | 225 | # Language to be used for generating the HTML full-text search index. 226 | # Sphinx supports the following languages: 227 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 228 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' 229 | # 230 | # html_search_language = 'en' 231 | 232 | # A dictionary with options for the search language support, empty by default. 233 | # 'ja' uses this config value. 234 | # 'zh' user can custom change `jieba` dictionary path. 235 | # 236 | # html_search_options = {'type': 'default'} 237 | 238 | # The name of a javascript file (relative to the configuration directory) that 239 | # implements a search results scorer. If empty, the default will be used. 240 | # 241 | # html_search_scorer = 'scorer.js' 242 | 243 | # Output file base name for HTML help builder. 244 | htmlhelp_basename = 'MutationTestinginPatternsdoc' 245 | 246 | # -- Options for LaTeX output --------------------------------------------- 247 | 248 | latex_elements = { 249 | # The paper size ('letterpaper' or 'a4paper'). 250 | # 251 | # 'papersize': 'letterpaper', 252 | 253 | # The font size ('10pt', '11pt' or '12pt'). 254 | # 255 | # 'pointsize': '10pt', 256 | 257 | # Additional stuff for the LaTeX preamble. 258 | # 259 | # 'preamble': '', 260 | 261 | # Latex figure (float) alignment 262 | # 263 | # 'figure_align': 'htbp', 264 | } 265 | 266 | # Grouping the document tree into LaTeX files. List of tuples 267 | # (source start file, target name, title, 268 | # author, documentclass [howto, manual, or own class]). 269 | latex_documents = [ 270 | (master_doc, 'MutationTestinginPatterns.tex', 'Mutation Testing in Patterns Documentation', 271 | 'Alexander Todorov', 'manual'), 272 | ] 273 | 274 | # The name of an image file (relative to this directory) to place at the top of 275 | # the title page. 276 | # 277 | # latex_logo = None 278 | 279 | # For "manual" documents, if this is true, then toplevel headings are parts, 280 | # not chapters. 281 | # 282 | # latex_use_parts = False 283 | 284 | # If true, show page references after internal links. 285 | # 286 | # latex_show_pagerefs = False 287 | 288 | # If true, show URL addresses after external links. 289 | # 290 | # latex_show_urls = False 291 | 292 | # Documents to append as an appendix to all manuals. 293 | # 294 | # latex_appendices = [] 295 | 296 | # It false, will not define \strong, \code, itleref, \crossref ... but only 297 | # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added 298 | # packages. 299 | # 300 | # latex_keep_old_macro_names = True 301 | 302 | # If false, no module index is generated. 303 | # 304 | # latex_domain_indices = True 305 | 306 | 307 | # -- Options for manual page output --------------------------------------- 308 | 309 | # One entry per manual page. List of tuples 310 | # (source start file, name, description, authors, manual section). 311 | man_pages = [ 312 | (master_doc, 'mutationtestinginpatterns', 'Mutation Testing in Patterns Documentation', 313 | [author], 1) 314 | ] 315 | 316 | # If true, show URL addresses after external links. 317 | # 318 | # man_show_urls = False 319 | 320 | 321 | # -- Options for Texinfo output ------------------------------------------- 322 | 323 | # Grouping the document tree into Texinfo files. List of tuples 324 | # (source start file, target name, title, author, 325 | # dir menu entry, description, category) 326 | texinfo_documents = [ 327 | (master_doc, 'MutationTestinginPatterns', 'Mutation Testing in Patterns Documentation', 328 | author, 'MutationTestinginPatterns', 'One line description of project.', 329 | 'Miscellaneous'), 330 | ] 331 | 332 | # Documents to append as an appendix to all manuals. 333 | # 334 | # texinfo_appendices = [] 335 | 336 | # If false, no module index is generated. 337 | # 338 | # texinfo_domain_indices = True 339 | 340 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 341 | # 342 | # texinfo_show_urls = 'footnote' 343 | 344 | # If true, do not generate a @detailmenu in the "Top" node's menu. 345 | # 346 | # texinfo_no_detailmenu = False 347 | 348 | 349 | # -- Options for Epub output ---------------------------------------------- 350 | 351 | # Bibliographic Dublin Core info. 352 | epub_title = project 353 | epub_author = author 354 | epub_publisher = author 355 | epub_copyright = copyright 356 | 357 | # The basename for the epub file. It defaults to the project name. 358 | # epub_basename = project 359 | 360 | # The HTML theme for the epub output. Since the default themes are not 361 | # optimized for small screen space, using the same theme for HTML and epub 362 | # output is usually not wise. This defaults to 'epub', a theme designed to save 363 | # visual space. 364 | # 365 | # epub_theme = 'epub' 366 | 367 | # The language of the text. It defaults to the language option 368 | # or 'en' if the language is not set. 369 | # 370 | # epub_language = '' 371 | 372 | # The scheme of the identifier. Typical schemes are ISBN or URL. 373 | # epub_scheme = '' 374 | 375 | # The unique identifier of the text. This can be a ISBN number 376 | # or the project homepage. 377 | # 378 | # epub_identifier = '' 379 | 380 | # A unique identification for the text. 381 | # 382 | # epub_uid = '' 383 | 384 | # A tuple containing the cover image and cover page html template filenames. 385 | # 386 | # epub_cover = () 387 | 388 | # A sequence of (type, uri, title) tuples for the guide element of content.opf. 389 | # 390 | # epub_guide = () 391 | 392 | # HTML files that should be inserted before the pages created by sphinx. 393 | # The format is a list of tuples containing the path and title. 394 | # 395 | # epub_pre_files = [] 396 | 397 | # HTML files that should be inserted after the pages created by sphinx. 398 | # The format is a list of tuples containing the path and title. 399 | # 400 | # epub_post_files = [] 401 | 402 | # A list of files that should not be packed into the epub file. 403 | epub_exclude_files = ['search.html'] 404 | 405 | # The depth of the table of contents in toc.ncx. 406 | # 407 | # epub_tocdepth = 3 408 | 409 | # Allow duplicate toc entries. 410 | # 411 | # epub_tocdup = True 412 | 413 | # Choose between 'default' and 'includehidden'. 414 | # 415 | # epub_tocscope = 'default' 416 | 417 | # Fix unsupported image types using the Pillow. 418 | # 419 | # epub_fix_images = False 420 | 421 | # Scale large images. 422 | # 423 | # epub_max_image_width = 0 424 | 425 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 426 | # 427 | # epub_show_urls = 'inline' 428 | 429 | # If false, no index is generated. 430 | # 431 | # epub_use_index = True 432 | -------------------------------------------------------------------------------- /index.rst: -------------------------------------------------------------------------------- 1 | .. include:: README.rst 2 | -------------------------------------------------------------------------------- /locale/README.rst: -------------------------------------------------------------------------------- 1 | How To Translate 2 | **************** 3 | 4 | Thanks to Sphinx, we can easily adding new langauge to translate it. 5 | 6 | 7 | .. image:: http://www.sphinx-doc.org/en/1.5.1/_images/translation.png 8 | 9 | 10 | Adding new language 11 | =================== 12 | 13 | For example, if we want to add japanese:: 14 | 15 | .. code-block:: bash 16 | 17 | sphinx-intl update -p _build/locale -l ja 18 | 19 | Then you will got these directories that contain po files: 20 | 21 | * ./locale/ja/LC_MESSAGES/ 22 | 23 | You can see that in locale directory already have `zh_TW` directory. 24 | 25 | Translating 26 | =========== 27 | 28 | Translate po file under ./locale/jp/LC_MESSAGES directory. The case of 29 | index.po for mutation testing in patterns: 30 | 31 | .. code-block:: bash 32 | 33 | #: ../../README.rst:22 34 | msgid "Mutation testing tools" 35 | msgstr "" 36 | 37 | 38 | Update po files by new pot files 39 | ================================ 40 | 41 | If the documentent is updated, it is necessary to generate update pot files 42 | and to apply differences to translated po files. 43 | 44 | .. code-block:: bash 45 | 46 | sphinx-intl update -p _build/locale 47 | 48 | 49 | Reference 50 | ========= 51 | 52 | `Sphinx Internationalization _ ` 53 | -------------------------------------------------------------------------------- /locale/zh_TW/LC_MESSAGES/README.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) 2016, Alexander Todorov 3 | # This file is distributed under the same license as the Mutation Testing in 4 | # Patterns package. 5 | # FIRST AUTHOR , 2017. 6 | # 7 | #, fuzzy 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: Mutation Testing in Patterns 1.0\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2017-01-23 22:51+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.3.4\n" 20 | 21 | #: ../../README.rst:2 22 | msgid "Mutation Testing in Patterns" 23 | msgstr "突變測試的測試模式" 24 | 25 | #: ../../README.rst:8 26 | msgid "" 27 | "Mutation testing is a technique used to evaluate the quality of existing " 28 | "software tests. Mutation testing involves modifying a program in small " 29 | "ways, for example replacing ``True`` constants with ``False`` and re-" 30 | "running its test suite. When the test suite fails the *mutant* is " 31 | "*killed*. This tells us how good the test suite is. The goal of this " 32 | "paper is to describe different software and testing patterns related " 33 | "using practical examples." 34 | msgstr "" 35 | "突變測試是一個用來衡量現有軟體測試質量的一種技巧。突變測試牽涉到小量變更程式," 36 | "例如將 ``True`` 置換為 ``False`` 然後重新運行測試程式。當測試程式失敗則代表" 37 | "*突變* 被 *殺死*。這能夠告訴我們測試程式的質量為何。這個指南的目標是使用實際的案例" 38 | "來描述不同的軟體以及測試模式。" 39 | 40 | #: ../../README.rst:15 41 | msgid "" 42 | "Some of them are language specific so please see the relevant sections " 43 | "for information about installing and running the necessary tools and " 44 | "examples." 45 | msgstr "" 46 | 47 | #: ../../README.rst:22 48 | msgid "Mutation testing tools" 49 | msgstr "" 50 | 51 | #: ../../README.rst:24 52 | msgid "" 53 | "This is a list of mutation testing tools which are under active use and " 54 | "maintenance from the community:" 55 | msgstr "" 56 | 57 | #: ../../README.rst:27 58 | msgid "Python - `Cosmic Ray `_" 59 | msgstr "" 60 | 61 | #: ../../README.rst:28 62 | msgid "Ruby - `Mutant `_" 63 | msgstr "" 64 | 65 | #: ../../README.rst:29 66 | msgid "Java - `Pitest `_" 67 | msgstr "" 68 | 69 | #: ../../README.rst:30 70 | msgid "JavaScript - `Stryker `_" 71 | msgstr "" 72 | 73 | #: ../../README.rst:31 74 | msgid "PHP - `Humbug `_" 75 | msgstr "" 76 | 77 | #: ../../README.rst:33 78 | msgid "" 79 | "For LLVM-based languages such as C, C++, Rust and Objective-C checkout " 80 | "[mull](https://github.com/mull-project/mull), which looks like a nice " 81 | "project but may not be ready for production use!" 82 | msgstr "" 83 | 84 | #: ../../README.rst:39 85 | msgid "Make sure your tools work" 86 | msgstr "" 87 | 88 | #: ../../README.rst:41 89 | msgid "" 90 | "Mutation testing relies on dynamically modifying program modules and " 91 | "loading the mutated instance from memory. Depending on the language " 92 | "specifics there may be several ways to refer to the same module. In " 93 | "Python the following are equivalent" 94 | msgstr "" 95 | 96 | #: ../../README.rst:56 97 | msgid "The equivalency here is in terms of having access to the same module API." 98 | msgstr "" 99 | 100 | #: ../../README.rst:58 101 | msgid "" 102 | "When we mutation test the right-most ``ham`` module our tools may not be " 103 | "able to resolve to the same module if various importing styles are used. " 104 | "For example see :doc:`python/example_00/README`." 105 | msgstr "" 106 | 107 | #: ../../README.rst:62 108 | msgid "" 109 | "Another possible issue is with programs that load modules dynamically or " 110 | "change the module search path at runtime. Depending on how the mutation " 111 | "testing tool works these operations may interfere with it. For example " 112 | "see :doc:`python/example_01/README`." 113 | msgstr "" 114 | 115 | #: ../../README.rst:69 116 | msgid "Make sure your tests work" 117 | msgstr "" 118 | 119 | #: ../../README.rst:71 120 | msgid "" 121 | "Mutation testing relies on the fact that your test suite will fail when a" 122 | " mutation is introduced. In turn any kind of failure will kill the " 123 | "mutant! The mutation test tool has no way of knowing whether your test " 124 | "suite failed because the mutant tripped one of the assertions or whether " 125 | "it failed due to other reasons." 126 | msgstr "" 127 | 128 | #: ../../README.rst:78 129 | msgid "" 130 | "Make sure your test suite is robust and doesn't randomly fail due to " 131 | "external factors! For example see :doc:`python/example_02/README`." 132 | msgstr "" 133 | 134 | #: ../../README.rst:85 135 | msgid "Divide and conquer" 136 | msgstr "" 137 | 138 | #: ../../README.rst:87 139 | msgid "The basic mutation test algorithm is this" 140 | msgstr "" 141 | 142 | #: ../../README.rst:96 143 | msgid "**mutation-operators** are the things that make small changes to your code" 144 | msgstr "" 145 | 146 | #: ../../README.rst:97 147 | msgid "" 148 | "**operator.sites** are the places in your code where operators can be " 149 | "applied" 150 | msgstr "" 151 | 152 | #: ../../README.rst:101 153 | msgid "" 154 | "As you can see mutation testing is a very expensive operation. For " 155 | "example the `pykickstart `_ " 156 | "project started with 5523 possible mutations and 347 tests, which took on" 157 | " average 100 seconds to execute. A full mutation testing execution needs " 158 | "more than 6 days to complete!" 159 | msgstr "" 160 | 161 | #: ../../README.rst:107 162 | msgid "" 163 | "In practice however not all tests are related to, or even make use of all" 164 | " program modules. This means that mutated operators are only tested via " 165 | "subset of the entire test suite. This fact can be used to reduce " 166 | "execution time by scheduling mutation tests against each individual " 167 | "file/module using only the tests which are related to it. The best case " 168 | "scenario is when your source file names map directly to test file names." 169 | msgstr "" 170 | 171 | #: ../../README.rst:115 172 | msgid "For example something like this" 173 | msgstr "" 174 | 175 | #: ../../README.rst:124 176 | msgid "" 177 | "Where **runTests** executes the mutation testing tool against a single " 178 | "file and executes only the test which is related to this file. For " 179 | "*pykickstart* this approach reduced the entire execution time to little " 180 | "over 6 hours!" 181 | msgstr "" 182 | 183 | #: ../../README.rst:131 184 | msgid "" 185 | "Other tools and languages may have a convention of how tests are " 186 | "organized or which tests are executed by the mutation testing tool. For " 187 | "example in Ruby the convention is to have all tests under " 188 | "`spec/*_spec.rb` which maps with the idea proposed above. Mutant, the " 189 | "Ruby mutation testing tool, uses this convention to find the tests it " 190 | "needs. For Python, on the other hand, the user needs to manually specify " 191 | "which tests should be executed!" 192 | msgstr "" 193 | 194 | #: ../../README.rst:140 195 | msgid "Fail fast" 196 | msgstr "" 197 | 198 | #: ../../README.rst:142 199 | msgid "" 200 | "Mutation testing relies on your test suite failing when it detects a " 201 | "faulty mutation. It doesn't matter which particular test has failed " 202 | "because most of the tools have no way of telling whether or not the " 203 | "failed test is related to the mutated code. That means it also doesn't " 204 | "matter if there are more than one failing tests so you can use this to " 205 | "your advantage." 206 | msgstr "" 207 | 208 | #: ../../README.rst:148 209 | msgid "" 210 | "Whenever your test tools and framework support the **fail fast** option " 211 | "make use of it to reduce test execution time even more!" 212 | msgstr "" 213 | 214 | #: ../../README.rst:152 215 | msgid "Refactor comparison to empty string" 216 | msgstr "" 217 | 218 | #: ../../README.rst:154 219 | msgid "" 220 | "Comparison operators may be mutated with each other which gives, " 221 | "depending on the langauge, about 10 possible mutations." 222 | msgstr "" 223 | 224 | #: ../../README.rst:157 225 | msgid "" 226 | "Every time ``S`` is not an empty string the following 3 variants are " 227 | "evaluated to ``True``:" 228 | msgstr "" 229 | 230 | #: ../../README.rst:160 231 | msgid "``if S != \"\"``" 232 | msgstr "" 233 | 234 | #: ../../README.rst:161 235 | msgid "``if S > \"\"``" 236 | msgstr "" 237 | 238 | #: ../../README.rst:162 239 | msgid "``if S not in \"\"``" 240 | msgstr "" 241 | 242 | #: ../../README.rst:164 243 | msgid "" 244 | "The existing test cases pass and these mutations are never killed. In " 245 | "languages like Python, non-empty sequences are evaluated to `True` in " 246 | "boolean context and you don't need to use comparisons. This reduces the " 247 | "number of possible mutations." 248 | msgstr "" 249 | 250 | #: ../../README.rst:169 251 | msgid "For Python you may use the *emptystring* extension of pylint" 252 | msgstr "" 253 | 254 | #: ../../README.rst:175 255 | msgid "" 256 | "See `pylint #1183 `_ for more " 257 | "info and :doc:`python/example_03/README` for an example." 258 | msgstr "" 259 | 260 | #: ../../README.rst:180 261 | msgid "" 262 | "In some cases empty string is an acceptable value and refactoring will " 263 | "change the behavior of the program! Be careful when doing this." 264 | msgstr "" 265 | 266 | #: ../../README.rst:185 267 | msgid "Refactor comparison to zero" 268 | msgstr "" 269 | 270 | #: ../../README.rst:187 271 | msgid "" 272 | "This is similar to the previous section but for integer values. For " 273 | "Python use the *comparetozero* extension to detect possible offenses." 274 | msgstr "" 275 | 276 | #: ../../README.rst:194 277 | msgid "" 278 | "See `pylint #1243 `_ for more " 279 | "info." 280 | msgstr "" 281 | 282 | #: ../../README.rst:198 283 | msgid "Python: Refactor len(X) comparisons to zero" 284 | msgstr "" 285 | 286 | #: ../../README.rst:200 287 | msgid "" 288 | "Every time ``X`` is not an empty sequence the following variants are " 289 | "evaluated to ``True`` and result in surviving mutants:" 290 | msgstr "" 291 | 292 | #: ../../README.rst:203 293 | msgid "``if len(X) != 0``" 294 | msgstr "" 295 | 296 | #: ../../README.rst:204 297 | msgid "``if len(X) > 0``" 298 | msgstr "" 299 | 300 | #: ../../README.rst:206 301 | msgid "" 302 | "Additionally if we don't have a test to validate the ``if`` body, for " 303 | "example that it raises an exception, then the following mutation will " 304 | "also survive:" 305 | msgstr "" 306 | 307 | #: ../../README.rst:210 308 | msgid "``if len(X) < 0``" 309 | msgstr "" 310 | 311 | #: ../../README.rst:212 312 | msgid "Refactoring this to ::" 313 | msgstr "" 314 | 315 | #: ../../README.rst:218 316 | msgid "" 317 | "is the best way to go about it. This also reduces the total number of " 318 | "possible mutations. A more complicated example, using two lists and " 319 | "boolean operation can be seen below." 320 | msgstr "" 321 | 322 | #: ../../README.rst:229 323 | msgid "Consider the following example" 324 | msgstr "" 325 | 326 | #: ../../README.rst:240 327 | msgid "" 328 | "Similar to previous examples the ``len() > 0`` expression can be " 329 | "refactored. Since joining an empty list will produce an empty string the " 330 | "``else`` block is not necessary. The example can be re-written as" 331 | msgstr "" 332 | 333 | #: ../../README.rst:251 334 | msgid "" 335 | "In pylint 2.0 there is a new checker called *len-as-condition* which will" 336 | " warn you about code snippets that compare the result of a `len()` call " 337 | "to zero. For more information see `pylint #1154 " 338 | "`_." 339 | msgstr "" 340 | 341 | #: ../../README.rst:256 342 | msgid "For practical example see :doc:`python/example_05/README`." 343 | msgstr "" 344 | 345 | #: ../../README.rst:260 346 | msgid "Python: Refactor if len(list) == 1" 347 | msgstr "" 348 | 349 | #: ../../README.rst:262 350 | msgid "The following code" 351 | msgstr "" 352 | 353 | #: ../../README.rst:272 354 | msgid "can be refactored into this" 355 | msgstr "" 356 | 357 | #: ../../README.rst:283 358 | msgid "" 359 | "This refactoring may have side effects when the list length is greater " 360 | "than 1, e.g. 2. Depending on your program this may ot may-not be the " 361 | "case." 362 | msgstr "" 363 | 364 | #: ../../README.rst:288 365 | msgid "Testing for X != 1" 366 | msgstr "" 367 | 368 | #: ../../README.rst:290 369 | msgid "When testing the not equals condition we need at least 3 test cases:" 370 | msgstr "" 371 | 372 | #: ../../README.rst:292 373 | msgid "Test with value smaller than the condition" 374 | msgstr "" 375 | 376 | #: ../../README.rst:293 377 | msgid "Test with value that equals the condition" 378 | msgstr "" 379 | 380 | #: ../../README.rst:294 381 | msgid "Test with value greater than the condition" 382 | msgstr "" 383 | 384 | #: ../../README.rst:296 385 | msgid "" 386 | "Most often we do test with value that equals the condition (the golden " 387 | "scenario) and either one of the other bordering values but not both. This" 388 | " leads to mutations which are not killed." 389 | msgstr "" 390 | 391 | #: ../../README.rst:300 392 | msgid "Example :doc:`python/example_04/README`." 393 | msgstr "" 394 | 395 | #: ../../README.rst:304 396 | msgid "Python: Refactor if X is None" 397 | msgstr "" 398 | 399 | #: ../../README.rst:306 400 | msgid "" 401 | "When X has a value of None the following mutations are equivalent are " 402 | "will survive:" 403 | msgstr "" 404 | 405 | #: ../../README.rst:309 406 | msgid "``if X is None:``" 407 | msgstr "" 408 | 409 | #: ../../README.rst:310 410 | msgid "``if X == None:``" 411 | msgstr "" 412 | 413 | #: ../../README.rst:312 414 | msgid "" 415 | "in addition static analyzers may report comparison to None as an offence." 416 | " To handle this refactor ``if X is None:`` to ``if not X:`` when " 417 | "possible." 418 | msgstr "" 419 | 420 | #: ../../README.rst:317 421 | msgid "For example see :doc:`python/example_06/README`." 422 | msgstr "" 423 | 424 | #: ../../README.rst:321 425 | msgid "Python: Refactor if X is not None" 426 | msgstr "" 427 | 428 | #: ../../README.rst:323 429 | msgid "" 430 | "This is the opposite of the previous section. Refactor ``if X is not " 431 | "None:`` to ``if X:``. For example see :doc:`python/example_11/README`." 432 | msgstr "" 433 | 434 | #: ../../README.rst:330 435 | msgid "Python: Testing __eq__ and __ne__" 436 | msgstr "" 437 | 438 | #: ../../README.rst:332 439 | msgid "" 440 | "When objects are compared by comparing their attributes then full " 441 | "mutation test coverage can be achieved by comparing the object to itself," 442 | " comparing to None, comparing two objects with the same attribute values " 443 | "and then test by changing the attributes one by one." 444 | msgstr "" 445 | 446 | #: ../../README.rst:337 447 | msgid "For example see :doc:`python/example_07/README`." 448 | msgstr "" 449 | 450 | #: ../../README.rst:339 451 | msgid "Consider if there is the following mistake in the example:" 452 | msgstr "" 453 | 454 | #: ../../README.rst:349 455 | msgid "" 456 | "Notice the redundant `self.device and` in the expression above! When " 457 | "`self.device` contains a value (string in this case) the expression is " 458 | "equivalent to `self.device == other.device`. On the other hand when " 459 | "`self.device` is `None` or an empty string the expression will always " 460 | "return `False`!" 461 | msgstr "" 462 | 463 | #: ../../README.rst:354 464 | msgid "" 465 | "If we have all of the above tests (which mutation testing has identified)" 466 | " then our test suite will fail and properly detect the defect ::" 467 | msgstr "" 468 | 469 | #: ../../README.rst:374 470 | msgid "" 471 | "At the time of writing *Cosmic Ray* did not fail if there was a failure " 472 | "during the baseline test execution and all mutations would be reported as" 473 | " killed because, well the test suite failed! This was reported in `CR#111" 474 | " `_ and fixed in " 475 | "`CR#181 `_." 476 | msgstr "" 477 | 478 | #: ../../README.rst:382 479 | msgid "Python: Testing sequence of if == int" 480 | msgstr "" 481 | 482 | #: ../../README.rst:384 483 | msgid "To completely test the following pattern" 484 | msgstr "" 485 | 486 | #: ../../README.rst:395 487 | msgid "" 488 | "you need to test with all descrete values plus values outside the allowed" 489 | " set. For example see :doc:`python/example_08/README`" 490 | msgstr "" 491 | 492 | #: ../../README.rst:400 493 | msgid "Python: Testing sequence of if == string" 494 | msgstr "" 495 | 496 | #: ../../README.rst:402 497 | msgid "To fully test the following pattern" 498 | msgstr "" 499 | 500 | #: ../../README.rst:413 501 | msgid "" 502 | "you need to test with all possible string values as well as with values " 503 | "outside the allowed set. For example see " 504 | ":doc:`python/example_08/README_str`." 505 | msgstr "" 506 | 507 | #: ../../README.rst:418 508 | msgid "Python: Missing or extra parameters" 509 | msgstr "" 510 | 511 | #: ../../README.rst:420 512 | msgid "" 513 | "Depending on how your method signature is defined it is possible to " 514 | "either accept additional parameters which are not needed or forget to " 515 | "pass along parameters which control internal behavior. Mutation testing " 516 | "helps you identify those cases and adjust your code accordingly." 517 | msgstr "" 518 | 519 | #: ../../README.rst:425 520 | msgid "For example see :doc:`python/example_09/README`." 521 | msgstr "" 522 | 523 | #: ../../README.rst:429 524 | msgid "Python: Testing for 0 <= X < 100" 525 | msgstr "" 526 | 527 | #: ../../README.rst:432 528 | msgid "When testing numerical ranges we need at least 4 tests:" 529 | msgstr "" 530 | 531 | #: ../../README.rst:434 532 | msgid "Test with both border values" 533 | msgstr "" 534 | 535 | #: ../../README.rst:435 536 | msgid "Test with values outside the range, ideally +1/-1" 537 | msgstr "" 538 | 539 | #: ../../README.rst:436 540 | msgid "" 541 | "Testing with a value in the middle of the range is not required for full " 542 | "mutation coverage!" 543 | msgstr "" 544 | 545 | #: ../../README.rst:439 546 | msgid "For example see :doc:`python/example_12/README`." 547 | msgstr "" 548 | 549 | #: ../../README.rst:443 550 | msgid "Python: On boolean expressions" 551 | msgstr "" 552 | 553 | #: ../../README.rst:445 554 | msgid "" 555 | "When dealing with non-trivial boolean expressions mutation testing often " 556 | "helps put things into perspective. It causes you to rethink the " 557 | "expression which often leads to refactoring and killing mutants. For " 558 | "example see :doc:`python/example_10/README`." 559 | msgstr "" 560 | 561 | #: ../../README.rst:452 562 | msgid "Refactor multiple boolean expressions" 563 | msgstr "" 564 | 565 | #: ../../README.rst:454 566 | msgid "" 567 | "Consider the following code where the expression left of ``and`` is " 568 | "always the same" 569 | msgstr "" 570 | 571 | #: ../../README.rst:471 572 | msgid "" 573 | "This can easily be refactored by removing the ``name == \"method\"`` " 574 | "expression and making the subsequent if statements nested under the first" 575 | " one." 576 | msgstr "" 577 | 578 | #: ../../README.rst:489 579 | msgid "" 580 | "The refactored code is shorter and provides less mutation sites thus " 581 | "reducing overall mutation test execution time. This code can be " 582 | "refactored even more aggressively into" 583 | msgstr "" 584 | 585 | #: ../../README.rst:504 586 | msgid "Indices and tables" 587 | msgstr "" 588 | 589 | #: ../../README.rst:506 590 | msgid ":ref:`genindex`" 591 | msgstr "" 592 | 593 | #: ../../README.rst:507 594 | msgid ":ref:`modindex`" 595 | msgstr "" 596 | 597 | #: ../../README.rst:508 598 | msgid ":ref:`search`" 599 | msgstr "" 600 | 601 | -------------------------------------------------------------------------------- /locale/zh_TW/LC_MESSAGES/index.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) 2016, Alexander Todorov 3 | # This file is distributed under the same license as the Mutation Testing in 4 | # Patterns package. 5 | # FIRST AUTHOR , 2017. 6 | # 7 | #, fuzzy 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: Mutation Testing in Patterns 1.0\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2017-01-23 22:51+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.3.4\n" 20 | 21 | #: ../../README.rst:2 22 | msgid "Mutation Testing in Patterns" 23 | msgstr "突變測試的測試模式 - Mutation Testing in Patterns" 24 | 25 | #: ../../README.rst:8 26 | msgid "" 27 | "Mutation testing is a technique used to evaluate the quality of existing " 28 | "software tests. Mutation testing involves modifying a program in small " 29 | "ways, for example replacing ``True`` constants with ``False`` and re-" 30 | "running its test suite. When the test suite fails the *mutant* is " 31 | "*killed*. This tells us how good the test suite is. The goal of this " 32 | "paper is to describe different software and testing patterns related " 33 | "using practical examples." 34 | msgstr "" 35 | "突變測試是一個用來衡量現有軟體測試程式質量的一種技巧。突變測試牽涉到小量變更程式," 36 | "例如將 ``True`` 置換為 ``False``,然後重新運行測試程式。當測試程式失敗則代表" 37 | "*突變* 被 *殺死*。這能夠告訴我們測試程式的質量為何。這個指南的目標是使用實際的案例" 38 | "來描述不同的軟體以及測試模式。" 39 | 40 | #: ../../README.rst:15 41 | msgid "" 42 | "Some of them are language specific so please see the relevant sections " 43 | "for information about installing and running the necessary tools and " 44 | "examples." 45 | msgstr "某些部份的描述與該語言特性有關,敬請參閱相關的章節來取得有關安裝以及" 46 | "運行必要工具、範例的資訊。" 47 | 48 | #: ../../README.rst:22 49 | msgid "Mutation testing tools" 50 | msgstr "突變測試工具 - Mutation testing tools" 51 | 52 | #: ../../README.rst:24 53 | msgid "" 54 | "This is a list of mutation testing tools which are under active use and " 55 | "maintenance from the community:" 56 | msgstr "這是一份突變測試工具的列表,列表中的工具都有活躍使用以及社群維護。" 57 | 58 | #: ../../README.rst:27 59 | msgid "Python - `Cosmic Ray `_" 60 | msgstr "" 61 | 62 | #: ../../README.rst:28 63 | msgid "Ruby - `Mutant `_" 64 | msgstr "" 65 | 66 | #: ../../README.rst:29 67 | msgid "Java - `Pitest `_" 68 | msgstr "" 69 | 70 | #: ../../README.rst:30 71 | msgid "JavaScript - `Stryker `_" 72 | msgstr "" 73 | 74 | #: ../../README.rst:31 75 | msgid "PHP - `Humbug `_" 76 | msgstr "" 77 | 78 | #: ../../README.rst:33 79 | msgid "" 80 | "For LLVM-based languages such as C, C++, Rust and Objective-C checkout " 81 | "[mull](https://github.com/mull-project/mull), which looks like a nice " 82 | "project but may not be ready for production use!" 83 | msgstr "LLVM-based 的語言,如 C、C++、Rust、與 Objective-C 可以參考 `mull `_ " 84 | ",一個看起來不錯專案,但是可能還沒 ready for production。" 85 | 86 | #: ../../README.rst:39 87 | msgid "Make sure your tools work" 88 | msgstr "確保你的工具可用 - Make sure your tools work " 89 | 90 | #: ../../README.rst:41 91 | msgid "" 92 | "Mutation testing relies on dynamically modifying program modules and " 93 | "loading the mutated instance from memory. Depending on the language " 94 | "specifics there may be several ways to refer to the same module. In " 95 | "Python the following are equivalent" 96 | msgstr "突變測試依賴於動態更改程式模組以及讀取從記憶體中變異 instance。" 97 | "根據不同的程式語言會有不同的方式來指涉同一個模組。在 Python 中下面的程式碼" 98 | "是等價的:" 99 | 100 | #: ../../README.rst:56 101 | msgid "The equivalency here is in terms of having access to the same module API." 102 | msgstr "\"等價\"在這邊的意思是訪問了相同的模組 API。" 103 | 104 | #: ../../README.rst:58 105 | msgid "" 106 | "When we mutation test the right-most ``ham`` module our tools may not be " 107 | "able to resolve to the same module if various importing styles are used. " 108 | "For example see :doc:`python/example_00/README`." 109 | msgstr "如果有許多不同的 importing styles 被使用的話," 110 | "當我們對最左邊的 ``ham`` 模組進行突變,我們的工具可能沒有辦法解析出相同的模組。" 111 | "請參考範例: :doc:`python/example_00/README`." 112 | 113 | #: ../../README.rst:62 114 | msgid "" 115 | "Another possible issue is with programs that load modules dynamically or " 116 | "change the module search path at runtime. Depending on how the mutation " 117 | "testing tool works these operations may interfere with it. For example " 118 | "see :doc:`python/example_01/README`." 119 | msgstr "另一個可能的問題是程式會動態的讀取模組,或是在執行時期改變模組搜尋的路徑。" 120 | "根據突變測試工具的不同,這些操作可能會產生一些相關的問題。範例請參考:" 121 | ":doc:`python/example_01/README`" 122 | 123 | #: ../../README.rst:69 124 | msgid "Make sure your tests work" 125 | msgstr "確保你的測試可行 - Make sure your tests work" 126 | 127 | #: ../../README.rst:71 128 | msgid "" 129 | "Mutation testing relies on the fact that your test suite will fail when a" 130 | " mutation is introduced. In turn any kind of failure will kill the " 131 | "mutant! The mutation test tool has no way of knowing whether your test " 132 | "suite failed because the mutant tripped one of the assertions or whether " 133 | "it failed due to other reasons." 134 | msgstr "突變測試的可靠度是基於當引入突變時,你的測試套件 (test suite) 會失敗 (fail)。" 135 | "這代表任何的失敗都會殺死突變!突變測試工具沒有辦法得知你的測試套件是因為突變引起一個" 136 | "斷言還是因為其他原因而失敗。" 137 | 138 | #: ../../README.rst:78 139 | msgid "" 140 | "Make sure your test suite is robust and doesn't randomly fail due to " 141 | "external factors! For example see :doc:`python/example_02/README`." 142 | msgstr "確保你的測試套件 (test suite) 是穩健的,並且不會因為外部因素" 143 | "而隨機的失敗!請參考範例 :doc:`python/example_02/README`。" 144 | 145 | #: ../../README.rst:85 146 | msgid "Divide and conquer" 147 | msgstr "分治法 - Divide and Conquer" 148 | 149 | #: ../../README.rst:87 150 | msgid "The basic mutation test algorithm is this" 151 | msgstr "最為基本的突變測試演算法如下:" 152 | 153 | #: ../../README.rst:96 154 | msgid "**mutation-operators** are the things that make small changes to your code" 155 | msgstr "**mutation-operators** 是指那些在程式中有微小變動的部份。 " 156 | 157 | #: ../../README.rst:97 158 | msgid "" 159 | "**operator.sites** are the places in your code where operators can be " 160 | "applied" 161 | msgstr "**operator.sites** 代表在你的程式中突變運算子可以被使用的地方。" 162 | 163 | #: ../../README.rst:101 164 | msgid "" 165 | "As you can see mutation testing is a very expensive operation. For " 166 | "example the `pykickstart `_ " 167 | "project started with 5523 possible mutations and 347 tests, which took on" 168 | " average 100 seconds to execute. A full mutation testing execution needs " 169 | "more than 6 days to complete!" 170 | msgstr "我們可以得知,突變測試是一個代價非常高的操作。舉例而言," 171 | "`pykickstart `_ " 172 | "專案約有 5523 個可能的突變與 347 個測試,這會讓測試平均時間來到 100 秒左右。" 173 | "一個完整的突變測試需要花 6 天的時間才能夠運行完成。" 174 | 175 | #: ../../README.rst:107 176 | msgid "" 177 | "In practice however not all tests are related to, or even make use of all" 178 | " program modules. This means that mutated operators are only tested via " 179 | "subset of the entire test suite. This fact can be used to reduce " 180 | "execution time by scheduling mutation tests against each individual " 181 | "file/module using only the tests which are related to it. The best case " 182 | "scenario is when your source file names map directly to test file names." 183 | msgstr "不過,在實務上並不是所有的測試都有必要。這代表突變運算子只需要測試" 184 | "整個測試套件中的子集就可以。如此可以透過對每個獨立的檔案或是模組排程" 185 | "測試那些相關的部份來降低執行的時間。最佳情況是所有的原始檔名都有相對應的測試檔案名稱。" 186 | 187 | #: ../../README.rst:115 188 | msgid "For example something like this" 189 | msgstr "舉例而言:" 190 | 191 | #: ../../README.rst:124 192 | msgid "" 193 | "Where **runTests** executes the mutation testing tool against a single " 194 | "file and executes only the test which is related to this file. For " 195 | "*pykickstart* this approach reduced the entire execution time to little " 196 | "over 6 hours!" 197 | msgstr "**runTests** 只對於原始檔在有相對應的測試程式時才會運行。這個方法讓" 198 | "**pykickstart** 的突變測試運行時間減少到只需要6個小時!。" 199 | 200 | #: ../../README.rst:131 201 | msgid "" 202 | "Other tools and languages may have a convention of how tests are " 203 | "organized or which tests are executed by the mutation testing tool. For " 204 | "example in Ruby the convention is to have all tests under " 205 | "`spec/*_spec.rb` which maps with the idea proposed above. Mutant, the " 206 | "Ruby mutation testing tool, uses this convention to find the tests it " 207 | "needs. For Python, on the other hand, the user needs to manually specify " 208 | "which tests should be executed!" 209 | msgstr "其他語言或是工具可能會有其組織突變測試的慣例。舉例而言," 210 | "Ruby 的慣例是將 `spec/*_spec.rb` 的測試以前面提到的方式表示。Mutant," 211 | "Ruby 的突變測試工具,會使用這個慣例來找尋需要的測試。Python 的話,使用者" 212 | "則必須要自己決定哪個部份需要被執行!" 213 | 214 | #: ../../README.rst:140 215 | msgid "Fail fast" 216 | msgstr "快速的失敗 - Fail fast" 217 | 218 | #: ../../README.rst:142 219 | msgid "" 220 | "Mutation testing relies on your test suite failing when it detects a " 221 | "faulty mutation. It doesn't matter which particular test has failed " 222 | "because most of the tools have no way of telling whether or not the " 223 | "failed test is related to the mutated code. That means it also doesn't " 224 | "matter if there are more than one failing tests so you can use this to " 225 | "your advantage." 226 | msgstr "突變測試是基於當你的測試套件 (test suite) 偵測到錯誤的突變時能夠失敗。" 227 | "他不管是因為哪個特定的測試失敗,因為大部分的工具也無法指出失敗的原因是因為" 228 | "突變還是什麼其他原因。這代表他也不管是不是有多個失敗的測試,你可以把" 229 | "這樣的特性當作是一個特點來使用。" 230 | 231 | #: ../../README.rst:148 232 | msgid "" 233 | "Whenever your test tools and framework support the **fail fast** option " 234 | "make use of it to reduce test execution time even more!" 235 | msgstr "當你的測試工具或是框架支援 **fail fast** 選項的時候" 236 | "記得使用該選項來減少測試執行時間!" 237 | 238 | #: ../../README.rst:152 239 | msgid "Refactor comparison to empty string" 240 | msgstr "重構對於空字串的比較" 241 | 242 | #: ../../README.rst:154 243 | msgid "" 244 | "Comparison operators may be mutated with each other which gives, " 245 | "depending on the langauge, about 10 possible mutations." 246 | msgstr "根據不同語言,比較運算子可能突變不同的數量。大約而言會有 10 種不同的突變。" 247 | 248 | #: ../../README.rst:157 249 | msgid "" 250 | "Every time ``S`` is not an empty string the following 3 variants are " 251 | "evaluated to ``True``:" 252 | msgstr "當 ``S`` 不是個空字串的時候,下面 3 個變體會被當作是 ``True``:" 253 | 254 | #: ../../README.rst:160 255 | msgid "``if S != \"\"``" 256 | msgstr "" 257 | 258 | #: ../../README.rst:161 259 | msgid "``if S > \"\"``" 260 | msgstr "" 261 | 262 | #: ../../README.rst:162 263 | msgid "``if S not in \"\"``" 264 | msgstr "" 265 | 266 | #: ../../README.rst:164 267 | msgid "" 268 | "The existing test cases pass and these mutations are never killed. In " 269 | "languages like Python, non-empty sequences are evaluated to `True` in " 270 | "boolean context and you don't need to use comparisons. This reduces the " 271 | "number of possible mutations." 272 | msgstr "因此存在的測試案例會通過,而突變永遠沒有被殺掉。如 Python 這樣的語言," 273 | "非空字串都會在布林運算中被視為 `True`,因此不需要去比較他。這降低了" 274 | "可能突變的數量。" 275 | 276 | #: ../../README.rst:169 277 | msgid "For Python you may use the *emptystring* extension of pylint" 278 | msgstr "Python 中可以使用 pylint 的 *emptystring* 套件:" 279 | 280 | #: ../../README.rst:175 281 | msgid "" 282 | "See `pylint #1183 `_ for more " 283 | "info and :doc:`python/example_03/README` for an example." 284 | msgstr "更多資訊請參見 `pylint #1183 `_ ," 285 | "相關範例請參考::doc:`python/example_03/README`" 286 | 287 | #: ../../README.rst:180 288 | msgid "" 289 | "In some cases empty string is an acceptable value and refactoring will " 290 | "change the behavior of the program! Be careful when doing this." 291 | msgstr "某些狀況下空字串是一個合法的值,而重構可能會造成行為改變。小心,小心再小心。" 292 | 293 | #: ../../README.rst:185 294 | msgid "Refactor comparison to zero" 295 | msgstr "重構與 0 比較的狀況" 296 | 297 | #: ../../README.rst:187 298 | msgid "" 299 | "This is similar to the previous section but for integer values. For " 300 | "Python use the *comparetozero* extension to detect possible offenses." 301 | msgstr "這與前一個章節有些相似,不過是比較整數數值。Python 可以透過 *comparetozero*" 302 | "找出可能的錯誤。" 303 | 304 | #: ../../README.rst:194 305 | msgid "" 306 | "See `pylint #1243 `_ for more " 307 | "info." 308 | msgstr "更多訊息請參考 `pylint #1243 `_ " 309 | 310 | #: ../../README.rst:198 311 | msgid "Python: Refactor len(X) comparisons to zero" 312 | msgstr "Python: 重構 len(X) 跟 0 比較" 313 | 314 | #: ../../README.rst:200 315 | msgid "" 316 | "Every time ``X`` is not an empty sequence the following variants are " 317 | "evaluated to ``True`` and result in surviving mutants:" 318 | msgstr "當 ``X`` 不是空的序列時,下面的表達式相當於與 ``True`` 比較," 319 | "而其結果將會在突變中存活下來。" 320 | 321 | #: ../../README.rst:203 322 | msgid "``if len(X) != 0``" 323 | msgstr "" 324 | 325 | #: ../../README.rst:204 326 | msgid "``if len(X) > 0``" 327 | msgstr "" 328 | 329 | #: ../../README.rst:206 330 | msgid "" 331 | "Additionally if we don't have a test to validate the ``if`` body, for " 332 | "example that it raises an exception, then the following mutation will " 333 | "also survive:" 334 | msgstr "此外,如果我們不對 ``if`` 的主體做驗證,例如說他會發起 exception," 335 | "則下列的突變也會存活下來:" 336 | 337 | #: ../../README.rst:210 338 | msgid "``if len(X) < 0``" 339 | msgstr "" 340 | 341 | #: ../../README.rst:212 342 | msgid "Refactoring this to ::" 343 | msgstr "我們可以重構成:" 344 | 345 | #: ../../README.rst:218 346 | msgid "" 347 | "is the best way to go about it. This also reduces the total number of " 348 | "possible mutations. A more complicated example, using two lists and " 349 | "boolean operation can be seen below." 350 | msgstr "是最好的方式。這同時可以降低突變的總數量。可以參考下面使用兩個 lists" 351 | "以及布林運算的更為複雜的範例。" 352 | 353 | #: ../../README.rst:229 354 | msgid "Consider the following example" 355 | msgstr "以下面的程式碼為例:" 356 | 357 | #: ../../README.rst:240 358 | msgid "" 359 | "Similar to previous examples the ``len() > 0`` expression can be " 360 | "refactored. Since joining an empty list will produce an empty string the " 361 | "``else`` block is not necessary. The example can be re-written as" 362 | msgstr "與前面的範例相近,我們可以將 ``len() > 0`` 重構。對一個空的 list" 363 | "joining 會產生出一個空字串,因此 ``else`` block 是不必要的。因此範例可以" 364 | "改寫為:" 365 | 366 | #: ../../README.rst:251 367 | msgid "" 368 | "In pylint 2.0 there is a new checker called *len-as-condition* which will" 369 | " warn you about code snippets that compare the result of a `len()` call " 370 | "to zero. For more information see `pylint #1154 " 371 | "`_." 372 | msgstr "pylint 2.0 中有個新的檢查叫作 *len-as-condition*,他會在你的程式中" 373 | "有對 `len()` 的結果與 0 進行比較時提出警告。更多資訊請參考 `pylint #1154 " 374 | "`_ " 375 | 376 | #: ../../README.rst:256 377 | msgid "For practical example see :doc:`python/example_05/README`." 378 | msgstr "範例請參考::doc:`python/example_05/README`" 379 | 380 | #: ../../README.rst:260 381 | msgid "Python: Refactor if len(list) == 1" 382 | msgstr "Python: 重構 if len(list) == 1" 383 | 384 | #: ../../README.rst:262 385 | msgid "The following code" 386 | msgstr "以下的程式碼" 387 | 388 | #: ../../README.rst:272 389 | msgid "can be refactored into this" 390 | msgstr "可以重構為:" 391 | 392 | #: ../../README.rst:283 393 | msgid "" 394 | "This refactoring may have side effects when the list length is greater " 395 | "than 1, e.g. 2. Depending on your program this may ot may-not be the " 396 | "case." 397 | msgstr "當 list 長度大於 1 的時候 (例如:2),這個重構可能會有副作用的產生。" 398 | "這取決於你的程式,這可能,也可能不是其中的一個測試案例。" 399 | 400 | #: ../../README.rst:288 401 | msgid "Testing for X != 1" 402 | msgstr "測試 X != 1" 403 | 404 | #: ../../README.rst:290 405 | msgid "When testing the not equals condition we need at least 3 test cases:" 406 | msgstr "當我們測試不等於的條件時,我們需要至少3個測試案例:" 407 | 408 | #: ../../README.rst:292 409 | msgid "Test with value smaller than the condition" 410 | msgstr "測試小於條件的數值" 411 | 412 | #: ../../README.rst:293 413 | msgid "Test with value that equals the condition" 414 | msgstr "測試等於條件的數值" 415 | 416 | #: ../../README.rst:294 417 | msgid "Test with value greater than the condition" 418 | msgstr "測試大於條件的數值" 419 | 420 | #: ../../README.rst:296 421 | msgid "" 422 | "Most often we do test with value that equals the condition (the golden " 423 | "scenario) and either one of the other bordering values but not both. This" 424 | " leads to mutations which are not killed." 425 | msgstr "通常我們都會測試相等的條件 (最好狀況) 然後測試大於或小於的其中一個狀況。" 426 | "這讓突變可能不會被殺死。" 427 | 428 | #: ../../README.rst:300 429 | msgid "Example :doc:`python/example_04/README`." 430 | msgstr "範例請參考::doc:`python/example_04/README`" 431 | 432 | #: ../../README.rst:304 433 | msgid "Python: Refactor if X is None" 434 | msgstr "Python: 重構 if X is None" 435 | 436 | #: ../../README.rst:306 437 | msgid "" 438 | "When X has a value of None the following mutations are equivalent are " 439 | "will survive:" 440 | msgstr "當 X 有 None 的值的時候,下面的突變是相等的且會存活下來:" 441 | 442 | #: ../../README.rst:309 443 | msgid "``if X is None:``" 444 | msgstr "" 445 | 446 | #: ../../README.rst:310 447 | msgid "``if X == None:``" 448 | msgstr "" 449 | 450 | #: ../../README.rst:312 451 | msgid "" 452 | "in addition static analyzers may report comparison to None as an offence." 453 | " To handle this refactor ``if X is None:`` to ``if not X:`` when " 454 | "possible." 455 | msgstr "此外,靜態分析可能會將這種比較視為一種錯誤。在可行的情況下應該要將" 456 | "``if X is None`` 重構為 ``if not X``。" 457 | 458 | #: ../../README.rst:317 459 | msgid "For example see :doc:`python/example_06/README`." 460 | msgstr "請參考範例: :doc:`python/example_06/README`." 461 | 462 | #: ../../README.rst:321 463 | msgid "Python: Refactor if X is not None" 464 | msgstr "Python: 重構 if X is not None" 465 | 466 | #: ../../README.rst:323 467 | msgid "" 468 | "This is the opposite of the previous section. Refactor ``if X is not " 469 | "None:`` to ``if X:``. For example see :doc:`python/example_11/README`." 470 | msgstr "這是前一節的相反情況。將 ``if X is not None:`` 重構為 ``if X:``。" 471 | "請參考範例::doc:`python/example_11/README`" 472 | 473 | #: ../../README.rst:330 474 | msgid "Python: Testing __eq__ and __ne__" 475 | msgstr "Python: 測試 __eq__ 以及 __ne__" 476 | 477 | #: ../../README.rst:332 478 | msgid "" 479 | "When objects are compared by comparing their attributes then full " 480 | "mutation test coverage can be achieved by comparing the object to itself," 481 | " comparing to None, comparing two objects with the same attribute values " 482 | "and then test by changing the attributes one by one." 483 | msgstr "當物件使用自己的比較運算方法比較時,完整的突變測試可以透過比較物件本身、" 484 | "與 None 比較、與兩個相同 attribute 數值的物件比較以及一個一個改變 attribute 來測試。" 485 | 486 | #: ../../README.rst:337 487 | msgid "For example see :doc:`python/example_07/README`." 488 | msgstr "請參考範例::doc:`python/example_07/README`" 489 | 490 | #: ../../README.rst:339 491 | msgid "Consider if there is the following mistake in the example:" 492 | msgstr "以下面的程式碼的錯誤為例:" 493 | 494 | #: ../../README.rst:349 495 | msgid "" 496 | "Notice the redundant `self.device and` in the expression above! When " 497 | "`self.device` contains a value (string in this case) the expression is " 498 | "equivalent to `self.device == other.device`. On the other hand when " 499 | "`self.device` is `None` or an empty string the expression will always " 500 | "return `False`!" 501 | msgstr "注意到表達式前面冗餘的 `self.devie and`!當 `self.device` 內有值" 502 | "的時候 (這邊是字串),表達式與 `self.device == other.device` 相等。" 503 | "當 `self.device` 是 `None` 或是空字串的時候,表達式永遠會回傳 `False`!" 504 | 505 | #: ../../README.rst:354 506 | msgid "" 507 | "If we have all of the above tests (which mutation testing has identified)" 508 | " then our test suite will fail and properly detect the defect ::" 509 | msgstr "如果我們有前面所以的測試的話 (那些突變測試已經確定的) 則我們的測試套件" 510 | "會失敗並且正確的被偵測到:" 511 | 512 | #: ../../README.rst:374 513 | msgid "" 514 | "At the time of writing *Cosmic Ray* did not fail if there was a failure " 515 | "during the baseline test execution and all mutations would be reported as" 516 | " killed because, well the test suite failed! This was reported in `CR#111" 517 | " `_ and fixed in " 518 | "`CR#181 `_." 519 | msgstr "在撰寫 *Cosmic Ray* 時,如果在 baseline 測試執行時出現錯誤," 520 | "並不會顯示失敗,而會將所有的突變回報為 `殺死`,這是因為測試套件就失敗了!" 521 | "回報在 `CR#111 `_ ," 522 | "修復在 `CR#181 `_ " 523 | 524 | #: ../../README.rst:382 525 | msgid "Python: Testing sequence of if == int" 526 | msgstr "Python: 測試一系列的 if == int" 527 | 528 | #: ../../README.rst:384 529 | msgid "To completely test the following pattern" 530 | msgstr "要完整的測試下面的 pattern" 531 | 532 | #: ../../README.rst:395 533 | msgid "" 534 | "you need to test with all descrete values plus values outside the allowed" 535 | " set. For example see :doc:`python/example_08/README`" 536 | msgstr "你需要測試所有的合法數值加上集合外的值。" 537 | "範例請參考::doc:`python/example_08/README`" 538 | 539 | #: ../../README.rst:400 540 | msgid "Python: Testing sequence of if == string" 541 | msgstr "Python: 測試一系列的 if == string" 542 | 543 | #: ../../README.rst:402 544 | msgid "To fully test the following pattern" 545 | msgstr "要完整的測試下面的 pattern" 546 | 547 | #: ../../README.rst:413 548 | msgid "" 549 | "you need to test with all possible string values as well as with values " 550 | "outside the allowed set. For example see " 551 | ":doc:`python/example_08/README_str`." 552 | msgstr "你需要測試所有可能的字串數值,以及那些不在可能字串內的值。" 553 | "範例請參考::doc:`python/example_08/README_str`" 554 | 555 | #: ../../README.rst:418 556 | msgid "Python: Missing or extra parameters" 557 | msgstr "Python: 缺失或額外參數" 558 | 559 | #: ../../README.rst:420 560 | msgid "" 561 | "Depending on how your method signature is defined it is possible to " 562 | "either accept additional parameters which are not needed or forget to " 563 | "pass along parameters which control internal behavior. Mutation testing " 564 | "helps you identify those cases and adjust your code accordingly." 565 | msgstr "根據你的方法 (method) 定義不同,其用來控制內部行為的參數可以不是必要或是忘記傳入。" 566 | "突變測試可以幫助你檢查這些狀況並且根據對應的情況調整程式碼。" 567 | 568 | #: ../../README.rst:425 569 | msgid "For example see :doc:`python/example_09/README`." 570 | msgstr "範例請參考::doc:`python/example_09/README`" 571 | 572 | #: ../../README.rst:429 573 | msgid "Python: Testing for 0 <= X < 100" 574 | msgstr "Python: 測試 0 <= X <= 100" 575 | 576 | #: ../../README.rst:432 577 | msgid "When testing numerical ranges we need at least 4 tests:" 578 | msgstr "當要對數值範圍做測試時我們需要至少 4 個測試:" 579 | 580 | #: ../../README.rst:434 581 | msgid "Test with both border values" 582 | msgstr "測試兩個邊界數值" 583 | 584 | #: ../../README.rst:435 585 | msgid "Test with values outside the range, ideally +1/-1" 586 | msgstr "測試邊界外的數值,理想上使用 +1 / -1" 587 | 588 | #: ../../README.rst:436 589 | msgid "" 590 | "Testing with a value in the middle of the range is not required for full " 591 | "mutation coverage!" 592 | msgstr "測試範圍中的數值並不是完整突變測試需要涵蓋的範圍!" 593 | 594 | #: ../../README.rst:439 595 | msgid "For example see :doc:`python/example_12/README`." 596 | msgstr "範例請參考::doc:`python/example_12/README`" 597 | 598 | #: ../../README.rst:443 599 | msgid "Python: On boolean expressions" 600 | msgstr "Python: 布林表達式" 601 | 602 | #: ../../README.rst:445 603 | msgid "" 604 | "When dealing with non-trivial boolean expressions mutation testing often " 605 | "helps put things into perspective. It causes you to rethink the " 606 | "expression which often leads to refactoring and killing mutants. For " 607 | "example see :doc:`python/example_10/README`." 608 | msgstr "當在處理重要的布林表達式時,突變測試通常可以幫你透撤整個狀況。" 609 | "他讓你需要重新思考整個表達式,通常這會讓你重構程式碼以及殺死突變。" 610 | "範例請參襖::doc:`python/example_10/README`" 611 | 612 | #: ../../README.rst:452 613 | msgid "Refactor multiple boolean expressions" 614 | msgstr "重構多重布林表達式" 615 | 616 | #: ../../README.rst:454 617 | msgid "" 618 | "Consider the following code where the expression left of ``and`` is " 619 | "always the same" 620 | msgstr "以下方的程式碼為例,表達式 ``and`` 左邊都是相同的" 621 | 622 | #: ../../README.rst:471 623 | msgid "" 624 | "This can easily be refactored by removing the ``name == \"method\"`` " 625 | "expression and making the subsequent if statements nested under the first" 626 | " one." 627 | msgstr "我們可以簡單的將表達式中 ``name == \"method\"`` 部份移除,移到上層," 628 | "並將隨後的 if 語句置放於其下:" 629 | 630 | #: ../../README.rst:489 631 | msgid "" 632 | "The refactored code is shorter and provides less mutation sites thus " 633 | "reducing overall mutation test execution time. This code can be " 634 | "refactored even more aggressively into" 635 | msgstr "這樣重構可以讓減少程式碼行數,並且降低突變的次數來減少總體突變測試的時間。" 636 | "這段程式碼可以再以下面的方式進行更基進的重構:" 637 | 638 | #: ../../README.rst:504 639 | msgid "Indices and tables" 640 | msgstr "目錄及索引" 641 | 642 | #: ../../README.rst:506 643 | msgid ":ref:`genindex`" 644 | msgstr ":ref:`genindex`" 645 | 646 | #: ../../README.rst:507 647 | msgid ":ref:`modindex`" 648 | msgstr ":ref:`modindex`" 649 | 650 | #: ../../README.rst:508 651 | msgid ":ref:`search`" 652 | msgstr ":ref:`search`" 653 | 654 | -------------------------------------------------------------------------------- /locale/zh_TW/LC_MESSAGES/python/example_00/README.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) 2016, Alexander Todorov 3 | # This file is distributed under the same license as the Mutation Testing in 4 | # Patterns package. 5 | # FIRST AUTHOR , 2017. 6 | # 7 | #, fuzzy 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: Mutation Testing in Patterns 1.0\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2017-01-23 22:51+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.3.4\n" 20 | 21 | #: ../../python/example_00/README.rst:2 22 | msgid "Mutant not killed due to module import issue" 23 | msgstr "因為 import 問題而無法殺死突變" 24 | 25 | #: ../../python/example_00/README.rst:4 26 | msgid "" 27 | "Example of how importing modules under different names allows mutations " 28 | "to survive. In this case the problem is related to how Cosmic-Ray loads " 29 | "the mutated modules. It has already been fixed in `PR #158 " 30 | "`_." 31 | msgstr "針對使用不同名稱載入模組會讓突變存活下來的範例。在這個案例中,問題" 32 | "跟 Cosmic-Ray 如何載入突變模組有關。這已經在 `PR #158 " 33 | "`_ 中修復。" 34 | 35 | #: ../../python/example_00/README.rst:10 36 | msgid "Reproducer" 37 | msgstr "" 38 | 39 | #: ../../python/example_00/README.rst:39 40 | msgid "" 41 | "In this example ``test_control.py`` properly kills the mutant once the " 42 | "above issue is fixed." 43 | msgstr "在這個範例中 ``test_control.py`` 在修復後便能正確殺死突變。" 44 | 45 | #: ../../python/example_00/README.rst:43 46 | msgid "Source code" 47 | msgstr "程式碼" 48 | 49 | -------------------------------------------------------------------------------- /locale/zh_TW/LC_MESSAGES/python/example_01/README.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) 2016, Alexander Todorov 3 | # This file is distributed under the same license as the Mutation Testing in 4 | # Patterns package. 5 | # FIRST AUTHOR , 2017. 6 | # 7 | #, fuzzy 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: Mutation Testing in Patterns 1.0\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2017-01-23 22:51+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.3.4\n" 20 | 21 | #: ../../python/example_01/README.rst:2 22 | msgid "Mutant not killed when dynamically importing module" 23 | msgstr "突變在動態載入模組時沒有被殺死" 24 | 25 | #: ../../python/example_01/README.rst:4 26 | msgid "" 27 | "Example of how dynamically importing modules allows mutations to survive." 28 | " In this case the problem is a bug in Cosmic-Ray which still hasn't been " 29 | "diagnosed properly. For more information see `Issue #157 " 30 | "`_." 31 | msgstr "這個範例演示動態載入模組導致突變存活。" 32 | "這個案例是 Cosmic-Ray 中尚未診斷完成的問題。更多資訊請參考" 33 | " `Issue #157 `_ " 34 | 35 | #: ../../python/example_01/README.rst:10 36 | msgid "Reproducer" 37 | msgstr "" 38 | 39 | #: ../../python/example_01/README.rst:38 40 | msgid "Verify test works" 41 | msgstr "驗證測試是成功的" 42 | 43 | #: ../../python/example_01/README.rst:40 44 | msgid "" 45 | "In this example ``test_control.py`` properly detects the mutant when the " 46 | "source code is modified by hand and the test executed manually. To verify" 47 | " this edit ``sandwich/ham/ham.py`` as shown above and then execute ::" 48 | msgstr "這個範例中 ``test_control.py`` 正確的在程式碼手動修改以及執行時," 49 | "偵測到突變。要驗證這個部份,請以下面的程式碼手動編輯 ``sandwitch/ham/ham.py`` 接著執行一次:" 50 | 51 | #: ../../python/example_01/README.rst:61 52 | msgid "Source code" 53 | msgstr "程式碼" 54 | 55 | -------------------------------------------------------------------------------- /locale/zh_TW/LC_MESSAGES/python/example_02/README.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) 2016, Alexander Todorov 3 | # This file is distributed under the same license as the Mutation Testing in 4 | # Patterns package. 5 | # FIRST AUTHOR , 2017. 6 | # 7 | #, fuzzy 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: Mutation Testing in Patterns 1.0\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2017-01-23 22:51+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.3.4\n" 20 | 21 | #: ../../python/example_02/README.rst:2 22 | msgid "Mutant killed due to flaky test" 23 | msgstr "" 24 | 25 | #: ../../python/example_02/README.rst:4 26 | msgid "" 27 | "Sometimes mutants may be falsely reported as killed simply because the " 28 | "test case failed. When your test suite isn't reliable your mutation " 29 | "testing isn't realiable as well." 30 | msgstr "" 31 | 32 | #: ../../python/example_02/README.rst:9 33 | msgid "Reproducer" 34 | msgstr "" 35 | 36 | #: ../../python/example_02/README.rst:38 37 | msgid "Verify mutants have survived" 38 | msgstr "" 39 | 40 | #: ../../python/example_02/README.rst:40 41 | msgid "" 42 | "The ``TestFlaky`` test isn't reliable because it doesn't take into " 43 | "account the interaction with the filesystem. In the example above the " 44 | "first 2 lines appear when *Cosmic-Ray* executes the baseline test suite, " 45 | "that is execute the test suite without any modifications. The next 2 " 46 | "lines come when ``upcase`` is mutated to ``True`` and the last 3 lines " 47 | "come when ``number`` is mutated to ``3``." 48 | msgstr "" 49 | 50 | #: ../../python/example_02/README.rst:47 51 | msgid "" 52 | "Notice that ``TestFlaky`` never asserts the contents of the written text," 53 | " nor the fact that it may be in upper case. However due to unrelated " 54 | "failures we're left to think that the test suite tests everything " 55 | "correctly. To see the real results execute ::" 56 | msgstr "" 57 | 58 | #: ../../python/example_02/README.rst:90 59 | msgid "" 60 | "The second test ``TestFlakyWithMock`` is better because it properly " 61 | "isolates interaction with the filesystem and because it properly verifies" 62 | " the expected behavior. All mutants are properly killed this time ::" 63 | msgstr "" 64 | 65 | #: ../../python/example_02/README.rst:102 66 | msgid "" 67 | "Since commit `db7b7c6` Cosmic Ray will fail if baseline test execution " 68 | "fails. This isn't the same as having unreliable tests but may help you " 69 | "identify something isn't right sooner than later. If you want to " 70 | "experiment execute the above `cosmic-ray run` command twice without " 71 | "deleting `test.txt` between test runs!" 72 | msgstr "" 73 | 74 | #: ../../python/example_02/README.rst:109 75 | msgid "Source code" 76 | msgstr "程式碼" 77 | 78 | -------------------------------------------------------------------------------- /locale/zh_TW/LC_MESSAGES/python/example_03/README.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) 2016, Alexander Todorov 3 | # This file is distributed under the same license as the Mutation Testing in 4 | # Patterns package. 5 | # FIRST AUTHOR , 2017. 6 | # 7 | #, fuzzy 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: Mutation Testing in Patterns 1.0\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2017-01-23 22:51+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.3.4\n" 20 | 21 | #: ../../python/example_03/README.rst:2 22 | msgid "Killing mutants by refactoring if str != \"\"" 23 | msgstr "重構 if str != \"\" 來殺死突變" 24 | 25 | #: ../../python/example_03/README.rst:5 26 | msgid "Reproducer" 27 | msgstr "" 28 | 29 | #: ../../python/example_03/README.rst:50 30 | msgid "" 31 | "Now compare the results with ``hello2.py`` and ``test_hello2.py`` where " 32 | "we've refactored the condition to ``if greeting:``::" 33 | msgstr "現在,比較 ``hello2.py`` 與 ``test_hello2.py`` 的輸出結果。我們在" 34 | "二的部份重構了字串比較的部份:``if greeting``" 35 | 36 | #: ../../python/example_03/README.rst:60 37 | msgid "Source code" 38 | msgstr "程式碼" 39 | 40 | -------------------------------------------------------------------------------- /locale/zh_TW/LC_MESSAGES/python/example_04/README.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) 2016, Alexander Todorov 3 | # This file is distributed under the same license as the Mutation Testing in 4 | # Patterns package. 5 | # FIRST AUTHOR , 2017. 6 | # 7 | #, fuzzy 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: Mutation Testing in Patterns 1.0\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2017-01-23 22:51+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.3.4\n" 20 | 21 | #: ../../python/example_04/README.rst:2 22 | msgid "Testing for X != 1" 23 | msgstr "測試 X != 1" 24 | 25 | #: ../../python/example_04/README.rst:5 26 | msgid "Reproducer" 27 | msgstr "" 28 | 29 | #: ../../python/example_04/README.rst:48 30 | msgid "Now compare the results from ``TestHelloProperly`` ::" 31 | msgstr "現在,比較 ``TestHelloProperly`` 的輸出結果" 32 | 33 | #: ../../python/example_04/README.rst:58 34 | msgid "Source code" 35 | msgstr "程式碼" 36 | -------------------------------------------------------------------------------- /locale/zh_TW/LC_MESSAGES/python/example_05/README.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) 2016, Alexander Todorov 3 | # This file is distributed under the same license as the Mutation Testing in 4 | # Patterns package. 5 | # FIRST AUTHOR , 2017. 6 | # 7 | #, fuzzy 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: Mutation Testing in Patterns 1.0\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2017-01-23 22:51+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.3.4\n" 20 | 21 | #: ../../python/example_05/README.rst:2 22 | msgid "Refactor if len(list) != 0" 23 | msgstr "重構 if len(list) != 0" 24 | 25 | #: ../../python/example_05/README.rst:5 26 | msgid "Reproducer" 27 | msgstr "" 28 | 29 | #: ../../python/example_05/README.rst:35 30 | msgid "" 31 | "If we didn't have the ``test_sayHello_with_friends()`` test then the ``!=" 32 | " -> <`` mutation would have survived as well!" 33 | msgstr "如果我們沒有 ``test_sayHello_with_friends()`` 測試,則 ``!=" 34 | " -> <`` 的突變將會存活下來。" 35 | 36 | #: ../../python/example_05/README.rst:38 37 | msgid "Now compare the results after refactoring ::" 38 | msgstr "現在比較重構後的輸出結果" 39 | 40 | #: ../../python/example_05/README.rst:45 41 | msgid "" 42 | "Functionality is the same but we have reduced the number of possible " 43 | "mutations!" 44 | msgstr "行為上完全相等,但是我們降低了可能的突變數量。" 45 | 46 | #: ../../python/example_05/README.rst:48 47 | msgid "Source code" 48 | msgstr "程式碼" 49 | 50 | -------------------------------------------------------------------------------- /locale/zh_TW/LC_MESSAGES/python/example_06/README.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) 2016, Alexander Todorov 3 | # This file is distributed under the same license as the Mutation Testing in 4 | # Patterns package. 5 | # FIRST AUTHOR , 2017. 6 | # 7 | #, fuzzy 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: Mutation Testing in Patterns 1.0\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2017-01-23 22:51+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.3.4\n" 20 | 21 | #: ../../python/example_06/README.rst:2 22 | msgid "Refactor if X is None" 23 | msgstr "重構 if X is None" 24 | 25 | #: ../../python/example_06/README.rst:5 26 | msgid "Reproducer" 27 | msgstr "" 28 | 29 | #: ../../python/example_06/README.rst:35 30 | msgid "" 31 | "Since `PR #162 `_ " 32 | "*Cosmic-Ray* skips mutations of the kind ``== -> is`` but doesn't skip " 33 | "the opposite of ``is -> ==``!" 34 | msgstr "`PR #162 `_ " 35 | "*Cosmic-Ray* 跳過了像是 ``== -> is`` 的突變,但是沒有跳過相反的" 36 | "``is -> ==`` 部份。" 37 | 38 | #: ../../python/example_06/README.rst:39 39 | msgid "Now compare the results after refactoring ::" 40 | msgstr "現在比較重構後的結果:" 41 | 42 | #: ../../python/example_06/README.rst:46 43 | msgid "" 44 | "Functionality is the same but we have reduced the number of possible " 45 | "mutations!" 46 | msgstr "行為上完全相等但是我們降低了突變可能的數量。" 47 | 48 | #: ../../python/example_06/README.rst:49 49 | msgid "Source code" 50 | msgstr "程式碼" 51 | 52 | -------------------------------------------------------------------------------- /locale/zh_TW/LC_MESSAGES/python/example_07/README.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) 2016, Alexander Todorov 3 | # This file is distributed under the same license as the Mutation Testing in 4 | # Patterns package. 5 | # FIRST AUTHOR , 2017. 6 | # 7 | #, fuzzy 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: Mutation Testing in Patterns 1.0\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2017-01-23 22:51+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.3.4\n" 20 | 21 | #: ../../python/example_07/README.rst:2 22 | msgid "Testing __eq__ & __ne__" 23 | msgstr "測試 __eq__ 與 __ne__" 24 | 25 | #: ../../python/example_07/README.rst:4 26 | msgid "" 27 | "This is an example of testing overriden ``__eq__`` and ``__ne__`` " 28 | "methods. In this example objects are identified by the value of their " 29 | "attributes. There is an example where attributes are of the same type and" 30 | " an example where attributes are of different types." 31 | msgstr "" 32 | 33 | #: ../../python/example_07/README.rst:9 34 | msgid "" 35 | "To kill all mutants we have to exercise all possible methods and " 36 | "comparison branches in them, see source comments for more info." 37 | msgstr "" 38 | 39 | #: ../../python/example_07/README.rst:12 40 | msgid "" 41 | "Once we've stablished what is equal and how that compares to ``None`` and" 42 | " itself we can start testing comparisons by modifying the attribute " 43 | "values one by one. Note that equality comparison works both ways, that is" 44 | " ``X == Y`` is the same as ``Y == X`` so we test it that way." 45 | msgstr "" 46 | 47 | #: ../../python/example_07/README.rst:17 48 | msgid "" 49 | "Comment out the ``test_default_objects_are_always_equal()`` method and " 50 | "one of the ``assertNotEqual(sandwich_1, sandwich_2)`` lines and re-test " 51 | "to see the difference in results!" 52 | msgstr "" 53 | 54 | #: ../../python/example_07/README.rst:22 55 | msgid "Reproducer" 56 | msgstr "" 57 | 58 | #: ../../python/example_07/README.rst:34 59 | msgid "Source code" 60 | msgstr "程式碼" 61 | 62 | -------------------------------------------------------------------------------- /locale/zh_TW/LC_MESSAGES/python/example_08/README.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) 2016, Alexander Todorov 3 | # This file is distributed under the same license as the Mutation Testing in 4 | # Patterns package. 5 | # FIRST AUTHOR , 2017. 6 | # 7 | #, fuzzy 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: Mutation Testing in Patterns 1.0\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2017-01-23 22:51+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.3.4\n" 20 | 21 | #: ../../python/example_08/README.rst:2 22 | msgid "Testing sequence of if == int statements" 23 | msgstr "測試有關 if == int 的序列" 24 | 25 | #: ../../python/example_08/README.rst:4 26 | msgid "Sometimes in programs we see the following pattern" 27 | msgstr "有時候在程式中會有這樣的模式" 28 | 29 | #: ../../python/example_08/README.rst:15 30 | msgid "" 31 | "``X`` is compared to several allowed values of type integer. It is " 32 | "important to notice that ``X`` accepts a descrete set of allowed values. " 33 | "When we forget to test with values outside the allowed set mutation " 34 | "testing will produce surviving mutants." 35 | msgstr "``X`` 與許多合法的整數去做比較。當我們忘記測試合法數值外的值的時候," 36 | "突變測試會產生存活的突變。" 37 | 38 | #: ../../python/example_08/README.rst:22 39 | msgid "The order of if statements isn't important." 40 | msgstr "statements 的順序不是這麼的重要" 41 | 42 | #: ../../python/example_08/README.rst:26 43 | msgid "Reproducer" 44 | msgstr "" 45 | 46 | #: ../../python/example_08/README.rst:100 47 | msgid "Killing the mutants" 48 | msgstr "殺死突變" 49 | 50 | #: ../../python/example_08/README.rst:103 51 | msgid "To kill all mutants we need to test with values outside the allowed set ::" 52 | msgstr "要殺死所有的突變,我們需要測試合法範圍外的值:" 53 | 54 | #: ../../python/example_08/README.rst:110 55 | msgid "Source code" 56 | msgstr "程式碼" 57 | 58 | 59 | -------------------------------------------------------------------------------- /locale/zh_TW/LC_MESSAGES/python/example_08/README_str.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) 2016, Alexander Todorov 3 | # This file is distributed under the same license as the Mutation Testing in 4 | # Patterns package. 5 | # FIRST AUTHOR , 2017. 6 | # 7 | #, fuzzy 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: Mutation Testing in Patterns 1.0\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2017-01-23 22:51+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.3.4\n" 20 | 21 | #: ../../python/example_08/README_str.rst:2 22 | msgid "Testing sequence of if == string statements" 23 | msgstr "測試 if == string 序列" 24 | 25 | #: ../../python/example_08/README_str.rst:4 26 | msgid "Sometimes in programs we see the following pattern" 27 | msgstr "有時候程式中會有這樣的模式" 28 | 29 | #: ../../python/example_08/README_str.rst:15 30 | msgid "" 31 | "``X`` is compared to several allowed values of type string. It is " 32 | "important to notice that ``X`` accepts a descrete set of allowed values. " 33 | "When we forget to test with string values outside the allowed set " 34 | "mutation testing will produce surviving mutants." 35 | msgstr "``X`` 與許多合法的字串去做比較。當我們忘記測試合法字串外的值的時候," 36 | "突變測試會產生存活的突變。" 37 | 38 | #: ../../python/example_08/README_str.rst:22 39 | msgid "The order of if statements isn't important." 40 | msgstr "順序並不是這麼的重要" 41 | 42 | #: ../../python/example_08/README_str.rst:26 43 | msgid "Reproducer" 44 | msgstr "" 45 | 46 | #: ../../python/example_08/README_str.rst:148 47 | msgid "Killing the mutants" 48 | msgstr "殺死突變" 49 | 50 | #: ../../python/example_08/README_str.rst:151 51 | msgid "" 52 | "To kill some mutants we need to test with values outside the allowed set " 53 | "::" 54 | msgstr "要殺死突變我們需要測試合法數值外的值:" 55 | 56 | #: ../../python/example_08/README_str.rst:157 57 | msgid "Remaining mutants" 58 | msgstr "剩餘突變" 59 | 60 | #: ../../python/example_08/README_str.rst:159 61 | msgid "" 62 | "When testing string comparisons the ==/in mutations are equivalent and " 63 | "can not be killed:" 64 | msgstr "當在測試字串比較時 ==/in 兩個突變是相等的且不能被殺死" 65 | 66 | #: ../../python/example_08/README_str.rst:168 67 | msgid "Source code" 68 | msgstr "程式碼" 69 | 70 | -------------------------------------------------------------------------------- /locale/zh_TW/LC_MESSAGES/python/example_09/README.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) 2016, Alexander Todorov 3 | # This file is distributed under the same license as the Mutation Testing in 4 | # Patterns package. 5 | # FIRST AUTHOR , 2017. 6 | # 7 | #, fuzzy 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: Mutation Testing in Patterns 1.0\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2017-01-23 22:51+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.3.4\n" 20 | 21 | #: ../../python/example_09/README.rst:2 22 | msgid "Missing or extra method parameters" 23 | msgstr "遺失或額外的方法參數" 24 | 25 | #: ../../python/example_09/README.rst:5 26 | msgid "Reproducer" 27 | msgstr "" 28 | 29 | #: ../../python/example_09/README.rst:13 30 | msgid "" 31 | "Initially we start with a set of tests which doesn't validate default " 32 | "values for method parameters. It may validate some other behavior which " 33 | "was considered more important at the time. In this example the test is " 34 | "empty because there is no other behavior present. This is ``test1.py``. " 35 | "Mutation testing will identify the missing tests as shown below ::" 36 | msgstr "" 37 | 38 | #: ../../python/example_09/README.rst:85 39 | msgid "" 40 | "Then we proceed to add the missing tests in ``test2.py``. Executing this " 41 | "test stand-alone identifies that something is wrong with the code under " 42 | "test. The ``Motorway`` constructor passes along an extra parameter which " 43 | "isn't consumed in the base class. In the ``RuralRoad`` constructor we've " 44 | "forgotten to pass the ``speedLimit`` parameter to the base constructor. " 45 | "::" 46 | msgstr "" 47 | 48 | #: ../../python/example_09/README.rst:115 49 | msgid "Next we proceed to refactor the code under test in ``roads2.py``" 50 | msgstr "" 51 | 52 | #: ../../python/example_09/README.rst:139 53 | msgid "" 54 | "In this example the refactored file is called ``roads2.py`` and its test " 55 | "is called ``test3.py``." 56 | msgstr "" 57 | 58 | #: ../../python/example_09/README.rst:154 59 | msgid "" 60 | "Finally we can verify that the test and refactored code work together and" 61 | " all mutants have been killed ::" 62 | msgstr "" 63 | 64 | #: ../../python/example_09/README.rst:170 65 | msgid "Source code" 66 | msgstr "程式碼" 67 | 68 | 69 | -------------------------------------------------------------------------------- /locale/zh_TW/LC_MESSAGES/python/example_10/README.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) 2016, Alexander Todorov 3 | # This file is distributed under the same license as the Mutation Testing in 4 | # Patterns package. 5 | # FIRST AUTHOR , 2017. 6 | # 7 | #, fuzzy 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: Mutation Testing in Patterns 1.0\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2017-01-23 22:51+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.3.4\n" 20 | 21 | #: ../../python/example_10/README.rst:2 22 | msgid "Testing and refactoring boolean expressions" 23 | msgstr "測試與重構布林表達式" 24 | 25 | #: ../../python/example_10/README.rst:4 26 | msgid "" 27 | "When dealing with non-trivial boolean expressions mutation testing often " 28 | "helps put things into perspective. It causes you to rethink the " 29 | "expression which often leads to refactoring and killing mutants." 30 | msgstr "當在處理重要的布林表達式時,突變測試通常可以幫你透撤整個狀況。" 31 | "他讓你需要重新思考整個表達式,通常這會讓你重構程式碼以及殺死突變。" 32 | 33 | #: ../../python/example_10/README.rst:9 34 | msgid "Example" 35 | msgstr "範例" 36 | 37 | #: ../../python/example_10/README.rst:16 38 | msgid "" 39 | "Initially we start with the example in ``boolops1.py`` and ``test1.py``. " 40 | "Although the test appears to be correct, all possible values for " 41 | "``list_a`` and ``list_b`` are tested, there are still surviving mutants. " 42 | "::" 43 | msgstr "" 44 | 45 | #: ../../python/example_10/README.rst:112 46 | msgid "" 47 | "If we proceed to refactor ``len(list)`` comparisons as shown previously " 48 | "it is easier to figure out that the boolean function is XNOR (if and only" 49 | " if), also called logical equality. In ``boolops2.py`` *Cosmic-Ray* " 50 | "doesn't detect any possible mutations because at the moment of writing it" 51 | " doesn't support mutating boolean operators. ::" 52 | msgstr "" 53 | 54 | #: ../../python/example_10/README.rst:124 55 | msgid "" 56 | "Another possible refactoring is ``boolops3.py`` where valid parameters " 57 | "are enumerated before the condition is checked. ::" 58 | msgstr "" 59 | 60 | #: ../../python/example_10/README.rst:133 61 | msgid "" 62 | "Yet another possible refactoring is ``boolops4.py`` where the condition " 63 | "is expressed using the built-ins ``any`` and ``all``. Unfortunately " 64 | "*Cosmic-Ray* doesn't recognize these as possible mutations either. ::" 65 | msgstr "" 66 | 67 | #: ../../python/example_10/README.rst:145 68 | msgid "Source code" 69 | msgstr "程式碼" 70 | 71 | -------------------------------------------------------------------------------- /locale/zh_TW/LC_MESSAGES/python/example_11/README.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) 2016, Alexander Todorov 3 | # This file is distributed under the same license as the Mutation Testing in 4 | # Patterns package. 5 | # FIRST AUTHOR , 2017. 6 | # 7 | #, fuzzy 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: Mutation Testing in Patterns 1.0\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2017-01-23 22:51+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.3.4\n" 20 | 21 | #: ../../python/example_11/README.rst:2 22 | msgid "Refactor if X is not None" 23 | msgstr "重構 if X is not None" 24 | 25 | #: ../../python/example_11/README.rst:4 26 | msgid "" 27 | "Objects of any type can be compared for not being ``None`` value however " 28 | "that leads to surviving mutations. ``None`` is a special value but in " 29 | "most practical cases it is equivalent to zero/empty value for the type." 30 | msgstr "任何型態的物件都可以跟 ``None`` 來比較,不過這會讓突變存活下來。" 31 | "``None`` 是一個特別的值,不過對於大部份常見的案例中這都等價於 0 或空值。" 32 | 33 | #: ../../python/example_11/README.rst:10 34 | msgid "Example" 35 | msgstr "範例:" 36 | 37 | #: ../../python/example_11/README.rst:17 38 | msgid "" 39 | "In ``example1.py`` we're accepting ``None`` as default parameter value " 40 | "and correctly identified 3 test cases - when password is a string, when " 41 | "it is empty string and when it is ``None``. There is one surviving " 42 | "mutant. ::" 43 | msgstr "在 ``example1.py`` 我們使用 ``None`` 當作 password 的預設參數並且正確的" 44 | "偵測出 3 個測試案例 - 當 password 是字串時,當參數傳入空字串而我們使用 ``None`` 比較時," 45 | "會有一個突變存活下來。" 46 | 47 | #: ../../python/example_11/README.rst:44 48 | msgid "" 49 | "If we decide to remove ``None`` and accept an empty string instead then " 50 | "``example2.py`` is reduced to one line and there are no surviving " 51 | "mutations. Also the number of mutations is significantly reduced. ::" 52 | msgstr "如果我們移除 ``None`` 而使用空字串作為預設值,在 ``example2.py`` 中可以" 53 | "減少一行程式碼並且讓突變都被殺死。同時突變數量也有顯著的減少。" 54 | 55 | #: ../../python/example_11/README.rst:61 56 | msgid "Source code" 57 | msgstr "程式碼" 58 | -------------------------------------------------------------------------------- /locale/zh_TW/LC_MESSAGES/python/example_12/README.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) 2016, Alexander Todorov 3 | # This file is distributed under the same license as the Mutation Testing in 4 | # Patterns package. 5 | # FIRST AUTHOR , 2017. 6 | # 7 | #, fuzzy 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: Mutation Testing in Patterns 1.0\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2017-01-23 22:51+0800\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "Last-Translator: FULL NAME \n" 15 | "Language-Team: LANGUAGE \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.3.4\n" 20 | 21 | #: ../../python/example_12/README.rst:2 22 | msgid "Testing for 0 <= X <= 100" 23 | msgstr "針對 0 <= X <= 100 的測試" 24 | 25 | #: ../../python/example_12/README.rst:4 26 | msgid "" 27 | "It is common for programs to expect numerical variables to accept values " 28 | "matching a certain range. A good example is a ``percent`` variable with " 29 | "allowed values from 0 to 100 inclusive." 30 | msgstr "在程式中我們很常見到,需要數值在某個特定範圍才接受的狀況。" 31 | "一個範例是 ``percent (百分比)`` 變數的數值需要在 0 到 100 之間。" 32 | 33 | #: ../../python/example_12/README.rst:8 34 | msgid "" 35 | "Test for such code will often validate correct operation with a value " 36 | "inside the range and expect an error for a value outside the range. As a " 37 | "bonus the test may be expecting errors when testing with values on both " 38 | "sides of the range!" 39 | msgstr "" 40 | 41 | #: ../../python/example_12/README.rst:15 42 | msgid "Reproducer" 43 | msgstr "" 44 | 45 | #: ../../python/example_12/README.rst:89 46 | msgid "" 47 | "To fully test this code you have to test with both border values and with" 48 | " the next possible values, which are outside of the range. Testing with a" 49 | " value that falls within the range, but isn't a border one doesn't affect" 50 | " mutation testing. ::" 51 | msgstr "" 52 | 53 | #: ../../python/example_12/README.rst:103 54 | msgid "This is similar to :doc:`../example_04/README`" 55 | msgstr "" 56 | 57 | #: ../../python/example_12/README.rst:106 58 | msgid "Source code" 59 | msgstr "程式碼" 60 | -------------------------------------------------------------------------------- /python/README.rst: -------------------------------------------------------------------------------- 1 | Mutation Testing in Patterns - Python Example 2 | ********************************************* 3 | 4 | This is python example 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | example_00/README.rst 10 | example_01/README.rst 11 | example_02/README.rst 12 | example_03/README.rst 13 | example_04/README.rst 14 | example_05/README.rst 15 | example_06/README.rst 16 | example_07/README.rst 17 | example_08/README.rst 18 | example_09/README.rst 19 | example_10/README.rst 20 | example_11/README.rst 21 | example_12/README.rst 22 | -------------------------------------------------------------------------------- /python/example_00/README.rst: -------------------------------------------------------------------------------- 1 | Mutant not killed due to module import issue 2 | ******************************************** 3 | 4 | Example of how importing modules under different names allows mutations to 5 | survive. In this case the problem is related to how Cosmic-Ray loads the 6 | mutated modules. It has already been fixed in 7 | `PR #158 `_. 8 | 9 | Reproducer 10 | ========== 11 | 12 | :: 13 | 14 | $ pip install https://github.com/sixty-north/cosmic-ray/zipball/2a48656 15 | $ celery -A cosmic_ray.tasks.worker worker 16 | 17 | $ cosmic-ray run --baseline=10 example.json sandwich/ham/ham.py -- tests 18 | $ cosmic-ray report example.json 19 | job ID 1:Outcome.SURVIVED:sandwich.ham.ham 20 | command: cosmic-ray worker sandwich.ham.ham number_replacer 0 unittest -- tests 21 | --- mutation diff --- 22 | --- a/sandwich/ham/ham.py 23 | +++ b/sandwich/ham/ham.py 24 | @@ -3,6 +3,6 @@ 25 | 26 | class Ham(object): 27 | 28 | - def __init__(self, pieces=10): 29 | + def __init__(self, pieces=11): 30 | self.pieces = pieces 31 | 32 | 33 | total jobs: 1 34 | complete: 1 (100.00%) 35 | survival rate: 100.00% 36 | 37 | .. note:: 38 | 39 | In this example ``test_control.py`` properly kills the mutant once 40 | the above issue is fixed. 41 | 42 | Source code 43 | =========== 44 | 45 | 46 | .. literalinclude:: sandwich/ham/ham.py 47 | :caption: sandwich/ham/ham.py 48 | :language: python 49 | 50 | .. literalinclude:: sandwich/control.py 51 | :caption: sandwich/control.py 52 | :language: python 53 | 54 | .. literalinclude:: tests/test_control.py 55 | :caption: tests/test_control.py 56 | :language: python 57 | -------------------------------------------------------------------------------- /python/example_00/sandwich/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atodorov/mutation-testing-in-patterns/f623c36fe330a65cd19a5fcc27973eb4db0c5e18/python/example_00/sandwich/__init__.py -------------------------------------------------------------------------------- /python/example_00/sandwich/control.py: -------------------------------------------------------------------------------- 1 | from sandwich.ham import ham 2 | ham_class = ham.Ham 3 | -------------------------------------------------------------------------------- /python/example_00/sandwich/ham/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atodorov/mutation-testing-in-patterns/f623c36fe330a65cd19a5fcc27973eb4db0c5e18/python/example_00/sandwich/ham/__init__.py -------------------------------------------------------------------------------- /python/example_00/sandwich/ham/ham.py: -------------------------------------------------------------------------------- 1 | class Ham(object): 2 | def __init__(self, pieces=10): 3 | self.pieces = pieces 4 | -------------------------------------------------------------------------------- /python/example_00/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atodorov/mutation-testing-in-patterns/f623c36fe330a65cd19a5fcc27973eb4db0c5e18/python/example_00/tests/__init__.py -------------------------------------------------------------------------------- /python/example_00/tests/test_control.py: -------------------------------------------------------------------------------- 1 | import sandwich.control 2 | import unittest 3 | 4 | class TestControl(unittest.TestCase): 5 | def test_loading_via_importlib(self): 6 | ham_in_fridge = sandwich.control.ham_class() 7 | self.assertEqual(ham_in_fridge.pieces, 10) 8 | 9 | 10 | if __name__ == "__main__": 11 | unittest.main() 12 | -------------------------------------------------------------------------------- /python/example_01/README.rst: -------------------------------------------------------------------------------- 1 | Mutant not killed when dynamically importing module 2 | *************************************************** 3 | 4 | Example of how dynamically importing modules allows mutations to 5 | survive. In this case the problem is a bug in Cosmic-Ray which still 6 | hasn't been diagnosed properly. For more information see 7 | `Issue #157 `_. 8 | 9 | Reproducer 10 | ========== 11 | 12 | :: 13 | 14 | $ pip install https://github.com/sixty-north/cosmic-ray/zipball/b3c57a3 15 | $ celery -A cosmic_ray.tasks.worker worker 16 | 17 | $ cosmic-ray run --baseline=10 example.json sandwich/ham/ham.py -- tests 18 | $ cosmic-ray report example.json 19 | job ID 1:Outcome.SURVIVED:sandwich.ham.ham 20 | command: cosmic-ray worker sandwich.ham.ham number_replacer 0 unittest -- tests 21 | --- mutation diff --- 22 | --- a/sandwich/ham/ham.py 23 | +++ b/sandwich/ham/ham.py 24 | @@ -3,6 +3,6 @@ 25 | 26 | class Ham(object): 27 | 28 | - def __init__(self, pieces=10): 29 | + def __init__(self, pieces=11): 30 | self.pieces = pieces 31 | 32 | 33 | total jobs: 1 34 | complete: 1 (100.00%) 35 | survival rate: 100.00% 36 | 37 | Verify test works 38 | ================= 39 | 40 | In this example ``test_control.py`` properly detects the mutant when the 41 | source code is modified by hand and the test executed manually. To verify this 42 | edit ``sandwich/ham/ham.py`` as shown above and then execute :: 43 | 44 | $ python -m unittest tests/test_control.py 45 | F 46 | ====================================================================== 47 | FAIL: test_loading_via_importlib (tests.test_control.TestControl) 48 | ---------------------------------------------------------------------- 49 | Traceback (most recent call last): 50 | File "~/example_01/tests/test_control.py", line 7, in test_loading_via_importlib 51 | self.assertEqual(ham_in_fridge.pieces, 10) 52 | AssertionError: 11 != 10 53 | 54 | ---------------------------------------------------------------------- 55 | Ran 1 test in 0.000s 56 | 57 | FAILED (failures=1) 58 | 59 | 60 | Source code 61 | =========== 62 | 63 | 64 | .. literalinclude:: sandwich/ham/ham.py 65 | :caption: sandwich/ham/ham.py 66 | :language: python 67 | 68 | .. literalinclude:: sandwich/control.py 69 | :caption: sandwich/control.py 70 | :language: python 71 | 72 | .. literalinclude:: tests/test_control.py 73 | :caption: tests/test_control.py 74 | :language: python 75 | -------------------------------------------------------------------------------- /python/example_01/sandwich/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atodorov/mutation-testing-in-patterns/f623c36fe330a65cd19a5fcc27973eb4db0c5e18/python/example_01/sandwich/__init__.py -------------------------------------------------------------------------------- /python/example_01/sandwich/control.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import importlib 4 | 5 | path = os.path.dirname(__file__) 6 | path = os.path.join(path, "ham") 7 | if not path in sys.path: 8 | sys.path.append(path) 9 | 10 | module = importlib.import_module('ham') 11 | ham_class = module.__dict__[module.__all__[0]] 12 | -------------------------------------------------------------------------------- /python/example_01/sandwich/ham/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atodorov/mutation-testing-in-patterns/f623c36fe330a65cd19a5fcc27973eb4db0c5e18/python/example_01/sandwich/ham/__init__.py -------------------------------------------------------------------------------- /python/example_01/sandwich/ham/ham.py: -------------------------------------------------------------------------------- 1 | __all__ = ['Ham'] 2 | 3 | class Ham(object): 4 | def __init__(self, pieces=10): 5 | self.pieces = pieces 6 | -------------------------------------------------------------------------------- /python/example_01/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atodorov/mutation-testing-in-patterns/f623c36fe330a65cd19a5fcc27973eb4db0c5e18/python/example_01/tests/__init__.py -------------------------------------------------------------------------------- /python/example_01/tests/test_control.py: -------------------------------------------------------------------------------- 1 | import sandwich.control 2 | import unittest 3 | 4 | class TestControl(unittest.TestCase): 5 | def test_loading_via_importlib(self): 6 | ham_in_fridge = sandwich.control.ham_class() 7 | self.assertEqual(ham_in_fridge.pieces, 10) 8 | 9 | 10 | if __name__ == "__main__": 11 | unittest.main() 12 | -------------------------------------------------------------------------------- /python/example_02/README.rst: -------------------------------------------------------------------------------- 1 | Mutant killed due to flaky test 2 | ******************************* 3 | 4 | Sometimes mutants may be falsely reported as killed simply because the 5 | test case failed. When your test suite isn't reliable your mutation testing 6 | isn't realiable as well. 7 | 8 | Reproducer 9 | ========== 10 | 11 | :: 12 | 13 | $ pip install nose 14 | $ pip install https://github.com/sixty-north/cosmic-ray/zipball/master 15 | 16 | $ cosmic-ray run --test-runner nose --baseline=10 example.json flaky.py -- test_flaky.py:TestFlaky 17 | $ cosmic-ray report example.json 18 | job ID 1:Outcome.KILLED:flaky 19 | command: cosmic-ray worker flaky boolean_replacer 0 unittest -- . 20 | 21 | job ID 2:Outcome.KILLED:flaky 22 | command: cosmic-ray worker flaky number_replacer 0 unittest -- . 23 | 24 | total jobs: 2 25 | complete: 2 (100.00%) 26 | survival rate: 0.00% 27 | 28 | $ cat test.txt 29 | Hello World 30 | Hello World 31 | HELLO WORLD 32 | HELLO WORLD 33 | Hello World 34 | Hello World 35 | Hello World 36 | 37 | Verify mutants have survived 38 | ============================ 39 | 40 | The ``TestFlaky`` test isn't reliable because it doesn't take into account 41 | the interaction with the filesystem. 42 | In the example above the first 2 lines appear when *Cosmic-Ray* executes 43 | the baseline test suite, that is execute the test suite without any modifications. 44 | The next 2 lines come when ``upcase`` is mutated to ``True`` and the last 3 45 | lines come when ``number`` is mutated to ``3``. 46 | 47 | Notice that ``TestFlaky`` never asserts the contents of the written text, 48 | nor the fact that it may be in upper case. However due to unrelated failures 49 | we're left to think that the test suite tests everything correctly. To see the 50 | real results execute :: 51 | 52 | $ rm test.txt 53 | $ cosmic-ray worker flaky boolean_replacer 0 nose -- test_flaky.py:TestFlaky 54 | Outcome.SURVIVED 55 | --- mutation diff --- 56 | --- a/example_02/flaky.py 57 | +++ b/example_02/flaky.py 58 | @@ -5,7 +5,7 @@ 59 | data_file.write(content) 60 | data_file.close() 61 | 62 | -def sayHello(times=2, upcase=False): 63 | +def sayHello(times=2, upcase=True): 64 | text = 'Hello World\\n' 65 | if upcase: 66 | text = text.upper() 67 | 68 | $ rm test.txt 69 | $ cosmic-ray worker flaky number_replacer 0 nose -- test_flaky.py:TestFlaky 70 | Outcome.KILLED 71 | Traceback (most recent call last): 72 | File "./example_02/test_flaky.py", line 10, in test_sayHello 73 | self.assertEqual(len(lines), 2) 74 | AssertionError: 3 != 2 75 | 76 | --- mutation diff --- 77 | --- a/example_02/flaky.py 78 | +++ b/example_02/flaky.py 79 | @@ -5,7 +5,7 @@ 80 | data_file.write(content) 81 | data_file.close() 82 | 83 | -def sayHello(times=2, upcase=False): 84 | +def sayHello(times=3, upcase=False): 85 | text = 'Hello World\\n' 86 | if upcase: 87 | text = text.upper() 88 | 89 | 90 | The second test ``TestFlakyWithMock`` is better because it properly isolates 91 | interaction with the filesystem and because 92 | it properly verifies the expected behavior. All mutants are properly killed 93 | this time :: 94 | 95 | $ cosmic-ray run --test-runner nose --baseline=10 example.json flaky.py -- test_flaky.py:TestFlakyWithMock 96 | $ cosmic-ray report example.json --full-report 97 | $ ls -l test.txt 98 | ls: cannot access test.txt: No such file or directory 99 | 100 | .. note:: 101 | 102 | Since commit `db7b7c6` Cosmic Ray will fail if baseline test execution fails. 103 | This isn't the same as having unreliable tests but may help you identify 104 | something isn't right sooner than later. If you want to experiment execute the 105 | above `cosmic-ray run` command twice without deleting `test.txt` between test runs! 106 | 107 | 108 | Source code 109 | =========== 110 | 111 | 112 | .. literalinclude:: flaky.py 113 | :caption: flaky.py 114 | :language: python 115 | 116 | .. literalinclude:: test_flaky.py 117 | :caption: test_flaky.py 118 | :language: python 119 | -------------------------------------------------------------------------------- /python/example_02/flaky.py: -------------------------------------------------------------------------------- 1 | def log_to_file(content): 2 | data_file = open('./test.txt', 'a+') 3 | data_file.write(content) 4 | data_file.close() 5 | 6 | def sayHello(times=2, upcase = False): 7 | text = 'Hello World\n' 8 | 9 | if upcase: 10 | text = text.upper() 11 | 12 | for i in range(times): 13 | log_to_file(text) 14 | -------------------------------------------------------------------------------- /python/example_02/test_flaky.py: -------------------------------------------------------------------------------- 1 | import flaky 2 | import unittest 3 | from unittest import mock 4 | 5 | class TestFlaky(unittest.TestCase): 6 | def test_sayHello(self): 7 | flaky.sayHello() 8 | try: 9 | f = open('./test.txt') 10 | lines = f.readlines() 11 | self.assertEqual(len(lines), 2) 12 | finally: 13 | f.close() 14 | 15 | class TestFlakyWithMock(unittest.TestCase): 16 | @mock.patch('flaky.log_to_file') 17 | def test_sayHello(self, _log_to_file): 18 | calls = [mock.call('Hello World\n'), mock.call('Hello World\n')] 19 | flaky.sayHello() 20 | # called twice with lower case string 21 | self.assertEqual(_log_to_file.call_count, 2) 22 | _log_to_file.assert_has_calls(calls) 23 | 24 | 25 | if __name__ == "__main__": 26 | unittest.main() 27 | -------------------------------------------------------------------------------- /python/example_03/README.rst: -------------------------------------------------------------------------------- 1 | Killing mutants by refactoring if str != "" 2 | ******************************************* 3 | 4 | Reproducer 5 | ========== 6 | 7 | :: 8 | 9 | $ pip install nose 10 | $ pip install https://github.com/sixty-north/cosmic-ray/zipball/master 11 | 12 | $ cosmic-ray run --test-runner nose --baseline=10 example.json hello.py -- test_hello.py 13 | $ cosmic-ray report example.json 14 | 15 | job ID 3:Outcome.SURVIVED:hello 16 | command: cosmic-ray worker hello replace_NotEq_with_NotIn 0 nose -- test_hello.py 17 | --- mutation diff --- 18 | --- a/example_03/hello.py 19 | +++ b/example_03/hello.py 20 | @@ -1,7 +1,7 @@ 21 | 22 | 23 | def sayHello(name, greeting=''): 24 | - if (greeting != ''): 25 | + if (greeting not in ''): 26 | return ((greeting + ', ') + name) 27 | else: 28 | return ('Hello, ' + name) 29 | 30 | job ID 8:Outcome.SURVIVED:hello 31 | command: cosmic-ray worker hello replace_NotEq_with_Gt 0 nose -- test_hello.py 32 | --- mutation diff --- 33 | --- a/example_03/hello.py 34 | +++ b/example_03/hello.py 35 | @@ -1,7 +1,7 @@ 36 | 37 | 38 | def sayHello(name, greeting=''): 39 | - if (greeting != ''): 40 | + if (greeting > ''): 41 | return ((greeting + ', ') + name) 42 | else: 43 | return ('Hello, ' + name) 44 | 45 | total jobs: 8 46 | complete: 8 (100.00%) 47 | survival rate: 25.00% 48 | 49 | 50 | Now compare the results with ``hello2.py`` and ``test_hello2.py`` where we've 51 | refactored the condition to ``if greeting:``:: 52 | 53 | $ cosmic-ray run --test-runner nose --baseline=10 example.json hello2.py -- test_hello2.py 54 | $ cosmic-ray report example.json 55 | total jobs: 0 56 | no jobs completed 57 | 58 | 59 | Source code 60 | =========== 61 | 62 | 63 | .. literalinclude:: hello.py 64 | :caption: hello.py 65 | :language: python 66 | 67 | .. literalinclude:: hello2.py 68 | :caption: hello2.py 69 | :language: python 70 | 71 | .. literalinclude:: test_hello.py 72 | :caption: test_hello.py 73 | :language: python 74 | 75 | .. literalinclude:: test_hello2.py 76 | :caption: test_hello2.py 77 | :language: python 78 | -------------------------------------------------------------------------------- /python/example_03/hello.py: -------------------------------------------------------------------------------- 1 | def sayHello(name, greeting=""): 2 | if greeting != "": 3 | return greeting + ', ' + name 4 | else: 5 | return "Hello, " + name 6 | -------------------------------------------------------------------------------- /python/example_03/hello2.py: -------------------------------------------------------------------------------- 1 | def sayHello(name, greeting=""): 2 | if greeting: 3 | return greeting + ', ' + name 4 | else: 5 | return "Hello, " + name 6 | -------------------------------------------------------------------------------- /python/example_03/test_hello.py: -------------------------------------------------------------------------------- 1 | import hello 2 | import unittest 3 | 4 | class TestHello(unittest.TestCase): 5 | def test_sayHello_name(self): 6 | result = hello.sayHello("Alex") 7 | self.assertEqual(result, "Hello, Alex") 8 | 9 | def test_sayHello_name_with_greeting(self): 10 | result = hello.sayHello("Alex", "Happy testing") 11 | self.assertEqual(result, "Happy testing, Alex") 12 | 13 | if __name__ == "__main__": 14 | unittest.main() 15 | -------------------------------------------------------------------------------- /python/example_03/test_hello2.py: -------------------------------------------------------------------------------- 1 | import hello2 2 | import unittest 3 | 4 | class TestHello(unittest.TestCase): 5 | def test_sayHello_name(self): 6 | result = hello2.sayHello("Alex") 7 | self.assertEqual(result, "Hello, Alex") 8 | 9 | def test_sayHello_name_with_greeting(self): 10 | result = hello2.sayHello("Alex", "Happy testing") 11 | self.assertEqual(result, "Happy testing, Alex") 12 | 13 | if __name__ == "__main__": 14 | unittest.main() 15 | -------------------------------------------------------------------------------- /python/example_04/README.rst: -------------------------------------------------------------------------------- 1 | Testing for X != 1 2 | ****************** 3 | 4 | Reproducer 5 | ========== 6 | 7 | :: 8 | 9 | $ pip install nose 10 | $ pip install https://github.com/sixty-north/cosmic-ray/zipball/master 11 | 12 | $ cosmic-ray run --test-runner nose --baseline=10 example.json hello.py -- test_hello.py:TestHello 13 | $ cosmic-ray report example.json 14 | 15 | job ID 1:Outcome.SURVIVED:hello 16 | command: cosmic-ray worker hello replace_NotEq_with_Gt 0 nose -- -v test_hello.py:TestHello 17 | --- mutation diff --- 18 | --- a/example_04/hello.py 19 | +++ b/example_04/hello.py 20 | @@ -3,7 +3,7 @@ 21 | 22 | def sayHello(name): 23 | names = myparser.parseArgs(name) 24 | - if (len(names) != 1): 25 | + if (len(names) > 1): 26 | raise Exception('You can say hello to only one person at a time!') 27 | return ('Hello, ' + name) 28 | 29 | job ID 3:Outcome.SURVIVED:hello 30 | command: cosmic-ray worker hello replace_NotEq_with_Lt 0 nose -- -v test_hello.py:TestHello 31 | --- mutation diff --- 32 | --- a/example_04/hello.py 33 | +++ b/example_04/hello.py 34 | @@ -3,7 +3,7 @@ 35 | 36 | def sayHello(name): 37 | names = myparser.parseArgs(name) 38 | - if (len(names) != 1): 39 | + if (len(names) < 1): 40 | raise Exception('You can say hello to only one person at a time!') 41 | return ('Hello, ' + name) 42 | 43 | total jobs: 9 44 | complete: 9 (100.00%) 45 | survival rate: 22.22% 46 | 47 | 48 | Now compare the results from ``TestHelloProperly`` :: 49 | 50 | $ cosmic-ray run --test-runner nose --baseline=10 example.json hello.py -- test_hello.py:TestHelloProperly 51 | $ cosmic-ray report example.json --full-report 52 | total jobs: 9 53 | complete: 9 (100.00%) 54 | survival rate: 0% 55 | 56 | 57 | Source code 58 | =========== 59 | 60 | 61 | .. literalinclude:: hello.py 62 | :caption: hello.py 63 | :language: python 64 | 65 | .. literalinclude:: myparser.py 66 | :caption: myparser.py 67 | :language: python 68 | 69 | .. literalinclude:: test_hello.py 70 | :caption: test_hello.py 71 | :language: python 72 | -------------------------------------------------------------------------------- /python/example_04/hello.py: -------------------------------------------------------------------------------- 1 | import myparser 2 | 3 | def sayHello(name): 4 | names = myparser.parseArgs(name) 5 | 6 | if len(names) != 1: 7 | raise Exception("You can say hello to only one person at a time!") 8 | 9 | return "Hello, " + name 10 | -------------------------------------------------------------------------------- /python/example_04/myparser.py: -------------------------------------------------------------------------------- 1 | def parseArgs(args): 2 | if not args: 3 | return [] 4 | 5 | return args.split(',') 6 | -------------------------------------------------------------------------------- /python/example_04/test_hello.py: -------------------------------------------------------------------------------- 1 | import hello 2 | import unittest 3 | 4 | class TestHello(unittest.TestCase): 5 | def test_sayHello_to_one_person(self): 6 | result = hello.sayHello("Alex") 7 | self.assertEqual(result, "Hello, Alex") 8 | 9 | class TestHelloProperly(TestHello): 10 | def test_sayHello_to_nobody(self): 11 | with self.assertRaises(Exception): 12 | hello.sayHello("") 13 | 14 | def test_sayHello_to_many_people(self): 15 | with self.assertRaises(Exception): 16 | hello.sayHello("Alex,Krasi") 17 | 18 | if __name__ == "__main__": 19 | unittest.main() 20 | -------------------------------------------------------------------------------- /python/example_05/README.rst: -------------------------------------------------------------------------------- 1 | Refactor if len(list) != 0 2 | ************************** 3 | 4 | Reproducer 5 | ========== 6 | 7 | :: 8 | 9 | $ pip install nose 10 | $ pip install https://github.com/sixty-north/cosmic-ray/zipball/master 11 | 12 | $ cosmic-ray run --test-runner nose --baseline=10 example.json hello.py -- test_hello.py 13 | $ cosmic-ray report example.json 14 | 15 | job ID 7:Outcome.SURVIVED:hello 16 | command: cosmic-ray worker hello replace_NotEq_with_Gt 0 nose -- -v test_hello.py 17 | --- mutation diff --- 18 | --- a/example_05/hello.py 19 | +++ b/example_05/hello.py 20 | @@ -1,7 +1,7 @@ 21 | 22 | 23 | def sayHello(name, friends): 24 | - if (len(friends) != 0): 25 | + if (len(friends) > 0): 26 | raise Exception("You can't say hello to other people's friends!") 27 | return ('Hello, ' + name) 28 | 29 | total jobs: 9 30 | complete: 9 (100.00%) 31 | survival rate: 11.11% 32 | 33 | .. note:: 34 | 35 | If we didn't have the ``test_sayHello_with_friends()`` test then the 36 | ``!= -> <`` mutation would have survived as well! 37 | 38 | Now compare the results after refactoring :: 39 | 40 | $ cosmic-ray run --test-runner nose --baseline=10 example.json hello2.py -- test_hello2.py 41 | $ cosmic-ray report example.json --full-report 42 | total jobs: 0 43 | no jobs completed 44 | 45 | Functionality is the same but we have reduced the number of possible mutations! 46 | 47 | Source code 48 | =========== 49 | 50 | 51 | .. literalinclude:: hello.py 52 | :caption: hello.py 53 | :language: python 54 | 55 | .. literalinclude:: test_hello.py 56 | :caption: test_hello.py 57 | :language: python 58 | 59 | .. literalinclude:: hello2.py 60 | :caption: hello2.py 61 | :language: python 62 | 63 | .. literalinclude:: test_hello2.py 64 | :caption: test_hello2.py 65 | :language: python 66 | -------------------------------------------------------------------------------- /python/example_05/hello.py: -------------------------------------------------------------------------------- 1 | def sayHello(name, friends): 2 | if len(friends) != 0: 3 | raise Exception("You can't say hello to other people's friends!") 4 | 5 | return "Hello, " + name 6 | -------------------------------------------------------------------------------- /python/example_05/hello2.py: -------------------------------------------------------------------------------- 1 | def sayHello(name, friends): 2 | if friends: 3 | raise Exception("You can't say hello to other people's friends!") 4 | 5 | return "Hello, " + name 6 | -------------------------------------------------------------------------------- /python/example_05/test_hello.py: -------------------------------------------------------------------------------- 1 | import hello 2 | import unittest 3 | 4 | class TestHello(unittest.TestCase): 5 | def test_sayHello_without_friends(self): 6 | result = hello.sayHello("Alex", []) 7 | self.assertEqual(result, "Hello, Alex") 8 | 9 | def test_sayHello_with_friends(self): 10 | with self.assertRaises(Exception): 11 | hello.sayHello("Alex", ["Krasi"]) 12 | 13 | 14 | if __name__ == "__main__": 15 | unittest.main() 16 | -------------------------------------------------------------------------------- /python/example_05/test_hello2.py: -------------------------------------------------------------------------------- 1 | import hello2 2 | import unittest 3 | 4 | class TestHello(unittest.TestCase): 5 | def test_sayHello_without_friends(self): 6 | result = hello2.sayHello("Alex", []) 7 | self.assertEqual(result, "Hello, Alex") 8 | 9 | def test_sayHello_with_friends(self): 10 | with self.assertRaises(Exception): 11 | hello2.sayHello("Alex", ["Krasi"]) 12 | 13 | 14 | if __name__ == "__main__": 15 | unittest.main() 16 | -------------------------------------------------------------------------------- /python/example_06/README.rst: -------------------------------------------------------------------------------- 1 | Refactor if X is None 2 | ********************* 3 | 4 | Reproducer 5 | ========== 6 | 7 | :: 8 | 9 | $ pip install nose 10 | $ pip install https://github.com/sixty-north/cosmic-ray/zipball/master 11 | 12 | $ cosmic-ray run --test-runner nose --baseline=10 example.json hello.py -- test_hello.py 13 | $ cosmic-ray report example.json 14 | 15 | job ID 4:Outcome.SURVIVED:hello 16 | command: cosmic-ray worker hello replace_Is_with_Eq 0 nose -- -v test_hello.py 17 | --- mutation diff --- 18 | --- a/example_06/hello.py 19 | +++ b/example_06/hello.py 20 | @@ -1,7 +1,7 @@ 21 | 22 | 23 | def sayHello(name, title=None): 24 | - if (title is None): 25 | + if (title == None): 26 | title = 'Mr.' 27 | return ('Hello %s %s' % (title, name)) 28 | 29 | total jobs: 9 30 | complete: 9 (100.00%) 31 | survival rate: 11.11% 32 | 33 | .. note:: 34 | 35 | Since `PR #162 `_ 36 | *Cosmic-Ray* skips mutations of the kind ``== -> is`` but doesn't skip 37 | the opposite of ``is -> ==``! 38 | 39 | Now compare the results after refactoring :: 40 | 41 | $ cosmic-ray run --test-runner nose --baseline=10 example.json hello2.py -- test_hello2.py 42 | $ cosmic-ray report example.json --full-report 43 | total jobs: 0 44 | no jobs completed 45 | 46 | Functionality is the same but we have reduced the number of possible mutations! 47 | 48 | Source code 49 | =========== 50 | 51 | 52 | .. literalinclude:: hello.py 53 | :caption: hello.py 54 | :language: python 55 | 56 | .. literalinclude:: test_hello.py 57 | :caption: test_hello.py 58 | :language: python 59 | 60 | .. literalinclude:: hello2.py 61 | :caption: hello2.py 62 | :language: python 63 | 64 | .. literalinclude:: test_hello2.py 65 | :caption: test_hello2.py 66 | :language: python 67 | -------------------------------------------------------------------------------- /python/example_06/hello.py: -------------------------------------------------------------------------------- 1 | def sayHello(name, title=None): 2 | if title is None: 3 | title = "Mr." 4 | 5 | return "Hello %s %s" % (title, name) 6 | -------------------------------------------------------------------------------- /python/example_06/hello2.py: -------------------------------------------------------------------------------- 1 | def sayHello(name, title=None): 2 | if not title: 3 | title = "Mr." 4 | 5 | return "Hello %s %s" % (title, name) 6 | -------------------------------------------------------------------------------- /python/example_06/test_hello.py: -------------------------------------------------------------------------------- 1 | import hello 2 | import unittest 3 | 4 | class TestHello(unittest.TestCase): 5 | def test_sayHello_without_title(self): 6 | result = hello.sayHello("Senko") 7 | self.assertEqual(result, "Hello Mr. Senko") 8 | 9 | def test_sayHello_with_title(self): 10 | result = hello.sayHello("Senko", title="The Misterious") 11 | self.assertEqual(result, "Hello The Misterious Senko") 12 | 13 | 14 | if __name__ == "__main__": 15 | unittest.main() 16 | -------------------------------------------------------------------------------- /python/example_06/test_hello2.py: -------------------------------------------------------------------------------- 1 | import hello 2 | import unittest 3 | 4 | class TestHello(unittest.TestCase): 5 | def test_sayHello_without_title(self): 6 | result = hello.sayHello("Senko") 7 | self.assertEqual(result, "Hello Mr. Senko") 8 | 9 | def test_sayHello_with_title(self): 10 | result = hello.sayHello("Senko", title="The Misterious") 11 | self.assertEqual(result, "Hello The Misterious Senko") 12 | 13 | 14 | if __name__ == "__main__": 15 | unittest.main() 16 | -------------------------------------------------------------------------------- /python/example_07/README.rst: -------------------------------------------------------------------------------- 1 | Testing __eq__ & __ne__ 2 | *********************** 3 | 4 | This is an example of testing overriden ``__eq__`` and ``__ne__`` methods. 5 | In this example objects are identified by the value of their attributes. 6 | There is an example where attributes are of the same type and an example 7 | where attributes are of different types. 8 | 9 | To kill all mutants we have to exercise all possible methods and comparison 10 | branches in them, see source comments for more info. 11 | 12 | Once we've stablished what is equal and how that compares to ``None`` and itself 13 | we can start testing comparisons by modifying the attribute values one by one. 14 | Note that equality comparison works both ways, that is ``X == Y`` is the same 15 | as ``Y == X`` so we test it that way. 16 | 17 | Comment out the ``test_default_objects_are_always_equal()`` method and one of 18 | the ``assertNotEqual(sandwich_1, sandwich_2)`` lines and re-test to see the 19 | difference in results! 20 | 21 | Reproducer 22 | ========== 23 | 24 | :: 25 | 26 | $ pip install nose 27 | $ pip install https://github.com/sixty-north/cosmic-ray/zipball/master 28 | 29 | $ cosmic-ray run --test-runner nose --baseline=10 example.json sandwich.py -- tests.py 30 | $ cosmic-ray report example.json --full-report 31 | 32 | 33 | Source code 34 | =========== 35 | 36 | 37 | .. literalinclude:: sandwich.py 38 | :caption: sandwich.py 39 | :language: python 40 | 41 | .. literalinclude:: tests.py 42 | :caption: tests.py 43 | :language: python 44 | -------------------------------------------------------------------------------- /python/example_07/sandwich.py: -------------------------------------------------------------------------------- 1 | class Sandwich(object): 2 | def __init__(self, meat = None, bread = None): 3 | """ 4 | @meat - string 5 | @bread - string 6 | 7 | Parameters identify the type of ingredients used. 8 | """ 9 | self.meat = meat 10 | self.bread = bread 11 | 12 | def __eq__(self, other): 13 | if not other: 14 | return False 15 | 16 | return self.meat == other.meat and self.bread == other.bread 17 | 18 | def __ne__(self, other): 19 | return not self == other 20 | 21 | class SandwichWithMayoAndEggs(Sandwich): 22 | def __init__(self, meat = None, bread = None, mayo=True, eggs=2): 23 | super(self.__class__, self).__init__(meat, bread) 24 | self.mayo = mayo 25 | self.eggs = eggs 26 | 27 | def __eq__(self, other): 28 | if not other: 29 | return False 30 | 31 | return self.meat == other.meat and self.bread == other.bread and \ 32 | self.mayo == other.mayo and self.eggs == other.eggs 33 | -------------------------------------------------------------------------------- /python/example_07/tests.py: -------------------------------------------------------------------------------- 1 | import sandwich 2 | import unittest 3 | 4 | class TestSandwich(unittest.TestCase): 5 | def setUp(self): 6 | self.sandwich_1 = sandwich.Sandwich() 7 | self.sandwich_2 = sandwich.Sandwich() 8 | 9 | def test_default_objects_are_always_equal(self): 10 | """ 11 | Newly created objects with the same attribute values 12 | (e.g. default ones) are always equal. At the same time 13 | != will return False for the same objects. 14 | """ 15 | self.assertEqual(self.sandwich_1, self.sandwich_2) 16 | self.assertFalse(self.sandwich_1 != self.sandwich_2) 17 | 18 | 19 | def test_object_does_not_equal_None(self): 20 | """ 21 | An object instance is never equal to None. 22 | Tests the if statement in the __eq__ method. 23 | """ 24 | self.assertNotEqual(self.sandwich_1, None) 25 | 26 | 27 | def test_objects_differing_by_one_attribute_are_not_equal(self): 28 | """ 29 | Objects are never equal if at least one attribute differs. 30 | To test all attributes we loop over each at a time, 31 | update the values and compare the objects. 32 | """ 33 | for atr in ['meat', 'bread']: 34 | setattr(self.sandwich_1, atr, 'test') 35 | setattr(self.sandwich_2, atr, '') 36 | # comparison works both ways so test it both ways 37 | self.assertNotEqual(self.sandwich_1, self.sandwich_2) 38 | self.assertNotEqual(self.sandwich_2, self.sandwich_1) 39 | setattr(self.sandwich_1, atr, '') 40 | setattr(self.sandwich_2, atr, '') 41 | 42 | class TestSandwichWithMayoAndEggs(unittest.TestCase): 43 | def setUp(self): 44 | self.sandwich_1 = sandwich.SandwichWithMayoAndEggs() 45 | self.sandwich_2 = sandwich.SandwichWithMayoAndEggs() 46 | 47 | def test_object_does_not_equal_None(self): 48 | self.assertNotEqual(self.sandwich_1, None) 49 | 50 | def test_default_values(self): 51 | self.assertEqual(self.sandwich_1.mayo, True) 52 | self.assertEqual(self.sandwich_1.eggs, 2) 53 | 54 | def test_objects_differing_by_one_attribute_are_not_equal(self): 55 | """ this time object attributes are of different types""" 56 | # meat 57 | self.sandwich_1.meat = 'Chicken' 58 | self.sandwich_2.meat = '' 59 | self.assertNotEqual(self.sandwich_1, self.sandwich_2) 60 | self.assertNotEqual(self.sandwich_2, self.sandwich_1) 61 | self.sandwich_1.meat = '' 62 | self.sandwich_2.meat = '' 63 | 64 | # bread 65 | self.sandwich_1.bread = 'Baguette' 66 | self.sandwich_2.bread = '' 67 | self.assertNotEqual(self.sandwich_1, self.sandwich_2) 68 | self.assertNotEqual(self.sandwich_2, self.sandwich_1) 69 | self.sandwich_1.bread = '' 70 | self.sandwich_2.bread = '' 71 | 72 | # mayo 73 | self.sandwich_1.mayo = True 74 | self.sandwich_2.mayo = False 75 | self.assertNotEqual(self.sandwich_1, self.sandwich_2) 76 | self.assertNotEqual(self.sandwich_2, self.sandwich_1) 77 | self.sandwich_1.mayo = False 78 | self.sandwich_2.mayo = False 79 | 80 | self.sandwich_1.eggs = 2 81 | self.sandwich_2.eggs = 0 82 | self.assertNotEqual(self.sandwich_1, self.sandwich_2) 83 | self.assertNotEqual(self.sandwich_2, self.sandwich_1) 84 | self.sandwich_1.eggs = 0 85 | self.sandwich_2.eggs = 0 86 | 87 | if __name__ == "__main__": 88 | unittest.main() 89 | -------------------------------------------------------------------------------- /python/example_08/README.rst: -------------------------------------------------------------------------------- 1 | Testing sequence of if == int statements 2 | **************************************** 3 | 4 | Sometimes in programs we see the following pattern 5 | 6 | .. code-block:: python 7 | 8 | if X == int_1: 9 | pass 10 | elif X == int_2: 11 | pass 12 | elif X == int_3: 13 | pass 14 | 15 | ``X`` is compared to several allowed values of type integer. 16 | It is important to notice that ``X`` accepts a descrete set of 17 | allowed values. When we forget to test with values outside the allowed set 18 | mutation testing will produce surviving mutants. 19 | 20 | .. note:: 21 | 22 | The order of if statements isn't important. 23 | 24 | 25 | Reproducer 26 | ========== 27 | 28 | :: 29 | 30 | $ pip install nose 31 | $ pip install https://github.com/sixty-north/cosmic-ray/zipball/master 32 | 33 | $ cosmic-ray run --test-runner nose --baseline=10 example.json selinux.py -- tests.py:Test_mode_from_int 34 | $ cosmic-ray report example.json 35 | 36 | job ID 3:Outcome.SURVIVED:selinux 37 | command: cosmic-ray worker selinux replace_Eq_with_GtE 2 nose -- -v tests.py:Test_mode_from_int 38 | --- mutation diff --- 39 | --- a/example_08/selinux.py 40 | +++ b/example_08/selinux.py 41 | @@ -7,7 +7,7 @@ 42 | retval += 'disabled' 43 | elif (int_mode == modes.SELINUX_ENFORCING): 44 | retval += 'enforcing' 45 | - elif (int_mode == modes.SELINUX_PERMISSIVE): 46 | + elif (int_mode >= modes.SELINUX_PERMISSIVE): 47 | retval += 'permissive' 48 | return retval 49 | 50 | job ID 16:Outcome.SURVIVED:selinux 51 | command: cosmic-ray worker selinux replace_Eq_with_LtE 0 nose -- -v tests.py:Test_mode_from_int 52 | --- mutation diff --- 53 | --- a/example_08/selinux.py 54 | +++ b/example_08/selinux.py 55 | @@ -3,7 +3,7 @@ 56 | 57 | def mode_from_int(int_mode): 58 | retval = '' 59 | - if (int_mode == modes.SELINUX_DISABLED): 60 | + if (int_mode <= modes.SELINUX_DISABLED): 61 | retval += 'disabled' 62 | elif (int_mode == modes.SELINUX_ENFORCING): 63 | retval += 'enforcing' 64 | 65 | job ID 17:Outcome.SURVIVED:selinux 66 | command: cosmic-ray worker selinux replace_Eq_with_LtE 1 nose -- -v tests.py:Test_mode_from_int 67 | --- mutation diff --- 68 | --- a/example_08/selinux.py 69 | +++ b/example_08/selinux.py 70 | @@ -5,7 +5,7 @@ 71 | retval = '' 72 | if (int_mode == modes.SELINUX_DISABLED): 73 | retval += 'disabled' 74 | - elif (int_mode == modes.SELINUX_ENFORCING): 75 | + elif (int_mode <= modes.SELINUX_ENFORCING): 76 | retval += 'enforcing' 77 | elif (int_mode == modes.SELINUX_PERMISSIVE): 78 | retval += 'permissive' 79 | 80 | job ID 18:Outcome.SURVIVED:selinux 81 | command: cosmic-ray worker selinux replace_Eq_with_LtE 2 nose -- -v tests.py:Test_mode_from_int 82 | --- mutation diff --- 83 | --- a/example_08/selinux.py 84 | +++ b/example_08/selinux.py 85 | @@ -7,7 +7,7 @@ 86 | retval += 'disabled' 87 | elif (int_mode == modes.SELINUX_ENFORCING): 88 | retval += 'enforcing' 89 | - elif (int_mode == modes.SELINUX_PERMISSIVE): 90 | + elif (int_mode <= modes.SELINUX_PERMISSIVE): 91 | retval += 'permissive' 92 | return retval 93 | 94 | total jobs: 24 95 | complete: 24 (100.00%) 96 | survival rate: 16.67% 97 | 98 | 99 | Killing the mutants 100 | =================== 101 | 102 | 103 | To kill all mutants we need to test with values outside the allowed set :: 104 | 105 | $ cosmic-ray run --test-runner nose --baseline=10 example.json selinux.py -- tests.py:TestCompletely 106 | $ cosmic-ray report example.json 107 | 108 | 109 | Source code 110 | =========== 111 | 112 | 113 | .. literalinclude:: modes.py 114 | :caption: modes.py 115 | :language: python 116 | 117 | .. literalinclude:: selinux.py 118 | :caption: selinux.py 119 | :language: python 120 | 121 | .. literalinclude:: tests.py 122 | :caption: tests.py 123 | :language: python 124 | -------------------------------------------------------------------------------- /python/example_08/README_str.rst: -------------------------------------------------------------------------------- 1 | Testing sequence of if == string statements 2 | ******************************************* 3 | 4 | Sometimes in programs we see the following pattern 5 | 6 | .. code-block:: python 7 | 8 | if X == "string_1": 9 | pass 10 | elif X == "string_2": 11 | pass 12 | elif X == "string_3": 13 | pass 14 | 15 | ``X`` is compared to several allowed values of type string. 16 | It is important to notice that ``X`` accepts a descrete set of 17 | allowed values. When we forget to test with string values outside the allowed set 18 | mutation testing will produce surviving mutants. 19 | 20 | .. note:: 21 | 22 | The order of if statements isn't important. 23 | 24 | 25 | Reproducer 26 | ========== 27 | 28 | :: 29 | 30 | $ pip install nose 31 | $ pip install https://github.com/sixty-north/cosmic-ray/zipball/master 32 | $ celery -A cosmic_ray.tasks.worker worker 33 | 34 | $ cosmic-ray run --test-runner nose --baseline=10 example.json selinux_str.py -- tests_str.py:Test_mode_from_str 35 | $ cosmic-ray report example.json 36 | 37 | job ID 4:Outcome.SURVIVED:selinux_str 38 | command: cosmic-ray worker selinux_str mutate_comparison_operator 2 nose -- tests_str.py:Test_mode_from_str 39 | --- mutation diff --- 40 | --- a/example_08/selinux_str.py 41 | +++ b/example_08/selinux_str.py 42 | @@ -3,7 +3,7 @@ 43 | 44 | def mode_from_str(str_mode): 45 | retval = None 46 | - if (str_mode == 'disabled'): 47 | + if (str_mode <= 'disabled'): 48 | retval = modes.SELINUX_DISABLED 49 | elif (str_mode == 'enforcing'): 50 | retval = modes.SELINUX_ENFORCING 51 | 52 | job ID 8:Outcome.SURVIVED:selinux_str 53 | command: cosmic-ray worker selinux_str mutate_comparison_operator 6 nose -- tests_str.py:Test_mode_from_str 54 | --- mutation diff --- 55 | --- a/example_08/selinux_str.py 56 | +++ b/example_08/selinux_str.py 57 | @@ -3,7 +3,7 @@ 58 | 59 | def mode_from_str(str_mode): 60 | retval = None 61 | - if (str_mode == 'disabled'): 62 | + if (str_mode in 'disabled'): 63 | retval = modes.SELINUX_DISABLED 64 | elif (str_mode == 'enforcing'): 65 | retval = modes.SELINUX_ENFORCING 66 | 67 | job ID 12:Outcome.SURVIVED:selinux_str 68 | command: cosmic-ray worker selinux_str mutate_comparison_operator 10 nose -- tests_str.py:Test_mode_from_str 69 | --- mutation diff --- 70 | --- a/example_08/selinux_str.py 71 | +++ b/example_08/selinux_str.py 72 | @@ -5,7 +5,7 @@ 73 | retval = None 74 | if (str_mode == 'disabled'): 75 | retval = modes.SELINUX_DISABLED 76 | - elif (str_mode == 'enforcing'): 77 | + elif (str_mode <= 'enforcing'): 78 | retval = modes.SELINUX_ENFORCING 79 | elif (str_mode == 'permissive'): 80 | retval = modes.SELINUX_PERMISSIVE 81 | 82 | job ID 16:Outcome.SURVIVED:selinux_str 83 | command: cosmic-ray worker selinux_str mutate_comparison_operator 14 nose -- tests_str.py:Test_mode_from_str 84 | --- mutation diff --- 85 | --- a/example_08/selinux_str.py 86 | +++ b/example_08/selinux_str.py 87 | @@ -5,7 +5,7 @@ 88 | retval = None 89 | if (str_mode == 'disabled'): 90 | retval = modes.SELINUX_DISABLED 91 | - elif (str_mode == 'enforcing'): 92 | + elif (str_mode in 'enforcing'): 93 | retval = modes.SELINUX_ENFORCING 94 | elif (str_mode == 'permissive'): 95 | retval = modes.SELINUX_PERMISSIVE 96 | 97 | job ID 20:Outcome.SURVIVED:selinux_str 98 | command: cosmic-ray worker selinux_str mutate_comparison_operator 18 nose -- tests_str.py:Test_mode_from_str 99 | --- mutation diff --- 100 | --- a/example_08/selinux_str.py 101 | +++ b/example_08/selinux_str.py 102 | @@ -7,7 +7,7 @@ 103 | retval = modes.SELINUX_DISABLED 104 | elif (str_mode == 'enforcing'): 105 | retval = modes.SELINUX_ENFORCING 106 | - elif (str_mode == 'permissive'): 107 | + elif (str_mode <= 'permissive'): 108 | retval = modes.SELINUX_PERMISSIVE 109 | return retval 110 | 111 | 112 | job ID 22:Outcome.SURVIVED:selinux_str 113 | command: cosmic-ray worker selinux_str mutate_comparison_operator 20 nose -- tests_str.py:Test_mode_from_str 114 | --- mutation diff --- 115 | --- a/example_08/selinux_str.py 116 | +++ b/example_08/selinux_str.py 117 | @@ -7,7 +7,7 @@ 118 | retval = modes.SELINUX_DISABLED 119 | elif (str_mode == 'enforcing'): 120 | retval = modes.SELINUX_ENFORCING 121 | - elif (str_mode == 'permissive'): 122 | + elif (str_mode >= 'permissive'): 123 | retval = modes.SELINUX_PERMISSIVE 124 | return retval 125 | 126 | 127 | job ID 24:Outcome.SURVIVED:selinux_str 128 | command: cosmic-ray worker selinux_str mutate_comparison_operator 22 nose -- tests_str.py:Test_mode_from_str 129 | --- mutation diff --- 130 | --- a/example_08/selinux_str.py 131 | +++ b/example_08/selinux_str.py 132 | @@ -7,7 +7,7 @@ 133 | retval = modes.SELINUX_DISABLED 134 | elif (str_mode == 'enforcing'): 135 | retval = modes.SELINUX_ENFORCING 136 | - elif (str_mode == 'permissive'): 137 | + elif (str_mode in 'permissive'): 138 | retval = modes.SELINUX_PERMISSIVE 139 | return retval 140 | 141 | 142 | total jobs: 25 143 | complete: 25 (100.00%) 144 | survival rate: 28.00% 145 | 146 | 147 | Killing the mutants 148 | =================== 149 | 150 | 151 | To kill some mutants we need to test with values outside the allowed set :: 152 | 153 | $ cosmic-ray run --test-runner nose --baseline=10 example.json selinux_str.py -- tests_str.py:TestCompletely 154 | $ cosmic-ray report example.json 155 | 156 | Remaining mutants 157 | ================= 158 | 159 | When testing string comparisons the ==/in mutations are equivalent and can not be killed: 160 | 161 | :: 162 | 163 | - elif (str_mode == 'permissive'): 164 | + elif (str_mode in 'permissive'): 165 | 166 | 167 | Source code 168 | =========== 169 | 170 | 171 | .. literalinclude:: modes.py 172 | :caption: modes.py 173 | :language: python 174 | 175 | .. literalinclude:: selinux_str.py 176 | :caption: selinux_str.py 177 | :language: python 178 | 179 | .. literalinclude:: tests_str.py 180 | :caption: tests_str.py 181 | :language: python 182 | -------------------------------------------------------------------------------- /python/example_08/modes.py: -------------------------------------------------------------------------------- 1 | SELINUX_DISABLED = 0 2 | SELINUX_ENFORCING = 1 3 | SELINUX_PERMISSIVE = 2 4 | -------------------------------------------------------------------------------- /python/example_08/selinux.py: -------------------------------------------------------------------------------- 1 | import modes 2 | 3 | def mode_from_int(int_mode): 4 | retval = "" 5 | 6 | if int_mode == modes.SELINUX_DISABLED: 7 | retval += "disabled" 8 | elif int_mode == modes.SELINUX_ENFORCING: 9 | retval += "enforcing" 10 | elif int_mode == modes.SELINUX_PERMISSIVE: 11 | retval += "permissive" 12 | # it doesn't matter if we have a trailing else clause or not 13 | # uncomment this to experiment 14 | # else: 15 | # retval += "unknown" 16 | 17 | return retval 18 | -------------------------------------------------------------------------------- /python/example_08/selinux_str.py: -------------------------------------------------------------------------------- 1 | import modes 2 | 3 | def mode_from_str(str_mode): 4 | retval = None 5 | 6 | if str_mode == "disabled": 7 | retval = modes.SELINUX_DISABLED 8 | elif str_mode == "enforcing": 9 | retval = modes.SELINUX_ENFORCING 10 | elif str_mode == "permissive": 11 | retval = modes.SELINUX_PERMISSIVE 12 | 13 | return retval 14 | -------------------------------------------------------------------------------- /python/example_08/tests.py: -------------------------------------------------------------------------------- 1 | import modes 2 | import selinux 3 | import unittest 4 | 5 | class Test_mode_from_int(unittest.TestCase): 6 | def test_with_disabled(self): 7 | m = selinux.mode_from_int(modes.SELINUX_DISABLED) 8 | self.assertEqual(m, "disabled") 9 | 10 | def test_with_enforcing(self): 11 | m = selinux.mode_from_int(modes.SELINUX_ENFORCING) 12 | self.assertEqual(m, "enforcing") 13 | 14 | def test_with_permissive(self): 15 | m = selinux.mode_from_int(modes.SELINUX_PERMISSIVE) 16 | self.assertEqual(m, "permissive") 17 | 18 | 19 | class TestCompletely(Test_mode_from_int): 20 | def test_with_values_outside_set(self): 21 | for mode in [-1, 9999]: 22 | m = selinux.mode_from_int(mode) 23 | self.assertEqual(m, "") 24 | 25 | if __name__ == "__main__": 26 | unittest.main() 27 | -------------------------------------------------------------------------------- /python/example_08/tests_str.py: -------------------------------------------------------------------------------- 1 | import modes 2 | import selinux_str 3 | import unittest 4 | 5 | class Test_mode_from_str(unittest.TestCase): 6 | def test_with_disabled(self): 7 | m = selinux_str.mode_from_str("disabled") 8 | self.assertEqual(m, modes.SELINUX_DISABLED) 9 | 10 | def test_with_enforcing(self): 11 | m = selinux_str.mode_from_str("enforcing") 12 | self.assertEqual(m, modes.SELINUX_ENFORCING) 13 | 14 | def test_with_permissive(self): 15 | m = selinux_str.mode_from_str("permissive") 16 | self.assertEqual(m, modes.SELINUX_PERMISSIVE) 17 | 18 | 19 | class TestCompletely(Test_mode_from_str): 20 | def test_with_values_outside_set(self): 21 | for mode in ['aaaaa', 'zzzzz']: 22 | m = selinux_str.mode_from_str(mode) 23 | self.assertEqual(m, None) 24 | 25 | if __name__ == "__main__": 26 | unittest.main() 27 | -------------------------------------------------------------------------------- /python/example_09/README.rst: -------------------------------------------------------------------------------- 1 | Missing or extra method parameters 2 | ********************************** 3 | 4 | Reproducer 5 | ========== 6 | 7 | :: 8 | 9 | $ pip install nose 10 | $ pip install https://github.com/sixty-north/cosmic-ray/zipball/master 11 | 12 | 13 | Initially we start with a set of tests which doesn't validate default values 14 | for method parameters. It may validate some other behavior which was considered 15 | more important at the time. In this example the test is empty because there is 16 | no other behavior present. This is ``test1.py``. Mutation testing will identify 17 | the missing tests as shown below :: 18 | 19 | 20 | $ cosmic-ray run --test-runner nose --baseline=10 example.json roads.py -- test1.py: 21 | $ cosmic-ray report example.json 22 | job ID 1:Outcome.SURVIVED:roads 23 | command: cosmic-ray worker roads number_replacer 0 nose -- -v test1.py 24 | --- mutation diff --- 25 | --- a/example_09/roads.py 26 | +++ b/example_09/roads.py 27 | @@ -2,7 +2,7 @@ 28 | 29 | class UrbanRoad(object): 30 | 31 | - def __init__(self, speedLimit=50, *args, **kwargs): 32 | + def __init__(self, speedLimit=51, *args, **kwargs): 33 | self.speedLimit = speedLimit 34 | 35 | class RuralRoad(UrbanRoad): 36 | 37 | job ID 2:Outcome.SURVIVED:roads 38 | command: cosmic-ray worker roads number_replacer 1 nose -- -v test1.py 39 | --- mutation diff --- 40 | --- a/example_09/roads.py 41 | +++ b/example_09/roads.py 42 | @@ -7,7 +7,7 @@ 43 | 44 | class RuralRoad(UrbanRoad): 45 | 46 | - def __init__(self, speedLimit=90, *args, **kwargs): 47 | + def __init__(self, speedLimit=91, *args, **kwargs): 48 | UrbanRoad.__init__(self, *args, **kwargs) 49 | 50 | class BaseMotorway(object): 51 | 52 | job ID 3:Outcome.SURVIVED:roads 53 | command: cosmic-ray worker roads number_replacer 2 nose -- -v test1.py 54 | --- mutation diff --- 55 | --- a/example_09/roads.py 56 | +++ b/example_09/roads.py 57 | @@ -13,7 +13,7 @@ 58 | class BaseMotorway(object): 59 | 60 | def __init__(self, *args, **kwargs): 61 | - self.minSpeed = 50 62 | + self.minSpeed = 51 63 | 64 | class Motorway(BaseMotorway): 65 | 66 | 67 | job ID 4:Outcome.SURVIVED:roads 68 | command: cosmic-ray worker roads number_replacer 3 nose -- -v test1.py 69 | --- mutation diff --- 70 | --- a/example_09/roads.py 71 | +++ b/example_09/roads.py 72 | @@ -17,6 +17,6 @@ 73 | 74 | class Motorway(BaseMotorway): 75 | 76 | - def __init__(self, speedLimit=130, *args, **kwargs): 77 | + def __init__(self, speedLimit=131, *args, **kwargs): 78 | BaseMotorway.__init__(self, speedLimit, *args, **kwargs) 79 | 80 | 81 | total jobs: 4 82 | complete: 4 (100.00%) 83 | survival rate: 100.00% 84 | 85 | Then we proceed to add the missing tests in ``test2.py``. Executing this 86 | test stand-alone identifies that something is wrong with the code under test. 87 | The ``Motorway`` constructor passes along an extra parameter which isn't 88 | consumed in the base class. In the ``RuralRoad`` constructor we've forgotten 89 | to pass the ``speedLimit`` parameter to the base constructor. :: 90 | 91 | 92 | $ python -m nose test2.py 93 | EF. 94 | ====================================================================== 95 | ERROR: test_motorway (test2.TestRoadLimits) 96 | ---------------------------------------------------------------------- 97 | Traceback (most recent call last): 98 | File "./example_09/test2.py", line 15, in test_motorway 99 | self.assertEqual(road.speedLimit, 130) 100 | AttributeError: 'Motorway' object has no attribute 'speedLimit' 101 | 102 | ====================================================================== 103 | FAIL: test_rural_road (test2.TestRoadLimits) 104 | ---------------------------------------------------------------------- 105 | Traceback (most recent call last): 106 | File "./example_09/test2.py", line 11, in test_rural_road 107 | self.assertEqual(road.speedLimit, 90) 108 | AssertionError: 50 != 90 109 | 110 | ---------------------------------------------------------------------- 111 | Ran 3 tests in 0.001s 112 | 113 | FAILED (errors=1, failures=1) 114 | 115 | Next we proceed to refactor the code under test in ``roads2.py`` 116 | 117 | .. code-block:: diff 118 | 119 | --- roads.py 2016-08-26 10:59:58.342135344 +0300 120 | +++ roads2.py 2016-08-26 11:18:30.639663587 +0300 121 | @@ -4,7 +4,7 @@ 122 | 123 | class RuralRoad(UrbanRoad): 124 | def __init__(self, speedLimit=90, *args, **kwargs): 125 | - UrbanRoad.__init__(self, *args, **kwargs) 126 | + UrbanRoad.__init__(self, speedLimit, *args, **kwargs) 127 | 128 | class BaseMotorway(object): 129 | def __init__(self, *args, **kwargs): 130 | @@ -12,4 +12,5 @@ 131 | 132 | class Motorway(BaseMotorway): 133 | def __init__(self, speedLimit=130, *args, **kwargs): 134 | - BaseMotorway.__init__(self, speedLimit, *args, **kwargs) 135 | + BaseMotorway.__init__(self, *args, **kwargs) 136 | + self.speedLimit = speedLimit 137 | 138 | 139 | In this example the refactored file is called ``roads2.py`` and its 140 | test is called ``test3.py``. 141 | 142 | .. code-block:: diff 143 | 144 | --- test2.py 2016-08-26 11:15:17.768878361 +0300 145 | +++ test3.py 2016-08-26 11:19:54.241167276 +0300 146 | @@ -1,4 +1,4 @@ 147 | -import roads 148 | +import roads2 as roads 149 | import unittest 150 | 151 | class TestRoadLimits(unittest.TestCase): 152 | 153 | 154 | Finally we can verify that the test and refactored code work together and all 155 | mutants have been killed :: 156 | 157 | $ python -m nose test3.py 158 | ... 159 | ---------------------------------------------------------------------- 160 | Ran 3 tests in 0.001s 161 | 162 | OK 163 | $ cosmic-ray run --test-runner nose --baseline=10 example.json roads2.py -- -v test3.py 164 | $ cosmic-ray report example.json 165 | total jobs: 4 166 | complete: 4 (100.00%) 167 | survival rate: 0.00% 168 | 169 | Source code 170 | =========== 171 | 172 | .. literalinclude:: roads.py 173 | :caption: roads.py 174 | :language: python 175 | 176 | .. literalinclude:: roads2.py 177 | :caption: roads2.py 178 | :language: python 179 | 180 | .. literalinclude:: test1.py 181 | :caption: test1.py 182 | :language: python 183 | 184 | .. literalinclude:: test2.py 185 | :caption: test2.py 186 | :language: python 187 | 188 | .. literalinclude:: test3.py 189 | :caption: test3.py 190 | :language: python 191 | -------------------------------------------------------------------------------- /python/example_09/roads.py: -------------------------------------------------------------------------------- 1 | class UrbanRoad(object): 2 | def __init__(self, speedLimit=50, *args, **kwargs): 3 | self.speedLimit = speedLimit 4 | 5 | class RuralRoad(UrbanRoad): 6 | def __init__(self, speedLimit=90, *args, **kwargs): 7 | UrbanRoad.__init__(self, *args, **kwargs) 8 | 9 | class BaseMotorway(object): 10 | def __init__(self, *args, **kwargs): 11 | self.minSpeed = 50 12 | 13 | class Motorway(BaseMotorway): 14 | def __init__(self, speedLimit=130, *args, **kwargs): 15 | BaseMotorway.__init__(self, speedLimit, *args, **kwargs) 16 | -------------------------------------------------------------------------------- /python/example_09/roads2.py: -------------------------------------------------------------------------------- 1 | class UrbanRoad(object): 2 | def __init__(self, speedLimit=50, *args, **kwargs): 3 | self.speedLimit = speedLimit 4 | 5 | class RuralRoad(UrbanRoad): 6 | def __init__(self, speedLimit=90, *args, **kwargs): 7 | UrbanRoad.__init__(self, speedLimit, *args, **kwargs) 8 | 9 | class BaseMotorway(object): 10 | def __init__(self, *args, **kwargs): 11 | self.minSpeed = 50 12 | 13 | class Motorway(BaseMotorway): 14 | def __init__(self, speedLimit=130, *args, **kwargs): 15 | BaseMotorway.__init__(self, *args, **kwargs) 16 | self.speedLimit = speedLimit 17 | -------------------------------------------------------------------------------- /python/example_09/test1.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | class TestRoadLimits(unittest.TestCase): 4 | pass 5 | 6 | if __name__ == "__main__": 7 | unittest.main() 8 | -------------------------------------------------------------------------------- /python/example_09/test2.py: -------------------------------------------------------------------------------- 1 | import roads 2 | import unittest 3 | 4 | class TestRoadLimits(unittest.TestCase): 5 | def test_urban_road(self): 6 | road = roads.UrbanRoad() 7 | self.assertEqual(road.speedLimit, 50) 8 | 9 | def test_rural_road(self): 10 | road = roads.RuralRoad() 11 | self.assertEqual(road.speedLimit, 90) 12 | 13 | def test_motorway(self): 14 | road = roads.Motorway() 15 | self.assertEqual(road.speedLimit, 130) 16 | self.assertEqual(road.minSpeed, 50) 17 | 18 | 19 | if __name__ == "__main__": 20 | unittest.main() 21 | -------------------------------------------------------------------------------- /python/example_09/test3.py: -------------------------------------------------------------------------------- 1 | import roads2 as roads 2 | import unittest 3 | 4 | class TestRoadLimits(unittest.TestCase): 5 | def test_urban_road(self): 6 | road = roads.UrbanRoad() 7 | self.assertEqual(road.speedLimit, 50) 8 | 9 | def test_rural_road(self): 10 | road = roads.RuralRoad() 11 | self.assertEqual(road.speedLimit, 90) 12 | 13 | def test_motorway(self): 14 | road = roads.Motorway() 15 | self.assertEqual(road.speedLimit, 130) 16 | self.assertEqual(road.minSpeed, 50) 17 | 18 | 19 | if __name__ == "__main__": 20 | unittest.main() 21 | -------------------------------------------------------------------------------- /python/example_10/README.rst: -------------------------------------------------------------------------------- 1 | Testing and refactoring boolean expressions 2 | ******************************************* 3 | 4 | When dealing with non-trivial boolean expressions mutation testing often helps 5 | put things into perspective. It causes you to rethink the expression which often 6 | leads to refactoring and killing mutants. 7 | 8 | Example 9 | ======= 10 | 11 | :: 12 | 13 | $ pip install nose 14 | $ pip install https://github.com/sixty-north/cosmic-ray/zipball/master 15 | 16 | Initially we start with the example in ``boolops1.py`` and ``test1.py``. 17 | Although the test appears to be correct, all possible values for ``list_a`` and 18 | ``list_b`` are tested, there are still surviving mutants. :: 19 | 20 | $ cosmic-ray run --test-runner nose --baseline=10 example.json boolops1.py -- test1.py: 21 | $ cosmic-ray report example.json 22 | 23 | job ID 5:Outcome.SURVIVED:boolops 24 | command: cosmic-ray worker boolops replace_Eq_with_LtE 0 nose -- -v test1.py 25 | --- mutation diff --- 26 | --- a/example_10/boolops1.py 27 | +++ b/example_10/boolops1.py 28 | @@ -1,6 +1,6 @@ 29 | 30 | 31 | def xnor_raise(list_a, list_b): 32 | - if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) > 0) and (len(list_b) > 0))): 33 | + if (((len(list_a) <= 0) and (len(list_b) == 0)) or ((len(list_a) > 0) and (len(list_b) > 0))): 34 | raise Exception('TEST') 35 | 36 | 37 | job ID 6:Outcome.SURVIVED:boolops 38 | command: cosmic-ray worker boolops replace_Eq_with_LtE 1 nose -- -v test1.py 39 | --- mutation diff --- 40 | --- a/example_10/boolops1.py 41 | +++ b/example_10/boolops1.py 42 | @@ -1,6 +1,6 @@ 43 | 44 | 45 | def xnor_raise(list_a, list_b): 46 | - if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) > 0) and (len(list_b) > 0))): 47 | + if (((len(list_a) == 0) and (len(list_b) <= 0)) or ((len(list_a) > 0) and (len(list_b) > 0))): 48 | raise Exception('TEST') 49 | 50 | 51 | job ID 13:Outcome.SURVIVED:boolops 52 | command: cosmic-ray worker boolops replace_Gt_with_NotEq 0 nose -- -v test1.py 53 | --- mutation diff --- 54 | --- a/example_10/boolops1.py 55 | +++ b/example_10/boolops1.py 56 | @@ -1,6 +1,6 @@ 57 | 58 | 59 | def xnor_raise(list_a, list_b): 60 | - if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) > 0) and (len(list_b) > 0))): 61 | + if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) != 0) and (len(list_b) > 0))): 62 | raise Exception('TEST') 63 | 64 | 65 | job ID 14:Outcome.SURVIVED:boolops 66 | command: cosmic-ray worker boolops replace_Gt_with_NotEq 1 nose -- -v test1.py 67 | --- mutation diff --- 68 | --- a/example_10/boolops1.py 69 | +++ b/example_10/boolops1.py 70 | @@ -1,6 +1,6 @@ 71 | 72 | 73 | def xnor_raise(list_a, list_b): 74 | - if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) > 0) and (len(list_b) > 0))): 75 | + if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) > 0) and (len(list_b) != 0))): 76 | raise Exception('TEST') 77 | 78 | 79 | job ID 23:Outcome.SURVIVED:boolops 80 | command: cosmic-ray worker boolops replace_Gt_with_IsNot 0 nose -- -v test1.py 81 | --- mutation diff --- 82 | --- a/example_10/boolops1.py 83 | +++ b/example_10/boolops1.py 84 | @@ -1,6 +1,6 @@ 85 | 86 | 87 | def xnor_raise(list_a, list_b): 88 | - if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) > 0) and (len(list_b) > 0))): 89 | + if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) is not 0) and (len(list_b) > 0))): 90 | raise Exception('TEST') 91 | 92 | 93 | job ID 24:Outcome.SURVIVED:boolops 94 | command: cosmic-ray worker boolops replace_Gt_with_IsNot 1 nose -- -v test1.py 95 | --- mutation diff --- 96 | --- a/example_10/boolops1.py 97 | +++ b/example_10/boolops1.py 98 | @@ -1,6 +1,6 @@ 99 | 100 | 101 | def xnor_raise(list_a, list_b): 102 | - if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) > 0) and (len(list_b) > 0))): 103 | + if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) > 0) and (len(list_b) is not 0))): 104 | raise Exception('TEST') 105 | 106 | 107 | total jobs: 38 108 | complete: 38 (100.00%) 109 | survival rate: 15.79% 110 | 111 | 112 | If we proceed to refactor ``len(list)`` comparisons as shown previously it 113 | is easier to figure out that the boolean function is XNOR (if and only if), 114 | also called logical equality. In ``boolops2.py`` *Cosmic-Ray* doesn't 115 | detect any possible mutations because at the moment of writing it doesn't 116 | support mutating boolean operators. :: 117 | 118 | $ cosmic-ray run --test-runner nose --baseline=10 example.json boolops2.py -- test2.py: 119 | $ cosmic-ray report example.json 120 | total jobs: 0 121 | no jobs completed 122 | 123 | 124 | Another possible refactoring is ``boolops3.py`` where valid parameters are 125 | enumerated before the condition is checked. :: 126 | 127 | $ cosmic-ray run --test-runner nose --baseline=10 example.json boolops3.py -- test3.py: 128 | $ cosmic-ray report example.json 129 | total jobs: 12 130 | complete: 12 (100.00%) 131 | survival rate: 0.00% 132 | 133 | Yet another possible refactoring is ``boolops4.py`` where the condition is 134 | expressed using the built-ins ``any`` and ``all``. Unfortunately *Cosmic-Ray* 135 | doesn't recognize these as possible mutations either. :: 136 | 137 | $ cosmic-ray run --test-runner nose --baseline=10 example.json boolops4.py -- test4.py: 138 | $ cosmic-ray report example.json 139 | total jobs: 0 140 | no jobs completed 141 | 142 | 143 | 144 | Source code 145 | =========== 146 | 147 | .. literalinclude:: boolops1.py 148 | :caption: boolops1.py 149 | :language: python 150 | 151 | .. literalinclude:: test1.py 152 | :caption: test1.py 153 | :language: python 154 | 155 | .. literalinclude:: boolops2.py 156 | :caption: boolops2.py 157 | :language: python 158 | 159 | .. literalinclude:: test2.py 160 | :caption: test2.py 161 | :language: python 162 | 163 | .. literalinclude:: boolops3.py 164 | :caption: boolops3.py 165 | :language: python 166 | 167 | .. literalinclude:: test3.py 168 | :caption: test3.py 169 | :language: python 170 | 171 | .. literalinclude:: boolops4.py 172 | :caption: boolops4.py 173 | :language: python 174 | 175 | .. literalinclude:: test4.py 176 | :caption: test4.py 177 | :language: python 178 | -------------------------------------------------------------------------------- /python/example_10/boolops1.py: -------------------------------------------------------------------------------- 1 | def xnor_raise(list_a, list_b): 2 | if (len(list_a) == 0 and len(list_b) == 0) or (len(list_a) > 0 and (len(list_b) > 0)): 3 | raise Exception('TEST') 4 | -------------------------------------------------------------------------------- /python/example_10/boolops2.py: -------------------------------------------------------------------------------- 1 | def xnor_raise(list_a, list_b): 2 | if (not list_a and not list_b) or (list_a and list_b): 3 | raise Exception('TEST') 4 | -------------------------------------------------------------------------------- /python/example_10/boolops3.py: -------------------------------------------------------------------------------- 1 | def xnor_raise(list_a, list_b): 2 | valid_lists = 0 3 | if list_a: 4 | valid_lists += 1 5 | 6 | if list_b: 7 | valid_lists += 1 8 | 9 | if valid_lists != 1: 10 | raise Exception('TEST') 11 | -------------------------------------------------------------------------------- /python/example_10/boolops4.py: -------------------------------------------------------------------------------- 1 | def xnor_raise(list_a, list_b): 2 | if not any([list_a, list_b]) or all([list_a, list_b]): 3 | raise Exception('TEST') 4 | -------------------------------------------------------------------------------- /python/example_10/test1.py: -------------------------------------------------------------------------------- 1 | import boolops1 as boolops 2 | import unittest 3 | 4 | class TestBoolOps(unittest.TestCase): 5 | def test_xnor_raise_a_empty_b_empty(self): 6 | with self.assertRaises(Exception): 7 | boolops.xnor_raise([], []) 8 | 9 | def test_xnor_raise_a_not_empty_b_not_empty(self): 10 | with self.assertRaises(Exception): 11 | boolops.xnor_raise([1], [2]) 12 | 13 | def test_xnor_raise_a_empty_b_not_empty(self): 14 | # doesn'r raise exception 15 | boolops.xnor_raise([], [2]) 16 | 17 | def test_xnor_raise_a_not_empty_b_empty(self): 18 | # doesn't raise exception 19 | boolops.xnor_raise([1], []) 20 | 21 | 22 | if __name__ == "__main__": 23 | unittest.main() 24 | -------------------------------------------------------------------------------- /python/example_10/test2.py: -------------------------------------------------------------------------------- 1 | import boolops2 as boolops 2 | import unittest 3 | 4 | class TestBoolOps(unittest.TestCase): 5 | def test_xnor_raise_a_empty_b_empty(self): 6 | with self.assertRaises(Exception): 7 | boolops.xnor_raise([], []) 8 | 9 | def test_xnor_raise_a_not_empty_b_not_empty(self): 10 | with self.assertRaises(Exception): 11 | boolops.xnor_raise([1], [2]) 12 | 13 | def test_xnor_raise_a_empty_b_not_empty(self): 14 | # doesn'r raise exception 15 | boolops.xnor_raise([], [2]) 16 | 17 | def test_xnor_raise_a_not_empty_b_empty(self): 18 | # doesn't raise exception 19 | boolops.xnor_raise([1], []) 20 | 21 | 22 | if __name__ == "__main__": 23 | unittest.main() 24 | -------------------------------------------------------------------------------- /python/example_10/test3.py: -------------------------------------------------------------------------------- 1 | import boolops3 as boolops 2 | import unittest 3 | 4 | class TestBoolOps(unittest.TestCase): 5 | def test_xnor_raise_a_empty_b_empty(self): 6 | with self.assertRaises(Exception): 7 | boolops.xnor_raise([], []) 8 | 9 | def test_xnor_raise_a_not_empty_b_not_empty(self): 10 | with self.assertRaises(Exception): 11 | boolops.xnor_raise([1], [2]) 12 | 13 | def test_xnor_raise_a_empty_b_not_empty(self): 14 | # doesn'r raise exception 15 | boolops.xnor_raise([], [2]) 16 | 17 | def test_xnor_raise_a_not_empty_b_empty(self): 18 | # doesn't raise exception 19 | boolops.xnor_raise([1], []) 20 | 21 | 22 | if __name__ == "__main__": 23 | unittest.main() 24 | -------------------------------------------------------------------------------- /python/example_10/test4.py: -------------------------------------------------------------------------------- 1 | import boolops4 as boolops 2 | import unittest 3 | 4 | class TestBoolOps(unittest.TestCase): 5 | def test_xnor_raise_a_empty_b_empty(self): 6 | with self.assertRaises(Exception): 7 | boolops.xnor_raise([], []) 8 | 9 | def test_xnor_raise_a_not_empty_b_not_empty(self): 10 | with self.assertRaises(Exception): 11 | boolops.xnor_raise([1], [2]) 12 | 13 | def test_xnor_raise_a_empty_b_not_empty(self): 14 | # doesn'r raise exception 15 | boolops.xnor_raise([], [2]) 16 | 17 | def test_xnor_raise_a_not_empty_b_empty(self): 18 | # doesn't raise exception 19 | boolops.xnor_raise([1], []) 20 | 21 | 22 | if __name__ == "__main__": 23 | unittest.main() 24 | -------------------------------------------------------------------------------- /python/example_11/README.rst: -------------------------------------------------------------------------------- 1 | Refactor if X is not None 2 | ************************* 3 | 4 | Objects of any type can be compared for not being ``None`` value however that 5 | leads to surviving mutations. ``None`` is a special value but in most practical 6 | cases it is equivalent to zero/empty value for the type. 7 | 8 | 9 | Example 10 | ======= 11 | 12 | :: 13 | 14 | $ pip install nose 15 | $ pip install https://github.com/sixty-north/cosmic-ray/zipball/master 16 | 17 | In ``example1.py`` we're accepting ``None`` as default parameter value and 18 | correctly identified 3 test cases - when password is a string, when it is 19 | empty string and when it is ``None``. There is one surviving mutant. :: 20 | 21 | $ cosmic-ray run --test-runner nose --baseline=10 example.json example1.py -- test1.py: 22 | $ cosmic-ray report example.json 23 | 24 | job ID 5:Outcome.SURVIVED:example1 25 | command: cosmic-ray worker example1 replace_IsNot_with_NotEq 0 nose -- -v test1.py 26 | --- mutation diff --- 27 | --- a/example_11/example1.py 28 | +++ b/example_11/example1.py 29 | @@ -1,7 +1,7 @@ 30 | 31 | 32 | def reverse_password(password=None): 33 | - if (password is not None): 34 | + if (password != None): 35 | return password[::(- 1)] 36 | else: 37 | return '' 38 | 39 | total jobs: 11 40 | complete: 11 (100.00%) 41 | survival rate: 9.09% 42 | 43 | 44 | If we decide to remove ``None`` and accept an empty string instead then 45 | ``example2.py`` is reduced to one line and there are no surviving 46 | mutations. Also the number of mutations is significantly reduced. :: 47 | 48 | $ cosmic-ray run --test-runner nose --baseline=10 example.json example2.py -- -v test2.py 49 | $ cosmic-ray report example.json 50 | job ID 1:Outcome.KILLED:example2 51 | command: cosmic-ray worker example2 number_replacer 0 nose -- -v test2.py 52 | 53 | job ID 2:Outcome.KILLED:example2 54 | command: cosmic-ray worker example2 arithmetic_operator_deletion 0 nose -- -v test2.py 55 | 56 | total jobs: 2 57 | complete: 2 (100.00%) 58 | survival rate: 0.00% 59 | 60 | Source code 61 | =========== 62 | 63 | .. literalinclude:: example1.py 64 | :caption: example1.py 65 | :language: python 66 | 67 | .. literalinclude:: test1.py 68 | :caption: test1.py 69 | :language: python 70 | 71 | .. literalinclude:: example2.py 72 | :caption: example2.py 73 | :language: python 74 | 75 | .. literalinclude:: test2.py 76 | :caption: test2.py 77 | :language: python 78 | -------------------------------------------------------------------------------- /python/example_11/example1.py: -------------------------------------------------------------------------------- 1 | def reverse_password(password = None): 2 | if password is not None: 3 | return password[::-1] 4 | else: 5 | return '' 6 | -------------------------------------------------------------------------------- /python/example_11/example2.py: -------------------------------------------------------------------------------- 1 | def reverse_password(password = ''): 2 | return password[::-1] 3 | -------------------------------------------------------------------------------- /python/example_11/test1.py: -------------------------------------------------------------------------------- 1 | import example1 as example 2 | import unittest 3 | 4 | class TestReversePassword(unittest.TestCase): 5 | def test_non_empty_password(self): 6 | revpass = example.reverse_password('secret') 7 | self.assertEqual(revpass, 'terces') 8 | 9 | def test_empty_password(self): 10 | revpass = example.reverse_password('') 11 | self.assertEqual(revpass, '') 12 | 13 | def test_no_password_provided(self): 14 | revpass = example.reverse_password() 15 | self.assertEqual(revpass, '') 16 | 17 | if __name__ == "__main__": 18 | unittest.main() 19 | -------------------------------------------------------------------------------- /python/example_11/test2.py: -------------------------------------------------------------------------------- 1 | import example2 as example 2 | import unittest 3 | 4 | class TestReversePassword(unittest.TestCase): 5 | def test_non_empty_password(self): 6 | revpass = example.reverse_password('secret') 7 | self.assertEqual(revpass, 'terces') 8 | 9 | def test_empty_password(self): 10 | revpass = example.reverse_password('') 11 | self.assertEqual(revpass, '') 12 | 13 | def test_no_password_provided(self): 14 | revpass = example.reverse_password() 15 | self.assertEqual(revpass, '') 16 | 17 | if __name__ == "__main__": 18 | unittest.main() 19 | -------------------------------------------------------------------------------- /python/example_12/README.rst: -------------------------------------------------------------------------------- 1 | Testing for 0 <= X <= 100 2 | ************************* 3 | 4 | It is common for programs to expect numerical variables to accept values 5 | matching a certain range. A good example is a ``percent`` variable with 6 | allowed values from 0 to 100 inclusive. 7 | 8 | Test for such code will often validate correct operation with 9 | a value inside the range and expect an error for a value outside the range. 10 | As a bonus the test may be expecting errors when testing with values on 11 | both sides of the range! 12 | 13 | 14 | Reproducer 15 | ========== 16 | 17 | :: 18 | 19 | $ pip install https://github.com/sixty-north/cosmic-ray/zipball/master 20 | 21 | $ cosmic-ray run --test-runner nose --baseline=10 example.json percent.py -- test1.py 22 | $ cosmic-ray report example.json 23 | 24 | job ID 6:Outcome.SURVIVED:percent 25 | command: cosmic-ray worker percent replace_Lt_with_LtE 0 nose -- -v test1.py 26 | --- mutation diff --- 27 | --- a/example_12/percent.py 28 | +++ b/example_12/percent.py 29 | @@ -1,7 +1,7 @@ 30 | 31 | 32 | def validate_percent(percent): 33 | - if ((percent < 0) or (percent > 100)): 34 | + if ((percent <= 0) or (percent > 100)): 35 | raise Exception('Percent must be between 0 and 100') 36 | return percent 37 | 38 | 39 | job ID 15:Outcome.SURVIVED:percent 40 | command: cosmic-ray worker percent replace_Gt_with_GtE 0 nose -- -v test1.py 41 | --- mutation diff --- 42 | --- a/example_12/percent.py 43 | +++ b/example_12/percent.py 44 | @@ -1,7 +1,7 @@ 45 | 46 | 47 | def validate_percent(percent): 48 | - if ((percent < 0) or (percent > 100)): 49 | + if ((percent < 0) or (percent >= 100)): 50 | raise Exception('Percent must be between 0 and 100') 51 | return percent 52 | 53 | 54 | job ID 16:Outcome.SURVIVED:percent 55 | command: cosmic-ray worker percent number_replacer 0 nose -- -v test1.py 56 | --- mutation diff --- 57 | --- a/example_12/percent.py 58 | +++ b/example_12/percent.py 59 | @@ -1,7 +1,7 @@ 60 | 61 | 62 | def validate_percent(percent): 63 | - if ((percent < 0) or (percent > 100)): 64 | + if ((percent < 1) or (percent > 100)): 65 | raise Exception('Percent must be between 0 and 100') 66 | return percent 67 | 68 | 69 | job ID 17:Outcome.SURVIVED:percent 70 | command: cosmic-ray worker percent number_replacer 1 nose -- -v test1.py 71 | --- mutation diff --- 72 | --- a/example_12/percent.py 73 | +++ b/example_12/percent.py 74 | @@ -1,7 +1,7 @@ 75 | 76 | 77 | def validate_percent(percent): 78 | - if ((percent < 0) or (percent > 100)): 79 | + if ((percent < 0) or (percent > 101)): 80 | raise Exception('Percent must be between 0 and 100') 81 | return percent 82 | 83 | 84 | total jobs: 20 85 | complete: 20 (100.00%) 86 | survival rate: 20.00% 87 | 88 | 89 | To fully test this code you have to test with both border values and 90 | with the next possible values, which are outside of the range. Testing with 91 | a value that falls within the range, but isn't a border one doesn't affect 92 | mutation testing. :: 93 | 94 | $ cosmic-ray run --test-runner nose --baseline=10 example.json percent.py -- -v test2.py 95 | $ cosmic-ray report example.json 96 | 97 | total jobs: 20 98 | complete: 20 (100.00%) 99 | survival rate: 0.00% 100 | 101 | .. note:: 102 | 103 | This is similar to :doc:`../example_04/README` 104 | 105 | Source code 106 | =========== 107 | 108 | .. literalinclude:: percent.py 109 | :caption: percent.py 110 | :language: python 111 | 112 | .. literalinclude:: test1.py 113 | :caption: test1.py 114 | :language: python 115 | 116 | .. literalinclude:: test2.py 117 | :caption: test2.py 118 | :language: python 119 | -------------------------------------------------------------------------------- /python/example_12/percent.py: -------------------------------------------------------------------------------- 1 | def validate_percent(percent): 2 | if percent < 0 or percent > 100: 3 | raise Exception("Percent must be between 0 and 100") 4 | 5 | return percent 6 | -------------------------------------------------------------------------------- /python/example_12/test1.py: -------------------------------------------------------------------------------- 1 | import percent 2 | import unittest 3 | 4 | class TestPercent(unittest.TestCase): 5 | def test_value_in_range(self): 6 | result = percent.validate_percent(50) 7 | self.assertEqual(result, 50) 8 | 9 | def test_value_not_in_range(self): 10 | with self.assertRaises(Exception): 11 | percent.validate_percent(500) 12 | 13 | with self.assertRaises(Exception): 14 | percent.validate_percent(-10) 15 | 16 | if __name__ == "__main__": 17 | unittest.main() 18 | -------------------------------------------------------------------------------- /python/example_12/test2.py: -------------------------------------------------------------------------------- 1 | import percent 2 | import unittest 3 | 4 | class TestPercent(unittest.TestCase): 5 | def test_lower_boundary(self): 6 | result = percent.validate_percent(0) 7 | self.assertEqual(result, 0) 8 | 9 | def test_upper_boundary(self): 10 | result = percent.validate_percent(100) 11 | self.assertEqual(result, 100) 12 | 13 | def test_below_lower_boundary(self): 14 | with self.assertRaises(Exception): 15 | percent.validate_percent(-1) 16 | 17 | def test_above_upper_boundary(self): 18 | with self.assertRaises(Exception): 19 | percent.validate_percent(101) 20 | 21 | if __name__ == "__main__": 22 | unittest.main() 23 | -------------------------------------------------------------------------------- /rtd_requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx==1.5.2 2 | # because of the comment block 3 | https://github.com/MrSenko/sphinx_rtd_theme/zipball/b882057#egg=sphinx-rtd-theme==0.1.10a 4 | --------------------------------------------------------------------------------