⇤ ← Revision 1 as of 2004-09-06 13:16:00
Size: 11594
Comment:
|
Size: 15593
Comment:
|
Deletions are marked like this. | Additions are marked like this. |
Line 142: | Line 142: |
* 单元测试是一个好东西。好的开发者一发现单元测试失败就应该心怀恐惧。他们应该把所有的更改都去掉,直到代码能够顺利通过测试。 | * 单元测试是一个好东西。好的开发者一发现单元测试失败就应该心怀恐惧。他们应该把所有的更改都去掉,直到代码能够顺利通过测试。 |
Line 144: | Line 144: |
* Unit tests are a good thing. Good developers recoil in horror when they see a failing unit test. They should drop everything until the test has been fixed. | * Unit tests are a good thing. Good developers recoil in horror when they see a failing unit test. They should drop everything until the test has been fixed. |
Line 146: | Line 146: |
* 好的开发者应该先写单元测试。测试代码完成以后,他们才开始编写具体的实现代码,并使之能够通过单元测试。然后当前的开发就可以告一段落了。 | * 好的开发者应该先写单元测试。测试代码完成以后,他们才开始编写具体的实现代码,并使之能够通过单元测试。然后当前的开发就可以告一段落了。 |
Line 148: | Line 148: |
* Good developers write the unit tests first. Once tests are done, they write implementation code until the unit tests pass. Then they stop. | * Good developers write the unit tests first. Once tests are done, they write implementation code until the unit tests pass. Then they stop. 这两个目标有的时候是彼此矛盾的。在编写任何实现代码之前,就编写单元测试,这注定一定会失败的。我们总想让开发者快点儿提交代码,以提高代码的可靠性并改善研究同一问题的多个开发人员之间的协作程度。当一个开发人员为代码添加新特性的时候,其它(其工作不涉及到新特性)的开发者本应当不必关注新代码所引入的错误。而不是在一个模块还没有完成,且未开始其单元测试的时候,就高喊“狼来了”,同时不让我们头脑中业已形成的由“狼来了”所导致的测试失败恐惧综合症有丝毫的淡化。如果真这么干,其结果要么是教会模块作者放弃该模块,或者是逼迫其在实现所有的功能后再提交他们的单元测试代码,要么是教会其它的开发者忽略测试程序所检查到的失败信息。这两种方式都不好。 These two goals will sometimes conflict. The unit tests that are written first, before any implementation has been done, are certain to fail. We want developers to commit their code frequently, for reliability and to improve coordination between multiple people working on the same problem together. While the code is being written, other developers (those not involved in the new feature) should not have to pay attention to failures in the new code. We should not dilute our well-indoctrinated Failing Test Horror Syndrome by crying wolf when an incomplete module has not yet started passing its unit tests. To do so would either teach the module author to put off writing or committing their unit tests until after all the functionality is working, or it would teach the other developers to ignore failing test cases. Both are bad things. 为了解决这个问题,我们使用了todo属性。当一个开发者第一次开始为某个尚未实现的新功能编写单元测试的时候,他们可以将该测试方法的todo属性设置为“期望失败”。该方法的运行仍然会产生失败,但是其产生的失败并不会被作为普通的失败加以记录,而是将这种“被期望的失败”放到一个单独的“期望失败”的统计类别中。开发者应该学会将“期望失败”与“实际失败”这两者区别对待,“期望失败”的优先级要比“实际失败”的优先级低一些,应该先解决那些导致“实际失败”的问题。 .todo is intended to solve this problem. When a developer first starts writing the unit tests for functionality that has not yet been implemented, they can set the .todo attribute on the test methods that are expected to fail. These methods will still be run, but their failure will not be counted the same as normal failures: they will go into an expected failures category. Developers should learn to treat this category as a second-priority queue, behind actual test failures. 当开发者实现了某个特性之后,对应的测试程序终于能够正常运行了。但是由于已经存在的todo属性将这些测试都被标记为“期望失败”,能够成功通过的测试程序反而会把成功信息放到一个“不被期望的成功”的类别中。此时,开发者就应该将测试程序中的todo属性去掉了。从这一点开始,我们才开始正常的测试,而任何一点儿失败都将会引发整个开发团队的立即反映。 As the developer implements the feature, the tests will eventually start passing. This is surprising: after all those tests are marked as being expected to fail. The .todo tests which nevertheless pass are put into a unexpected success category. The developer should remove the .todo tag from these tests. At that point, they become normal tests, and their failure is once again cause for immediate action by the entire development team. 整个测试的生命周期可以描述为: The life cycle of a test is thus: |
Twisted中的单元测试 (Unit Tests in Twisted)
- 每个单元测试用来测试一个功能点。单元测试完全自动进行,执行迅速。对于整个系统的单元测试被整合到一个测试集合中,整个测试集可以批量执行。单元测试的结果是十分简单:通过或是不通过。所有这些意味着你可以方便的在任何时候测试你系统,并且能够迅速看到测试结果是通过还是没通过。 Each unit test tests one bit of functionality in the software. Unit tests are entirely automated and complete quickly. Unit tests for the entire system are gathered into one test suite, and may all be run in a single batch. The result of a unit test is simple: either it passes, or it doesn't. All this means you can test the entire system at any time without inconvenience, and quickly see what passes and what fails.
Twisted中的单元测试哲学(Unit Tests in the Twisted Philosophy)
- Twisted开发小组坚持实践极限编程的思想,而使用单元测试的正是极限编程的基础。单元测试是一个不断增长你信心的工具。假设你修改了一个算法,你想知道你是否做错了什么东西吗?请运行单元测试。如果某个测试失败,你能够很清楚的知道哪儿出了问题,因为每个测试只覆盖了很少量的代码,而你清楚的知道你对代码做过怎样的修改。如果所有的测试都通过了,那么没问题,你可以继续了,你再也不需要担心你的修改可能会无意中影响了其它人的程序了。 The Twisted development team adheres to the practice of Extreme Programming (XP), and the usage of unit tests is a cornerstone XP practice. Unit tests are a tool to give you increased confidence. You changed an algorithm -- did you break something? Run the unit tests. If a test fails, you know where to look, because each test covers only a small amount of code, and you know it has something to do with the changes you just made. If all the tests pass, you're good to go, and you don't need to second-guess yourself or worry that you just accidently broke someone else's program.
什么是应该测试的,什么是不应该测试的(What to Test, What Not to Test)
- 你不必为每个方法都编写测试程序,只需为那些可能会发生变化的方法编写测试程序就够了。
- --Kent Beck,极限编程解析,58页。
- -- Kent Beck, Extreme Programming Explained, p. 58.
运行测试程序(Running the Tests)
- == 怎样运行(How) ==
$ Twisted/admin/runtests
在你的Emacs初始化文件中加上下面的代码,这会极大的方便你在Emacs中执行单元测试。 You'll find that having something like this in your emacs init files is quite handy:(defun runtests () (interactive) (compile "python /somepath/Twisted/admin/runtests")) (global-set-key [(alt t)] 'runtests)
- 永远要在提交代码前确定你的代码已经通过了所有的测试。如果其他人开始开发,却发现刚刚checkout到的代码无法通过测试,他们可能会非常生气并把你揪出来。 Always always always be sure all the tests pass before committing any code. If someone else checks out code at the start of a development session and finds failing tests, they will not be happy and may decide to hunt you down. 由于开发队伍的成员分布在世界各地,那个能够帮助你调好代码的人可能并不你的隔壁。你可能想把你的工作进展与整个网络上的同伴分享,但是你又必须保证CVS的主版本树永远保持一个正确的状态。此时,你需要使用分支技术,当你解决了你的问题,并通过了所有的测试之后,你就可以把你的修改合并到主版本中了。 Since this is a geographically dispersed team, the person who can help you get your code working probably isn't in the room with you. You may want to share your work in progress over the network, but you want to leave the main CVS tree in good working order. So use a branch, and merge your changes back in only after your problem is solved and all the unit tests pass again.
加入一个测试程序(Adding a Test)
- 在没有向Twisted中加入相应的测试程序之前,请不要向Twisted中加入新的模块。否则,你可能会在毫不知情的情况下破坏了你的模块。当你知道出了问题以后(可能是马上就知道,也可能是在一个release版本之后),由于已经做过太多的修改,你根本无法确定到底是那里出了问题。 Please don't add new modules to Twisted without adding tests for them too. Otherwise we could change something which breaks your module and not find out until later, making it hard to know exactly what the change that broke it was, or until after a release, and nobody wants broken code in a release.
所有的测试程序都放在Twisted/twisted/test/目录下,测试程序的名称由“test_”前缀加上对应模块或是包的名称组成。在下面的链接这小节中可以找到很多关于使用PyUnit框架编写单元测试的文档。
Tests go in Twisted/twisted/test/, and are named test_foo.py, where foo is the name of the module or package being tested. Extensive documentation on using the PyUnit framework for writing unit tests can be found in the links section below.
一个与标准PyUnit文档像背离的事实:为了确保测试结果的任何变化都是由代码或是测试环境所造成的,而与测试的过程无关,Twisted自己移植了一个兼容的测试框架。这就意味着,无论何时想要导入单元测试模块,你都应该使用 from twisted.trial import unittest 来代替标准的 import unittest 。
One deviation from the standard PyUnit documentation: To ensure that any variations in test results are due to variations in the code or environment and not the test process itself, Twisted ships with its own, compatible, testing framework. That just means that when you import the unittest module, you will from twisted.trial import unittest instead of the standard import unittest. 只要遵循模块文件的命名与放置习惯,runtests就能聪明的找到你所加入的新的测试程序。 As long as you have followed the module naming and placement conventions, runtests will be smart enough to pick up any new tests you write.
忽略测试,TODO项目(Skipping tests, TODO items)
Twisted单元测试框架对PyUnit做了一些小的扩展,用来鼓励开发者添加新的测试程序。测试程序在测试一些可选功能时常见的情况是:可能需要依赖于某些特定的外部库文件是否可用,可能只能工作在特定的操作系统上。最重要的问题是,没有人把这些限制当作一个bug。 Trial, the Twisted unit test framework, has some extensions which are designed to encourage developers to add new tests. One common situation is that a test exercises some optional functionality: maybe it depends upon certain external libraries being available, maybe it only works on certain operating systems. The important common factor is that nobody considers these limitations to be a bug.
为了使测试尽可能的简便,在特定的环境下某些测试可能会被忽略。单个的测试用例可以抛出SkipTest异常来指出这些测试应该被忽略,于是这个测试程序中其余部分就不再被执行。在测试结果summary部分,除了会统计通过或是不通过的测试用例的个数以外,还会对测试中被忽略部分做专门的统计。抛出SkipTest异常这种方式也可以用在条件语句中,用来判断是否满足程序执行的必要条件。
To make it easy to test as much as possible, some tests may be skipped in certain situations. Individual test cases can raise the SkipTest exception to indicate that they should be skipped, and the remainder of the test is not run. In the summary (the very last thing printed, at the bottom of the test output) the test is counted as a skip instead of a success or fail. This should be used inside a conditional which looks for the necessary prerequisites:
def testSSHClient(self): if not ssh_path: raise unittest.SkipTest, "cannot find ssh, nothing to test" foo() # do actual test after the SkipTest
你也可以在一个方法上设定skip属性,这个属性描述了测试为什么被忽略。使用这种机制可以十分方便的临时取消一个测试用例,当然也根据情况手动修改一个类的属性。 You can also set the .skip attribute on the method, with a string to indicate why the test is being skipped. This is convenient for temporarily turning off a test case, but it can also be set conditionally (by manipulating the class attributes after they've been defined):def testThing(self): dotest() testThing.skip = "disabled locally" class MyTestCase(unittest.TestCase): def testOne(self): ... def testThing(self): dotest() if not haveThing: MyTestCase.testThing.im_func.skip = "cannot test without Thing" # but testOne() will still run
最后,你也可以通过设置对应类的skip属性来一次性取消整个测试用例。如果你根据代码的功能依赖关系定义测试程序,那么这种方式可以很方便的取消某种现在还不支持的功能的所需要的单元测试。Finally, you can turn off an entire TestCase at once by setting the .skip attribute on the class. If you organize your tests by the functionality they depend upon, this is a convenient way to disable just the tests which cannot be run.
class SSLTestCase(unittest.TestCase): ... class TCPTestCase(unittest.TestCase): ... if not haveSSL: SSLTestCase.skip = "cannot test without SSL support" # but TCPTestCase will still run
== todo属性与测试新功能(.todo and Testing New Functionality) ==- 产生于极限编程开发过程中的两个好的实践方法有时候看上去好像相互矛盾: Two good practices which arise from the XP development process are sometimes at odds with each other:
- 单元测试是一个好东西。好的开发者一发现单元测试失败就应该心怀恐惧。他们应该把所有的更改都去掉,直到代码能够顺利通过测试。
- Unit tests are a good thing. Good developers recoil in horror when they see a failing unit test. They should drop everything until the test has been fixed.
- 好的开发者应该先写单元测试。测试代码完成以后,他们才开始编写具体的实现代码,并使之能够通过单元测试。然后当前的开发就可以告一段落了。
- Good developers write the unit tests first. Once tests are done, they write implementation code until the unit tests pass. Then they stop. 这两个目标有的时候是彼此矛盾的。在编写任何实现代码之前,就编写单元测试,这注定一定会失败的。我们总想让开发者快点儿提交代码,以提高代码的可靠性并改善研究同一问题的多个开发人员之间的协作程度。当一个开发人员为代码添加新特性的时候,其它(其工作不涉及到新特性)的开发者本应当不必关注新代码所引入的错误。而不是在一个模块还没有完成,且未开始其单元测试的时候,就高喊“狼来了”,同时不让我们头脑中业已形成的由“狼来了”所导致的测试失败恐惧综合症有丝毫的淡化。如果真这么干,其结果要么是教会模块作者放弃该模块,或者是逼迫其在实现所有的功能后再提交他们的单元测试代码,要么是教会其它的开发者忽略测试程序所检查到的失败信息。这两种方式都不好。 These two goals will sometimes conflict. The unit tests that are written first, before any implementation has been done, are certain to fail. We want developers to commit their code frequently, for reliability and to improve coordination between multiple people working on the same problem together. While the code is being written, other developers (those not involved in the new feature) should not have to pay attention to failures in the new code. We should not dilute our well-indoctrinated Failing Test Horror Syndrome by crying wolf when an incomplete module has not yet started passing its unit tests. To do so would either teach the module author to put off writing or committing their unit tests until after all the functionality is working, or it would teach the other developers to ignore failing test cases. Both are bad things. 为了解决这个问题,我们使用了todo属性。当一个开发者第一次开始为某个尚未实现的新功能编写单元测试的时候,他们可以将该测试方法的todo属性设置为“期望失败”。该方法的运行仍然会产生失败,但是其产生的失败并不会被作为普通的失败加以记录,而是将这种“被期望的失败”放到一个单独的“期望失败”的统计类别中。开发者应该学会将“期望失败”与“实际失败”这两者区别对待,“期望失败”的优先级要比“实际失败”的优先级低一些,应该先解决那些导致“实际失败”的问题。
- todo is intended to solve this problem. When a developer first starts writing the unit tests for functionality that has not yet been implemented, they can set the .todo attribute on the test methods that are expected to fail. These methods will still be run, but their failure will not be counted the same as normal failures: they will go into an expected failures category. Developers should learn to treat this category as a second-priority queue, behind actual test failures. 当开发者实现了某个特性之后,对应的测试程序终于能够正常运行了。但是由于已经存在的todo属性将这些测试都被标记为“期望失败”,能够成功通过的测试程序反而会把成功信息放到一个“不被期望的成功”的类别中。此时,开发者就应该将测试程序中的todo属性去掉了。从这一点开始,我们才开始正常的测试,而任何一点儿失败都将会引发整个开发团队的立即反映。 As the developer implements the feature, the tests will eventually start passing. This is surprising: after all those tests are marked as being expected to fail. The .todo tests which nevertheless pass are put into a unexpected success category. The developer should remove the .todo tag from these tests. At that point, they become normal tests, and their failure is once again cause for immediate action by the entire development team. 整个测试的生命周期可以描述为: The life cycle of a test is thus: