Changeset 835

Show
Ignore:
Timestamp:
Wed Mar 8 10:41:07 2006
Author:
osmond
Message:

译毕,待校

Files:

Legend:

Unmodified
Added
Removed
Modified
  • zh-translations/branches/diveintopython-zh-5.4/zh-cn/xml/roman.xml

    r201 r835  
    2 2 <chapter id="roman">  
    3 3 <?dbhtml filename="unit_testing/index.html"?>  
    4   <title>Unit Testing</title>  
    5   <titleabbrev id="roman.numberonly">Chapter 13</titleabbrev>  
      4 <title>单元测试</title>  
      5 <titleabbrev id="roman.numberonly">第 13 章</titleabbrev>  
    6 6 <section id="roman.intro">  
    7   <title>Introduction to Roman numerals</title>  
      7 <title>罗马数字程序介绍 II</title>  
    7 7 <abstract>  
    8 8 <title/>  
    9   <para>In previous chapters, you <quote>dived in</quote> by immediately looking at code and trying to understand it as quickly as possible.  Now that you have some &python; under your belt, you're going to step back and look at the steps that happen <emphasis>before</emphasis> the code gets written.</para>  
      9 <para>在前面的章节中,你迅速<quote>深入</quote>并试图以最快的速度理解了这个程序。 现在你以对 &python; 更多的了解将回顾这个程序并从其代码开发<emphasis>之前</emphasis>入手。</para>  
    9 9 </abstract>  
    10   <para>In the next few chapters, you're going to write, debug, and optimize a set of utility functions to convert to and from Roman numerals.  You saw the mechanics of constructing and validating Roman numerals in <xref linkend="re.roman"/>, but now let's step back and consider what it would take to expand that into a two-way utility.</para>  
      10 <para>在接下来的几章中,你将会编写,纠错以及优化一系列使用函数来进行罗马数字和阿拉伯数字之间的转换。你已从<xref linkend="re.roman"/>中获知构造和验证罗马数字的机制,现在我们要做的事是退后一步去思考如何将这些机制扩展到一个双向转换的工具。</para>  
    10 10  
    11   <para><link linkend="re.roman">The rules for Roman numerals</link> lead to a number of interesting observations:</para>  
      11 <para><link linkend="re.roman">罗马数字的规则</link>有如下一些有趣的特点:</para>  
    11 11 <orderedlist>  
    12   <listitem><para>There is only one correct way to represent a particular number as Roman numerals.</para></listitem>  
    13   <listitem><para>The converse is also true: if a string of characters is a valid Roman numeral, it represents only one number (&ie; it can only be read one way).</para></listitem>  
    14   <listitem><para>There is a limited range of numbers that can be expressed as Roman numerals, specifically <literal>1</literal> through <literal>3999</literal>.  (The Romans did have several ways of expressing larger numbers, for instance by having a bar over a numeral to represent that its normal value should be multiplied by <literal>1000</literal>, but you're not going to deal with that.  For the purposes of this chapter, let's stipulate that Roman numerals go from <literal>1</literal> to <literal>3999</literal>.)</para></listitem>  
    15   <listitem><para>There is no way to represent &zero; in Roman numerals.  (Amazingly, the ancient Romans had no concept of &zero; as a number.  Numbers were for counting things you had; how can you count what you don't have?)</para></listitem>  
    16   <listitem><para>There is no way to represent negative numbers in Roman numerals.</para></listitem>  
    17   <listitem><para>There is no way to represent fractions or non-integer numbers in Roman numerals.</para></listitem>  
      12 <listitem><para>一个特定数字以罗马数字表示时只有单一方式。</para></listitem>  
      13 <listitem><para>反之亦然:一个有效的罗马数字表示的数也只对应一个阿拉伯数字表示。(也就是说转换成阿拉伯数字表示只有一种方法).</para></listitem>  
      14 <listitem><para>我们研究的是 <literal>1</literal> 和 <literal>3999</literal> 之间的数字的罗马数字表示。(罗马数字有很多方法用以记录更大的数,例如在数字上加线表示<literal>1000</literal>倍的数,但你不必去理会这些。就本章而言,我们姑且把罗马数字限定在 <literal>1</literal> 到 <literal>3999</literal> 之间)。</para></listitem>  
      15 <listitem><para>罗马数字无法表示 &zero; (令人诧异,古罗马竟然没有 &zero;  这个数字的概念。数字是为数数服务的,没有怎么数呢?)</para></listitem>  
      16 <listitem><para>罗马数字不能表示负数。</para></listitem>  
      17 <listitem><para>罗马数字无法表示分数和非整数。</para></listitem>  
    18 18 </orderedlist>  
    19   <para>Given all of this, what would you expect out of a set of functions to convert to and from Roman numerals?</para>  
      19 <para>基于如上所述,你将如何构造罗马数字转换函数呢?</para>  
    19 19 <orderedlist id="roman.requirements">  
    20   <title>&roman_filename; requirements</title>  
    21   <listitem><para>&toroman; should return the Roman numeral representation for all integers &one; to <literal>3999</literal>.</para></listitem>  
    22   <listitem><para>&toroman; should fail when given an integer outside the range &one; to <literal>3999</literal>.</para></listitem>  
    23   <listitem><para>&toroman; should fail when given a non-integer number.</para></listitem>  
    24   <listitem><para>&fromroman; should take a valid Roman numeral and return the number that it represents.</para></listitem>  
    25   <listitem><para>&fromroman; should fail when given an invalid Roman numeral.</para></listitem>  
    26   <listitem><para>If you take a number, convert it to Roman numerals, then convert that back to a number, you should end up with the number you started with.  So <literal>fromRoman(toRoman(n)) == n</literal> for all <varname>n</varname> in <literal>1..3999</literal>.</para></listitem>  
    27   <listitem><para>&toroman; should always return a Roman numeral using uppercase letters.</para></listitem>  
    28   <listitem><para>&fromroman; should only accept uppercase Roman numerals (&ie; it should fail when given lowercase input).</para></listitem>  
      20 <title>&roman_filename; 功能需求</title>  
      21 <listitem><para>&toroman; 应该返回所有 &one; 到 <literal>3999</literal> 的罗马数字表示。</para></listitem>  
      22 <listitem><para>&toroman; 在遇到 &one; 到  <literal>3999</literal> 之外的数字时应该失败。</para></listitem>  
      23 <listitem><para>&toroman; 在遇到非整数时应该失败。</para></listitem>  
      24 <listitem><para>&fromroman; 应该能将给定的合法罗马数字表示转换为阿拉伯数字表示。</para></listitem>  
      25 <listitem><para>&fromroman; 在遇到非法罗马数字表示时应该失败。</para></listitem>  
      26 <listitem><para>将一个数转换为罗马数字表示,再转换回阿拉报数字表示后应该和最初的数相同。因此,<literal>fromRoman(toRoman(n)) == n </literal>对于 <literal>1..3999</literal> 之间所有 <varname>n</varname> 都适用。</para></listitem>  
      27 <listitem><para>&toroman; 返回的罗马数字应该适用大写字母。</para></listitem>  
      28 <listitem><para>&fromroman; 应该只接受大写罗马数字(也就是说给定小写字母进行转换时应该失败)。</para></listitem>  
    29 29 </orderedlist>  
    30 30 <itemizedlist role="furtherreading">  
    31   <title>Further reading</title>  
    32   <listitem><para><ulink url="&url_romannumerals;">This site</ulink> has more on Roman numerals, including a fascinating <ulink url="&url_romanhistory;">history</ulink> of how Romans and other civilizations really used them (short answer: haphazardly and inconsistently).</para></listitem>  
      31 <title>进一步阅读</title>  
      32 <listitem><para><ulink url="&url_romannumerals;">这个站点</ulink> 有关于罗马数字更多的内容,包括罗马人如何使用罗马数字的迷人 <ulink url="&url_romanhistory;">历史</ulink> (简言之:充满偶然性和异然性)。</para></listitem>  
    33 33 </itemizedlist>  
    34 34 </section>  
    35 35 <section id="roman.divein">  
    36 36 <?dbhtml filename="unit_testing/diving_in.html"?>  
    37   <title>Diving in</title>  
      37 <title>深入</title>  
    37 37 <abstract>  
    38 38 <title/>  
    39   <para>Now that you've completely defined the behavior you expect from your conversion functions, you're going to do something a little unexpected: you're going to write a test suite that puts these functions through their paces and makes sure that they behave the way you want them to.  You read that right: you're going to write code that tests code that you haven't written yet.</para>  
      39 <para>现在你已经定义了你的转换程序所应有的功能,下面一步有点令你出乎意料:你将要开发一个测试组建将使用你未来的函数以确保它们工作正常。没错:你将为还未开发的程序开发测试代码。</para>  
    39 39 </abstract>  
    40   <para>This is called unit testing, since the set of two conversion functions can be written and tested as a unit, separate from any larger program they may become part of later.  &python; has a framework for unit testing, the appropriately-named &unittest_module; module.</para>  
      40 <para>这就是所谓的单元测试,由于这两个转换程序组成可以被当作一个单元共同开发和测试,而不去考虑他们可能今后成为一个大程序的一部分。 &python; 有一个单元测试框架,被恰如其分地称作 &unittest_module; 模块。</para>  
    40 40 <note id="note.unittest" role="compatibility">  
    41   <title>Do you have &unittest_module;?</title>  
    42   <para>&unittest_module; is included with &python; 2.1 and later.  &python; 2.0 users can download it from <ulink url="&url_pyunit;">&url_pyunit_display;</ulink>.</para>  
      41 <title>你有 &unittest_module; 吗?</title>  
      42 <para>&unittest_module; 已被包含于 &python; 2.1 和之后的版本中。 &python; 2.0 用户则可以从 <ulink url="&url_pyunit;">&url_pyunit_display;</ulink>下载。</para>  
    43 43 </note>  
    44   <para>Unit testing is an important part of an overall testing-centric development strategy.  If you write unit tests, it is important to write them early (preferably before writing the code that they test), and to keep them updated as code and requirements change.  Unit testing is not a replacement for higher-level functional or system testing, but it is important in all phases of development:</para>  
      44 <para>单元测试是核心测试开发技巧的一部分。如果你要写单元测试代码,尽早(最好是在被测试代码开发之前)开发并根据代码开发和需求的变化不断更新是很重要的。 单元测试不能取代更高层面的有效性和系统测试,但在开发的每个阶段都很重要。</para>  
    44 44 <itemizedlist>  
    45   <listitem><para>Before writing code, it forces you to detail your requirements in a useful fashion.</para></listitem>  
    46   <listitem><para>While writing code, it keeps you from over-coding.  When all the test cases pass, the function is complete.</para></listitem>  
    47   <listitem><para>When refactoring code, it assures you that the new version behaves the same way as the old version.</para></listitem>  
    48   <listitem><para>When maintaining code, it helps you cover your ass when someone comes screaming that your latest change broke their old code.  (<quote>But <emphasis>sir</emphasis>, all the unit tests passed when I checked it in...</quote>)</para></listitem>  
    49   <listitem><para>When writing code in a team, it increases confidence that the code you're about to commit isn't going to break other peoples' code, because you can run their unittests first. (I've seen this sort of thing in code sprints.  A team breaks up the assignment, everybody takes the specs for their task, writes unit tests for it, then shares their unit tests with the rest of the team.  That way, nobody goes off too far into developing code that won't play well with others.)</para></listitem>  
      45 <listitem><para>代码开发之前,强迫你以有效的方式考虑需求的细节。</para></listitem>  
      46 <listitem><para>代码开发中,防止过度开发。 通过了测试程序就开发完成了。</para></listitem>  
      47 <listitem><para>重构代码时,确保新版和旧版功能一致。</para></listitem>  
      48 <listitem><para>维护代码时,当你的代码更改导致别人代码出问题时帮你留住面子。(<quote>但是 <emphasis>先生</emphasis>, 我检查过,所有的单元测试都通过了......</quote>)</para></listitem>  
      49 <listitem><para>在团队开发时,因为你可以事先运行他人的单元测试代码,所以可以使你有信心自己的代码不至于另他人的代码崩溃。(我曾经见到过, 一个团队共同分担的工作中,每个人都根据自己部分的需求开发单元测试,然后大家共同应用这些单元测试。 这样一来,没有人会出太大的偏差而导致不融于团队。)</para></listitem>  
    50 50 </itemizedlist>  
    51 51 </section>  
    52 52 <section id="roman.romantest">  
    53 53 <?dbhtml filename="unit_testing/romantest.html"?>  
    54   <title>Introducing &romantest_filename;</title>  
      54 <title>介绍 &romantest_filename;</title>  
    54 54 <abstract>  
    55 55 <title/>  
    56   <para>This is the complete test suite for your Roman numeral conversion functions, which are yet to be written but will eventually be in &roman_filename;.  It is not immediately obvious how it all fits together; none of these classes or methods reference any of the others.  There are good reasons for this, as you'll see shortly.</para>  
      56 <para>这是将被开发并存在 &roman_filename; 中的罗马数字转换程序的完整测试组建。很难立刻看出他们是如何协同工作的,似乎所有类或者方法之间都没有关系。这是有原因的,而且你很快就会明了。</para>  
    56 56 </abstract>  
    57 57 <example>  
     
    73 73 </example>  
    74 74 <itemizedlist role="furtherreading">  
    75   <title>Further reading</title>  
    76   <listitem><para><ulink url="&url_pyunit;">The &pyunit; home page</ulink> has an in-depth discussion of <ulink url="&url_pyunit;pyunit.html">using the &unittest_module; framework</ulink>, including advanced features not covered in this chapter.</para></listitem>  
    77   <listitem><para><ulink url="&url_pyunit;pyunit.html">The &pyunit; &faq;</ulink> explains <ulink url="&url_pyunit;pyunit.html#WHERE">why test cases are stored separately</ulink> from the code they test.</para></listitem>  
    78   <listitem><para>&pythonlibraryreference; summarizes the <ulink url="&url_pythonlibraryreference;module-unittest.html">&unittest_module;</ulink> module.</para></listitem>  
    79   <listitem><para><ulink url="&url_xp;">ExtremeProgramming.org</ulink> discusses <ulink url="&url_xp;rules/unittests.html">why you should write unit tests</ulink>.</para></listitem>  
    80   <listitem><para><ulink url="&url_c2;">The Portland Pattern Repository</ulink> has an ongoing discussion of <ulink url="&url_c2;?UnitTests">unit tests</ulink>, including a <ulink url="&url_c2;?StandardDefinitionOfUnitTest">standard definition</ulink>, why you should <ulink url="&url_c2;?CodeUnitTestFirst">code unit tests first</ulink>, and several in-depth <ulink url="&url_c2;?UnitTestTrial">case studies</ulink>.</para></listitem>  
      75 <title>进一步阅读</title>  
      76 <listitem><para><ulink url="&url_pyunit;"> &pyunit; 主页</ulink> 对于使用 <ulink url="&url_pyunit;pyunit.html">&unittest_module; 框架</ulink> 以及本章没能涵盖的高级特性有深入的讨论。</para></listitem>  
      77 <listitem><para><ulink url="&url_pyunit;pyunit.html"> &pyunit; &faq;</ulink> 解释了 <ulink url="&url_pyunit;pyunit.html#WHERE">如何另外存储测试</ulink> 以便与被测试代码分离。</para></listitem>  
      78 <listitem><para>&pythonlibraryreference; 总结了 <ulink url="&url_pythonlibraryreference;module-unittest.html">&unittest_module;</ulink> 模块。</para></listitem>  
      79 <listitem><para><ulink url="&url_xp;">ExtremeProgramming.org</ulink> 讨论 <ulink url="&url_xp;rules/unittests.html">你为什么需要编写单元测试</ulink>。</para></listitem>  
      80 <listitem><para><ulink url="&url_c2;">The Portland Pattern Repository</ulink> 有一个持续的 <ulink url="&url_c2;?UnitTests">单元测试</ulink> 讨论,包括了一个 <ulink url="&url_c2;?StandardDefinitionOfUnitTest">标准的定义</ulink>,为什么你需要 <ulink url="&url_c2;?CodeUnitTestFirst">首先开发单元测试代码</ulink> 以及另外一些深层次 <ulink url="&url_c2;?UnitTestTrial">案例</ulink>。</para></listitem>  
    81 81 </itemizedlist>  
    82 82 </section>  
    83 83 <section id="roman.success">  
    84 84 <?dbhtml filename="unit_testing/testing_for_success.html"?>  
    85   <title>Testing for success</title>  
      85 <title>成功测试法</title>  
    85 85 <abstract>  
    86 86 <title/>  
    87   <para>The most fundamental part of unit testing is constructing individual test cases.  A test case answers a single question about the code it is testing.</para>  
      87 <para>单元测试的基础是构建独立测试(test case)。 一个独立测试回答一个关于被测试代码的问题。</para>  
    87 87 </abstract>  
    88   <para>A test case should be able to...</para>  
      88 <para>一个独立测试需要:</para>  
    88 88 <itemizedlist>  
    89   <listitem><para>...run completely by itself, without any human input.  Unit testing is about automation.</para></listitem>  
    90   <listitem><para>...determine by itself whether the function it is testing has passed or failed, without a human interpreting the results.</para></listitem>  
    91   <listitem><para>...run in isolation, separate from any other test cases (even if they test the same functions).  Each test case is an island.</para></listitem>  
      89 <listitem><para>完全独立运行,不需要人工输入。单元测试应该是自动的。</para></listitem>  
      90 <listitem><para>可以自己决断,不论被测试函数通过还是失败都不需要人工干预结果。</para></listitem>  
      91 <listitem><para>隔离运行,可以与其他独立测试隔离(尽管他们可能测试着同一个函数)。每个独立测试是一个孤岛。</para></listitem>  
    92 92 </itemizedlist>  
    93   <para>Given that, let's build the first test case.  You have the following <link linkend="roman.requirements">requirement</link>:</para>  
      93 <para>基于如上原则,让我们构建第一个独立测试。应符合如下 <link linkend="roman.requirements">要求</link>:</para>  
    93 93 <orderedlist>  
    94   <listitem><para>&toroman; should return the Roman numeral representation for all integers &one; to <literal>3999</literal>.</para></listitem>  
      94 <listitem><para>&toroman; 应该为所有 &one; 到 <literal>3999</literal>  的整数返回罗马数字表示。</para></listitem>  
    94 94 </orderedlist>  
    95 95 <example id="roman.testtoromanknownvalues.example">  
     
    111 111 <calloutlist>  
    112 112 <callout arearefs="roman.success.1.0">  
    113   <para>To write a test case, first subclass the &testcase_classname; class of the &unittest_module; module.  This class provides many useful methods which you can use in your test case to test specific conditions.</para>  
      113 <para>在编写独立测试时,第一个子类派生自  &unittest_module; 模块的 &testcase_classname; 类,这个类提供了用于独立测试特定情况测试的很多有用方法。</para>  
    113 113 </callout>  
    114 114 <callout arearefs="roman.success.1.1">  
    115   <para>This is a list of integer/numeral pairs that I verified manually.  It includes the lowest ten numbers, the highest number, every number that translates to a single-character Roman numeral, and a random sampling of other valid numbers.  The point of a unit test is not to test every possible input, but to test a representative sample.</para>  
      115 <para>此时我手工转换的一个 integer/numeral 对列表。 它包含了最小的十个数,最大数,每个罗马数字单字符对应的数,以及其他随即挑选的有效数样本。单元测试的关键不在于所有可能的输入,而是一个有代表性的样本。</para>  
    115 115 </callout>  
    116 116 <callout arearefs="roman.success.1.2">  
    117   <para>Every individual test is its own method, which must take no parameters and return no value.  If the method exits normally without raising an exception, the test is considered passed; if the method raises an exception, the test is considered failed.</para>  
      117 <para>每个独立测试都是其自己的方法,必须不需要输入值也不返回任何值。如果该方法正常退出而没有引发异常,测试被认为通过;如果测试引发异常,测试被认为失败。</para>  
    117 117 </callout>  
    118 118 <callout arearefs="roman.success.1.3">  
    119   <para>Here you call the actual &toroman; function.  (Well, the function hasn't be written yet, but once it is, this is the line that will call it.)  Notice that you have now defined the &api; for the &toroman; function: it must take an integer (the number to convert) and return a string (the Roman numeral representation).  If the &api; is different than that, this test is considered failed.</para>  
      119 <para>这里你真正调用 &toroman; 函数。(当然,函数还没有编写,但一旦被编写,这里便是调用之处) 注意你在这里为 &toroman; 函数定义了 &api; :它必须接受整数(待转换的数)并返回一个字符串(罗马数字表示), 如果 &api; 不是这样,测试将失败。</para>  
    119 119 </callout>  
    120 120 <callout arearefs="roman.success.1.4">  
    121   <para>Also notice that you are not trapping any exceptions when you call &toroman;.  This is intentional.  &toroman; shouldn't raise an exception when you call it with valid input, and these input values are all valid.  If &toroman; raises an exception, this test is considered failed.</para>  
      121 <para>同样值得注意,你在调用 &toroman; 时没有使用任何可能发生意外的数。这正是我们所希望的,以有效输入调用,不会引发任何异常,这里的输入都是有效的。如果 &toroman; 引发了异常,则测试被认为失败。</para>  
    121 121 </callout>  
    122 122 <callout arearefs="roman.success.1.5">  
    123   <para>Assuming the &toroman; function was defined correctly, called correctly, completed successfully, and returned a value, the last step is to check whether it returned the <emphasis>right</emphasis> value.  This is a common question, and the &testcase_classname; class provides a method, &assertEqual;, to check whether two values are equal.  If the result returned from &toroman; (<varname>result</varname>) does not match the known value you were expecting (<varname>numeral</varname>), &assertEqual; will raise an exception and the test will fail.  If the two values are equal, &assertEqual; will do nothing.  If every value returned from &toroman; matches the known value you expect, &assertEqual; never raises an exception, so <function>testToRomanKnownValues</function> eventually exits normally, which means &toroman; has passed this test.</para>  
      123 <para>假设 &toroman; 函数被正确编写,正确调用,完全成功并返回一个值,最后一步便是这个返回值<emphasis>正确</emphasis>与否。这是一个常见的问题, &testcase_classname; 类提供了一个方法: &assertEqual; 来测试两个值是否相等。如果 &toroman; 返回的 (<varname>value</varname>) 不像我们预期的等于 (<varname>numeral</varname>), &assertEqual; 将会引发一个异常,测试也就此失败。如果两个值相等,&assertEqual; 什么也不做。如果每个从 &toroman; 返回的值都等于预期值 &assertEqual; 便不会引发异常, <function>testToRomanKnownValues</function> 最终正常退出,这意味着 &toroman; 通过了该测试。</para>  
    123 123 </callout>  
    124 124 </calloutlist>  
     
    133 133 <section id="roman.failure">  
    134 134 <?dbhtml filename="unit_testing/testing_for_failure.html"?>  
    135   <title>Testing for failure</title>  
      135 <title>失败测试法</title>  
    135 135 <abstract>  
    136 136 <title/>  
    137   <para>It is not enough to test that functions succeed when given good input; you must also test that they fail when given bad input.  And not just any sort of failure; they must fail in the way you expect.</para>  
      137 <para>使用有效输入确保函数成功通过测试还不够,你还需要测试无效输入导致函数失败的情形。但并不是任何失败都可以,必须如你预期地失败。</para>  
    137 137 </abstract>  
    138   <para>Remember the <link linkend="roman.requirements">other requirements</link> for &toroman;:</para>  
      138 <para>还记得 &toroman; 的 <link linkend="roman.requirements">其他要求</link> 吧:</para>  
    138 138 <orderedlist continuation="continues">  
    139   <listitem><para>&toroman; should fail when given an integer outside the range &one; to <literal>3999</literal>.</para></listitem>  
    140   <listitem><para>&toroman; should fail when given a non-integer number.</para></listitem>  
      139 <listitem><para>&toroman; 在输入值为 &one; to <literal>3999</literal> 之外时失败。</para></listitem>  
      140 <listitem><para>&toroman; 在输入值为非整数时失败。</para></listitem>  
    141 141 </orderedlist>  
    142   <para>In &python;, functions indicate failure by raising <link linkend="fileinfo.exception">exceptions</link>, and the &unittest_module; module provides methods for testing whether a function raises a particular exception when given bad input.</para>  
      142 <para>在 &python; 中,函数以引发<link linkend="fileinfo.exception">异常</link>的方式表示失败。&unittest_module; 模块提供了用于测试函数是否在给定无效输入时引发特定异常的方法。</para>  
    142 142 <example id="roman.tobadinput.example">  
    143   <title>Testing bad input to &toroman;</title>  
      143 <title>测试 &toroman; 的无效输入</title>  
    143 143 <programlisting>  
    144 144 &romantest_tobadinputdef;  
     
    165 165 <calloutlist>  
    166 166 <callout arearefs="roman.failure.1.1">  
    167   <para>The &testcase_classname; class of the &unittest_module; provides the &assertRaises; method, which takes the following arguments: the exception you're expecting, the function you're testing, and the arguments you're passing that function.  (If the function you're testing takes more than one argument, pass them all to &assertRaises;, in order, and it will pass them right along to the function you're testing.)  Pay close attention to what you're doing here: instead of calling &toroman; directly and manually checking that it raises a particular exception (by wrapping it in a <link linkend="fileinfo.exception">&tryexcept; block</link>), &assertRaises; has encapsulated all of that for us.  All you do is give it the exception (<errorcode>roman.OutOfRangeError</errorcode>), the function (&toroman;), and &toroman;'s arguments (<literal>4000</literal>), and &assertRaises; takes care of calling &toroman; and checking to make sure that it raises <errorcode>roman.OutOfRangeError</errorcode>.  (Also note that you're passing the &toroman; function itself as an argument; you're not calling it, and you're not passing the name of it as a string.  Have I mentioned recently how handy it is that <link linkend="odbchelper.objects">everything in &python; is an object</link>, including functions and exceptions?)</para>  
      167 <para>&unittest_module; 模块中的 &testcase_classname; 类提供了 &assertRaises; 方法,它接受这几个参数:预期的异常,测试的函数以及传递给函数的参数 (如果被测试函数有不止一个参数,把它们按顺序全部传递给 &assertRaises; ,它会把这些参数传给被测的函数。)特别注意这里的操作:不是直接调用 &toroman; 再手工查看是否引发特定异常 (使用 <link linkend="fileinfo.exception">&tryexcept; block</link> 捕捉异常), &assertRaises; 为我们封装了这些。 所有你要做的就是把异常(<errorcode>roman.OutOfRangeError</errorcode>),函数(&toroman;)以及 &toroman; 的参数(<literal>4000</literal>)传递给 &assertRaises; ,它会调用 &toroman; 查看是否引发 <errorcode>roman.OutOfRangeError</errorcode> 异常。 (还应注意到你是把 &toroman; 函数本身当作一个参数,而不是调用它,传递它的时候也不是把它的名字作为一个字符串。我提到过吗? 无论是函数还是异常,<link linkend="odbchelper.objects"> &python; 中的任何事物都是对象</link>)。</para>  
    167 167 </callout>  
    168 168 <callout arearefs="roman.failure.1.2">  
    169   <para>Along with testing numbers that are too large, you need to test numbers that are too small.  Remember, Roman numerals cannot express &zero; or negative numbers, so you have a test case for each of those (<function>testZero</function> and <function>testNegative</function>).  In <function>testZero</function>, you are testing that &toroman; raises a <errorcode>roman.OutOfRangeError</errorcode> exception when called with &zero;; if it does <emphasis>not</emphasis> raise a <errorcode>roman.OutOfRangeError</errorcode> (either because it returns an actual value, or because it raises some other exception), this test is considered failed.</para>  
      169 <para>与测试过大的数相伴的便是测试过小的数。记住,罗马数字不能表示 &zero; 和负数,所以你要分别编写独立测试( <function>testZero</function> 和 <function>testNegative</function>)。 在 <function>testZero</function> 中,你测试 &toroman; 调用 &zero; 引发的 <errorcode>roman.OutOfRangeError</errorcode> 异常,如果 <emphasis>没能</emphasis> 引发 <errorcode>roman.OutOfRangeError</errorcode> (不论是返回了一个值或者引发其他异常),则测试失败。</para>  
    169 169 </callout>  
    170 170 <callout arearefs="roman.failure.1.3">  
    171   <para><link linkend="roman.requirements">Requirement #3</link> specifies that &toroman; cannot accept a non-integer number, so here you test to make sure that &toroman; raises a <errorcode>roman.NotIntegerError</errorcode> exception when called with <literal>0.5</literal>.  If &toroman; does not raise a <errorcode>roman.NotIntegerError</errorcode>, this test is considered failed.</para>  
      171 <para><link linkend="roman.requirements">要求 #3</link>  &toroman; 不能接受非整数输入,所以这里你测试 &toroman; 在输入 <literal>0.5</literal> 时引发 <errorcode>roman.NotIntegerError</errorcode> 异常。如果 &toroman; 没有引发 <errorcode>roman.NotIntegerError</errorcode> 异常,则测试失败。</para>  
    171 171 </callout>  
    172 172 </calloutlist>  
    173 173 </example>  
    174   <para>The next two <link linkend="roman.requirements">requirements</link> are similar to the first three, except they apply to &fromroman; instead of &toroman;:</para>  
      174 <para>接下来的两个 <link linkend="roman.requirements">要求</link> 与前三个类似,不同点是他们所针对的是 &fromroman; 而不是 &toroman;:</para>  
    174 174 <orderedlist continuation="continues">  
    175   <listitem><para>&fromroman; should take a valid Roman numeral and return the number that it represents.</para></listitem>  
    176   <listitem><para>&fromroman; should fail when given an invalid Roman numeral.</para></listitem>  
      175 <listitem><para>&fromroman; 应该能将输入的有效罗马数字转换为相应的阿拉伯数字表示。</para></listitem>  
      176 <listitem><para>&fromroman; 在输入无效罗马数字时应该失败。</para></listitem>  
    177 177 </orderedlist>  
    178   <para>Requirement #4 is handled in the same way as <link linkend="roman.testtoromanknownvalues.example">requirement #1</link>, iterating through a sampling of known values and testing each in turn.  Requirement #5 is handled in the same way as requirements #2 and #3, by testing a series of bad inputs and making sure &fromroman; raises the appropriate exception.</para>  
      178 <para>要求 #4 与 <link linkend="roman.testtoromanknownvalues.example">要求 #1</link> 的处理方法相同,测试一个已知样本中的一个个数字对。 要求 #5 与 #2 和 #3的处理方法相同,通过无效输入引发 &fromroman; 引发恰当的异常。</para>  
    178 178 <example id="roman.frombadinput.example">  
    179   <title>Testing bad input to &fromroman;</title>  
      179 <title>测试 &fromroman; 的无效输入</title>  
    179 179 <programlisting>  
    180 180 &romantest_frombadinputdef;  
     
    201 201 <calloutlist>  
    202 202 <callout arearefs="roman.failure.2.1">  
    203   <para>Not much new to say about these; the pattern is exactly the same as the one you used to test bad input to &toroman;.  I will briefly note that you have another exception: <errorcode>roman.InvalidRomanNumeralError</errorcode>.  That makes a total of three custom exceptions that will need to be defined in &roman_filename; (along with <errorcode>roman.OutOfRangeError</errorcode> and <errorcode>roman.NotIntegerError</errorcode>).  You'll see how to define these custom exceptions when you actually start writing &roman_filename;, later in this chapter.</para>  
      203 <para>没什么新鲜的, 与测试 &toroman; 无效输入时相同的模式,只是你有了一个新的异常: <errorcode>roman.InvalidRomanNumeralError</errorcode>。总计三个自定义异常需要在 &roman_filename; 中定义(另外的两个是 <errorcode>roman.OutOfRangeError</errorcode> 和 <errorcode>roman.NotIntegerError</errorcode>)。稍后你在开始编写 &roman_filename; 时将会知道如何定义这些自定义异常。</para>  
    203 203 </callout>  
    204 204 </calloutlist>  
     
    208 208 <section id="roman.sanity">  
    209 209 <?dbhtml filename="unit_testing/testing_for_sanity.html"?>  
    210   <title>Testing for sanity</title>  
      210 <title>回旋测试(Testing for sanity)</title>  
    210 210 <abstract>  
    211 211 <title/>  
    212   <para>Often, you will find that a unit of code contains a set of reciprocal functions, usually in the form of conversion functions where one converts A to B and the other converts B to A.  In these cases, it is useful to create a <quote>sanity check</quote> to make sure that you can convert A to B and back to A without losing precision, incurring rounding errors, or triggering any other sort of bug.</para>  
      212 <para>你经常会发现一组代码中包含互反函数,他们通常是转换函数,一个把 A 转换为 B ,另一个把 B 转换为 A。 在这种情况下,创建<quote>回旋检查</quote>可以使你在由 A 转 B 再转 A 的过程中确保精确,不至于出现取整等错误。</para>  
    212 212 </abstract>  
    213   <para>Consider this <link linkend="roman.requirements">requirement</link>:</para>  
      213 <para>考虑这个<link linkend="roman.requirements">要求</link>:</para>  
    213 213 <orderedlist continuation="continues">  
    214   <listitem><para>If you take a number, convert it to Roman numerals, then convert that back to a number, you should end up with the number you started with.  So <literal>fromRoman(toRoman(n)) == n</literal> for all <varname>n</varname> in <literal>1..3999</literal>.</para></listitem>  
      214 <listitem><para>如果你给定一个数,把它转化为罗马数字表示,然后再转换回阿拉伯数字表示,你所得到的应该是最初改定的那个数。 因此,对于 <literal>1..3999</literal> 中的<varname>n</varname>, <literal>fromRoman(toRoman(n)) == n</literal> 总成立。</para></listitem>  
    214 214 </orderedlist>  
    215 215 <example id="roman.sanity.example">  
    216   <title>Testing &toroman; against &fromroman;</title>  
      216 <title>以 &toroman; 测试 &fromroman; 的输出</title>  
    216 216 <programlisting>  
    217 217 &romantest_sanityclassdef;  
     
    229 229 <calloutlist>  
    230 230 <callout arearefs="roman.sanity.1.1">  
    231   <para>You've seen <link linkend="odbchelper.multiassign.range">the &range; function</link> before, but here it is called with two arguments, which returns a list of integers starting at the first argument (&one;) and counting consecutively up to <emphasis>but not including</emphasis> the second argument (<literal>4000</literal>).  Thus, <literal>1..3999</literal>, which is the valid range for converting to Roman numerals.</para>  
      231 <para>你已经见到过 <link linkend="odbchelper.multiassign.range"> 这个 &range; 函数</link>, 但这里它以两个参数被调用,返回了第一个参数(&one;)开始到<emphasis>但不包括</emphasis>第二个参数(<literal>4000</literal>)的整数列表。因此,<literal>1..3999</literal> 就是准备转换为罗马数字表示的有效值列表。</para>  
    231 231 </callout>  
    232 232 <callout arearefs="roman.sanity.1.2">  
    233   <para>I just wanted to mention in passing that <varname>integer</varname> is not a keyword in &python;; here it's just a variable name like any other.</para>  
      233 <para>我想提一下,这里的 <varname>integer</varname> 并不是一个 &python; 关键字,而只是没有什么特别的变量名。</para>  
    233 233 </callout>  
    234 234 <callout arearefs="roman.sanity.1.3">  
    235   <para>The actual testing logic here is straightforward: take a number (<varname>integer</varname>), convert it to a Roman numeral (<varname>numeral</varname>), then convert it back to a number (<varname>result</varname>) and make sure you end up with the same number you started with.  If not, &assertEqual; will raise an exception and the test will immediately be considered failed.  If all the numbers match, &assertEqual; will always return silently, the entire <function>testSanity</function> method will eventually return silently, and the test will be considered passed.</para>  
      235 <para>这里的测试逻辑显而易见:把一个数 (<varname>integer</varname>)转换为罗马数字表示的数(<varname>numeral</varname>),然后再转换回来(<varname>result</varname>)并确保最后的结果和最初的数是同一个数。 如果不是, &assertEqual; 便会引发异常,测试也便立刻失败。如果所有的结果都和初始数一致, &assertEqual; 将会保持沉默,整个 <function>testSanity</function> 方法将会最终也保持沉默,测试则将会被认定为通过。</para>  
    235 235 </callout>  
    236 236 </calloutlist>  
    237 237 </example>  
    238   <para>The <link linkend="roman.requirements">last two requirements</link> are different from the others because they seem both arbitrary and trivial:</para>  
      238 <para><link linkend="roman.requirements">最后两个要求</link> 和其他的要求不同,似乎即武断又琐碎:</para>  
    238 238 <orderedlist continuation="continues">  
    239   <listitem><para>&toroman; should always return a Roman numeral using uppercase letters.</para></listitem>  
    240   <listitem><para>&fromroman; should only accept uppercase Roman numerals (&ie; it should fail when given lowercase input).</para></listitem>  
      239 <listitem><para>&toroman; 返回的罗马数字应该使用大写字母。</para></listitem>  
      240 <listitem><para>&fromroman; 应该只接受大写罗马数字(也就是说给定小写字母进行转换时应该失败)。</para></listitem>  
    241 241 </orderedlist>  
    242   <para>In fact, they are somewhat arbitrary.  You could, for instance, have stipulated that &fromroman; accept lowercase and mixed case input.  But they are not completely arbitrary; if &toroman; is always returning uppercase output, then &fromroman; must at least accept uppercase input, or the <quote>sanity check</quote> (requirement #6) would fail.  The fact that it <emphasis>only</emphasis> accepts uppercase input is arbitrary, but as any systems integrator will tell you, case always matters, so it's worth specifying the behavior up front.  And if it's worth specifying, it's worth testing.</para>  
      242 <para>事实上,它们确实有点武断,譬如你完全可以让 &fromroman; 接受小写和大小写混合的输入;但他们也不是完全武断;如果 &toroman; 总是返回大写的输出,那么 &fromroman; 至少应该接受大写字母输入,不然 <quote>回旋检查</quote>(要求 #6)就会失败。 但归根结底,<emphasis>只</emphasis>接受大写输入还是武断, 但就像每个系统都回告诉你的那样,大小写总是个问题,因此事先规定这一点还是有必要的。既然是有必要规定的,那么也有必要测试。</para>  
    242 242 <example>  
    243   <title>Testing for case</title>  
      243 <title>大小写测试</title>  
    243 243 <programlisting>  
    244 244 &romantest_casecheckdef;  
     
    263 263 <calloutlist>  
    264 264 <callout arearefs="roman.sanity.2.1">  
    265   <para>The most interesting thing about this test case is all the things it doesn't test.  It doesn't test that the value returned from &toroman; is <link linkend="roman.testtoromanknownvalues.example">right</link> or even <link linkend="roman.sanity.example">consistent</link>; those questions are answered by separate test cases.  You have a whole test case just to test for uppercase-ness.  You might be tempted to combine this with the <link linkend="roman.sanity.example">sanity check</link>, since both run through the entire range of values and call &toroman;.<footnote><para><quote>I can resist everything except temptation.</quote> --Oscar Wilde</para></footnote>  But that would violate one of the <link linkend="roman.success">fundamental rules</link>: each test case should answer only a single question.  Imagine that you combined this case check with the sanity check, and then that test case failed.  You would need to do further analysis to figure out which part of the test case failed to determine what the problem was.  If you need to analyze the results of your unit testing just to figure out what they mean, it's a sure sign that you've mis-designed your test cases.</para>  
      265 <para>很有意思的是这个独立测试它并不测试什么。它所测试的不是 &toroman; 的返回值是否 <link linkend="roman.testtoromanknownvalues.example">正确</link> 或者 <link linkend="roman.sanity.example">一致</link>;这些问题由其他独立测试来回答。整个这个独立测试仅仅是测试大写问题。你也许觉得应该将它并入到 <link linkend="roman.sanity.example">回旋检查</link>,毕竟都要遍历整个输入值范围并调用 &toroman;。<footnote><para><quote>除了诱惑什么我都能抗拒(I can resist everything except temptation.)</quote> --Oscar Wilde</para></footnote>  但是这样将会违背一条 <link linkend="roman.success">基本规则</link>:每个独立测试都应该只回答单一的问题。试想一下,你将这个测试并入到回旋检查中并遇到了测试失败。 你还需要进一步分析以便判定独立测试的哪部分出了问题。 如果你需要分析方能找出问题所在,无疑你的独立测试在设计上出了问题。</para>  
    265 265 </callout>  
    266 266 <callout arearefs="roman.sanity.2.2">  
    267   <para>There's a similar lesson to be learned here: even though <quote>you know</quote> that &toroman; always returns uppercase, you are explicitly converting its return value to uppercase here to test that &fromroman; accepts uppercase input.  Why?  Because the fact that &toroman; always returns uppercase is an independent requirement.  If you changed that requirement so that, for instance, it always returned lowercase, the <function>testToRomanCase</function> test case would need to change, but this test case would still work.  This was another of the <link linkend="roman.success">fundamental rules</link>: each test case must be able to work in isolation from any of the others.  Every test case is an island.</para>  
      267 <para>这有一个和前面相似的情况: 尽管 <quote>你知道</quote> &toroman; 总是返回大写字母,你可以直接把返回值传递给只接受大写的 &fromroman; 进行测试。 为什么?因为 &toroman; 只返回大写字母是一个独立的要求。如果你改变了这个要求,例如改成总是返回小写字母,那么 <function>testToRomanCase</function> 独立测试也应作出调整,独立测试应该仍能通过。 这是另外一个 <link linkend="roman.success">基本规则</link>:每个独立测试必须可以与其他独立测试隔离, 每个独立测试是一个孤岛。</para>  
    267 267 </callout>  
    268 268 <callout arearefs="roman.sanity.2.3">  
    269   <para>Note that you're not assigning the return value of &fromroman; to anything.  This is legal syntax in &python;; if a function returns a value but nobody's listening, &python; just throws away the return value.  In this case, that's what you want.  This test case doesn't test anything about the return value; it just tests that &fromroman; accepts the uppercase input without raising an exception.</para>  
      269 <para>注意你并没有使用 &fromroman; 的返回值。 这是一个合法的 &python; 语法:如果一个函数返回一个值,但没有被使用, &python; 会直接把这个返回值扔掉。 这正是你所希望的,这个独立测试并不对返回值进行测试,只是测试 &fromroman; 接受大写字母而不引发异常。</para>  
    269 269 </callout>  
    270 270 <callout arearefs="roman.sanity.2.4">  
    271   <para>This is a complicated line, but it's very similar to what you did in the <classname>ToRomanBadInput</classname> and <classname>FromRomanBadInput</classname> tests.  You are testing to make sure that calling a particular function (<function>roman.fromRoman</function>) with a particular value (<literal>numeral.lower()</literal>, the lowercase version of the current Roman numeral in the loop) raises a particular exception (<literal>roman.InvalidRomanNumeralError</literal>).  If it does (each time through the loop), the test passes; if even one time it does something else (like raises a different exception, or returning a value without raising an exception at all), the test fails.</para>  
      271 <para>这行有点复杂,但是它与 <classname>ToRomanBadInput</classname> 和 <classname>FromRomanBadInput</classname> 测试很相似。 你在测试以确保特定函数 (<function>roman.fromRoman</function>)接受特定值(<literal>numeral.lower()</literal>,循环中目前罗马数字的小写版)引发特定的异常 (<literal>roman.InvalidRomanNumeralError</literal>)。 如果(在循环中的每一次)确实如此,测试通过;如果有一次不是这样(比如引发另外的异常或者不引发异常),测试失败。</para>  
    271 271 </callout>  
    272 272 </calloutlist>  
    273 273 </example>  
    274   <para>In the next chapter, you'll see how to write code that passes these tests.</para>  
      274 <para>在下一章中,你将看到如何编写可以通过测试的代码。</para>  
    274 274 </section>  
    275 275 </chapter>  
    276 276 <chapter id="roman1.5">  
    277 277 <?dbhtml filename="unit_testing/stage_1.html"?>  
    278   <title>Test-First Programming</title>  
    279   <titleabbrev id="roman1.5.numberonly">Chapter 14</titleabbrev>  
      278 <title>程序第一测试</title>  
      279 <titleabbrev id="roman1.5.numberonly">第 14 章</titleabbrev>  
    280 280 <section id="roman.stage1">  
    281   <title>&roman_filename;, stage 1</title>  
      281 <title>&roman_filename;, 第 1 步</title>  
    281 281 <abstract>  
    282 282 <title/>  
    283   <para>Now that the unit tests are complete, it's time to start writing the code that the test cases are attempting to test.  You're going to do this in stages, so you can see all the unit tests fail, then watch them pass one by one as you fill in the gaps in &roman_filename;.</para>  
      283 <para>到目前为止,单元测试已经完成,到了开始编写被单元测试测试的代码的时候了。你将一步步完成这个工作,你将看到所有的单元测试都是失败的,然后一个个攻破也同时填充了 &roman_filename;。</para>  
    283 283 </abstract>  
    284 284 <example>  
    285 285 <title>&roman1_filename;</title>  
    286   <para>This file is available in <filename>py/roman/stage1/</filename> in the examples directory.</para>  
      286 <para>这个程序可以从 <filename>py/roman/stage1/</filename> 的 examples 目录中获得。</para>  
    286 286 &para_download;  
    287 287 <programlisting>  
     
    312 312 <calloutlist>  
    313 313 <callout arearefs="roman.stage1.1.1">  
    314   <para>This is how you define your own custom exceptions in &python;.  Exceptions are classes, and you create your own by subclassing existing exceptions.  It is strongly recommended (but not required) that you subclass <errorcode>Exception</errorcode>, which is the base class that all built-in exceptions inherit from.  Here I am defining <errorcode>RomanError</errorcode> (inherited from <errorcode>Exception</errorcode>) to act as the base class for all my other custom exceptions to follow.  This is a matter of style; I could just as easily have inherited each individual exception from the <errorcode>Exception</errorcode> class directly.</para>  
      314 <para>这是如何定义你自己的 &python; 异常。你可以通过建立已有异常的子类来构建。 强烈建议(但不是必须)你从建立 <errorcode>Exception</errorcode> 这个所有内建异常的父类的子类入手。 这里我定义了 <errorcode>RomanError</errorcode> (从 <errorcode>Exception</errorcode> 继承而来)作为我所有自定义异常的基类。 这是一个风格问题,我也可以直接从 <errorcode>Exception</errorcode> 继承建立每一个自定义异常。</para>  
    314 314 </callout>  
    315 315 <callout arearefs="roman.stage1.1.2">  
    316   <para>The <errorcode>OutOfRangeError</errorcode> and <errorcode>NotIntegerError</errorcode> exceptions will eventually be used by &toroman; to flag various forms of invalid input, as specified in <link linkend="roman.tobadinput.example"><classname>ToRomanBadInput</classname></link>.</para>  
      316 <para><errorcode>OutOfRangeError</errorcode> 和 <errorcode>NotIntegerError</errorcode> 异常将会最终被用于 &toroman; 以标示不同类型的无效输入,更具体而言就是 <link linkend="roman.tobadinput.example"><classname>ToRomanBadInput</classname></link> 测试的那些。</para>  
    316 316 </callout>  
    317 317 <callout arearefs="roman.stage1.1.3">  
    318   <para>The <errorcode>InvalidRomanNumeralError</errorcode> exception will eventually be used by &fromroman; to flag invalid input, as specified in <link linkend="roman.frombadinput.example"><classname>FromRomanBadInput</classname></link>.</para>  
      318 <para><errorcode>InvalidRomanNumeralError</errorcode> 将被最终用于 &fromroman; 以标示无效输入,具体而言就是 <link linkend="roman.frombadinput.example"><classname>FromRomanBadInput</classname></link>测试的那些。</para>  
    318 318 </callout>  
    319 319 <callout arearefs="roman.stage1.1.4">  
    320   <para>At this stage, you want to define the &api; of each of your functions, but you don't want to code them yet, so you stub them out using the &python; reserved word <link linkend="fileinfo.class.simplest">&pass;</link>.</para>  
      320 <para>在这一步中你需要定义每个函数的 &api; ,但是你不需要立刻编写他们,而是以 &python; 关键字 <link linkend="fileinfo.class.simplest">&pass;</link> 一笔带过。</para>  
    320 320 </callout>  
    321 321 </calloutlist>  
    322 322 </example>  
    323   <para>Now for the big moment (drum roll please): you're finally going to run the unit test against this stubby little module.  At this point, every test case should fail.  In fact, if any test case passes in stage 1, you should go back to &romantest_filename; and re-evaluate why you coded a test so useless that it passes with do-nothing functions.</para>  
    324   <para>Run &romantest1_filename; with the <option>-v</option> command-line option, which will give more verbose output so you can see exactly what's going on as each test case runs.  With any luck, your output should look like this:</para>  
      323 <para>重要的时刻到了(请打起鼓来):你终于要对这个简陋的小模块开始运行单元测试了。 目前而言,每一个独立测试都应该失败。 事实上,任何独立测试在第 1 步通过,你都因该返回 &romantest_filename; 重新评价一下,为什么你的测试代码如此没用,什么都不作的函数都能通过测试。</para>  
      324 <para>运行 &romantest1_filename; 带有 <option>-v</option> 选项的命令行可以得到更详细的输出信息,这样你就可以看到每一个独立测试的具体运行情况。如果幸运,你的结果应该是这样的:</para>  
    325 325 <example id="roman.stage1.output">  
    326 326 <title>Output of &romantest1_filename; against &roman1_filename;</title>  
     
    452 452 <calloutlist>  
    453 453 <callout arearefs="roman.stage1.2.1">  
    454   <para>Running the script runs <function>unittest.main()</function>, which runs each test case, which is to say each method defined in each class within &romantest_filename;.  For each test case, it prints out the &docstring; of the method and whether that test passed or failed.  As expected, none of the test cases passed.</para>  
      454 <para>运行脚本将会执行 <function>unittest.main()</function>,由它来执行每个独立测试,也就是每个在 &romantest_filename; 中定义的方法。 对于每个独立测试,无论测试通过与否,都会输出 &docstring;。 意料之中,没有通过一个独立测试。</para>  
    454 454 </callout>  
    455 455 <callout arearefs="roman.stage1.2.2">  
    456   <para>For each failed test case, &unittest_module; displays the trace information showing exactly what happened.  In this case, the call to &assertRaises; (also called <function>failUnlessRaises</function>) raised an <errorcode>AssertionError</errorcode> because it was expecting &toroman; to raise an <errorcode>OutOfRangeError</errorcode> and it didn't.</para>  
      456 <para>对于每个失败的独立测试, &unittest_module; 显示的跟踪信息告诉我们都发生了什么。 就此处而言,调用 &assertRaises; (也称作 <function>failUnlessRaises</function>) 引发了一个 <errorcode>AssertionError</errorcode> 异常, 因为期待 &toroman; 所引发的 <errorcode>OutOfRangeError</errorcode> 异常没有出现。</para>  
    456 456 </callout>  
    457 457 <callout arearefs="roman.stage1.2.3">  
    458   <para>After the detail, &unittest_module; displays a summary of how many tests were performed and how long it took.</para>  
      458 <para>在这些细节后面, &unittest_module; 给出了一个关于测试运行的总结和运行耗时。</para>  
    458 458 </callout>  
    459 459 <callout arearefs="roman.stage1.2.4">  
    460   <para>Overall, the unit test failed because at least one test case did not pass.  When a test case doesn't pass, &unittest_module; distinguishes between failures and errors.  A failure is a call to an <function>assertXYZ</function> method, like &assertEqual; or &assertRaises;, that fails because the asserted condition is not true or the expected exception was not raised.  An error is any other sort of exception raised in the code you're testing or the unit test case itself.  For instance, the <function>testFromRomanCase</function> method (<quote>&fromroman; should only accept uppercase input</quote>) was an error, because the call to <function>numeral.upper()</function> raised an <errorcode>AttributeError</errorcode> exception, because &toroman; was supposed to return a string but didn't.  But <function>testZero</function> (<quote>&toroman; should fail with 0 input</quote>) was a failure, because the call to &fromroman; did not raise the <errorcode>InvalidRomanNumeral</errorcode> exception that &assertRaises; was looking for.</para>  
      460 <para>总而言之,任何一个独立测试不能通过都会导致单元测试失败。 当一个独立测试没能通过, &unittest_module; 能在失败和错误间进行分辨。 失败则调用 <function>assertXYZ</function> 方法, 比如: &assertEqual; 或者 &assertRaises;,证明这个失败可能是由于例外情况的产生或者预期异常没有引发而导致。 一个错误不是任何类型的异常溢出或者单元测试本身的问题。例如:<function>testFromRomanCase</function> 方法 (<quote>&fromroman; 只接受大写输入</quote>)就是一个错误,因为调用 <function>numeral.upper()</function> 引发了一个 <errorcode>AttributeError</errorcode> 异常,因为没有返回 &toroman; 支持的字符串类型的返回值。 但是, <function>testZero</function> (<quote>&toroman; 应该在输入 0 时失败</quote>)是一个失败,因为调用 &fromroman; 没有引发一个 &assertRaises; 期待的异常: <errorcode>InvalidRomanNumeral</errorcode>。</para>  
    460 460 </callout>  
    461 461 </calloutlist>  
     
    468 468 <section id="roman.stage2">  
    469 469 <?dbhtml filename="unit_testing/stage_2.html"?>  
    470   <title>&roman_filename;, stage 2</title>  
      470 <title>&roman_filename;, 第 2 步</title>  
    470 470 <abstract>  
    471 471 <title/>  
    472   <para>Now that you have the framework of the &roman_module; module laid out, it's time to start writing code and passing test cases.</para>  
      472 <para>现在你有了 &roman_module; 模块的大概框架, 到了开始写代码以通过测试的时候了。</para>  
    472 472 </abstract>  
    473 473 <example id="roman.stage2.example">  
    474 474 <title>&roman2_filename;</title>  
    475   <para>This file is available in <filename>py/roman/stage2/</filename> in the examples directory.</para>  
      475 <para>这个文件可以从 <filename>py/roman/stage2/</filename> 的 examples 目录中获得。</para>  
    475 475 &para_download;  
    476 476 <programlisting>  
     
    516 516 <calloutlist>  
    517 517 <callout arearefs="roman.stage2.1.1">  
    518   <para><varname>romanNumeralMap</varname> is a tuple of tuples which defines three things:</para>  
      518 <para><varname>romanNumeralMap</varname> 是一个用来定义三个内容的元组的元组:</para>  
    518 518 <orderedlist>  
    519   <listitem><para>The character representations of the most basic Roman numerals.  Note that this is not just the single-character Roman numerals; you're also defining two-character pairs like <literal>CM</literal> (<quote>one hundred less than one thousand</quote>); this will make the &toroman; code simpler later.</para></listitem>  
    520   <listitem><para>The order of the Roman numerals.  They are listed in descending value order, from <literal>M</literal> all the way down to <literal>I</literal>.</para></listitem>  
    521   <listitem><para>The value of each Roman numeral.  Each inner tuple is a pair of <literal>(<replaceable>numeral</replaceable>, <replaceable>value</replaceable>)</literal>.</para></listitem>  
      519 <listitem><para>大部分罗马数字字符。 注意不只是罗马数字的单字符,你同样在这里定义诸如 <literal>CM</literal>  (<quote>比一千少一百</quote>)的双字符,这可以另稍后编写的 &toroman; 简单一些。</para></listitem>  
      520 <listitem><para>罗马数字的顺序。他们是以降序排列的,从<literal>M</literal> 一路到 <literal>I</literal>。</para></listitem>  
      521 <listitem><para>每个罗马数字所对应的数值。 每个内部的元组都是一个 <literal>(<replaceable>numeral</replaceable>, <replaceable>value</replaceable>)</literal>数值对。</para></listitem>  
    522 522 </orderedlist>  
    523 523 </callout>  
    524 524 <callout arearefs="roman.stage2.1.2">  
    525   <para>Here's where your rich data structure pays off, because you don't need any special logic to handle the subtraction rule.  To convert to Roman numerals, you simply iterate through <varname>romanNumeralMap</varname> looking for the largest integer value less than or equal to the input.  Once found, you add the Roman numeral representation to the end of the output, subtract the corresponding integer value from the input, lather, rinse, repeat.</para>  
      525 <para>这里便显示出你丰富的数据结构带来的优势,你不必处理特定的逻辑减法规则。你只需要通过搜寻 <varname>romanNumeralMap</varname> 寻找不大于输入数值的最大对应整数即可。 一旦找到,就在结果的结尾把这个整数对应的罗马字符添加到输出结果的末尾,从输入值中减去这个整数,一遍遍这样继续下去。</para>  
    525 525 </callout>  
    526 526 </calloutlist>  
    527 527 </example>  
    528 528 <example>  
    529   <title>How &toroman; works</title>  
    530   <para>If you're not clear how &toroman; works, add a <function>print</function> statement to the end of the <literal>while</literal> loop:</para>  
      529 <title>&toroman; 如何工作</title>  
      530 <para>如果你不明了 &toroman; 如何工作,在 <literal>while</literal> 循环的结尾添加一个 <function>print</function> 语句:</para>  
    531 531 <programlisting>  
    532 532         while n >= integer:  
     
    547 547 </screen>  
    548 548 </example>  
    549   <para>So &toroman; appears to work, at least in this manual spot check.  But will it pass the unit testing?  Well no, not entirely.</para>  
      549 <para>看来 &toroman; 可以运转了,至少手工测试可以。 但能通过单元测试吗?啊哈,不,不完全可以。</para>  
    549 549 <example>  
    550   <title>Output of &romantest2_filename; against &roman2_filename;</title>  
    551   <para>Remember to run &romantest2_filename; with the <literal>-v</literal> command-line flag to enable verbose mode.</para>  
      550 <title>以 &romantest2_filename; 测试 &roman2_filename; 的输出</title>  
      551 <para>要记得运行 &romantest2_filename; 带有 <literal>-v</literal> 选项的命令行开启详细信息模式。</para>  
    552 552 <screen><computeroutput>fromRoman should only accept uppercase input ... FAIL  
    553 553 toRoman should always return uppercase ... ok                  </computeroutput><co id="roman.stage2.2.1"/><computeroutput>  
     
    565 565 <calloutlist>  
    566 566 <callout arearefs="roman.stage2.2.1">  
    567   <para>&toroman; does, in fact, always return uppercase, because <varname>romanNumeralMap</varname> defines the Roman numeral representations as uppercase.  So this test passes already.</para>  
      567 <para>&toroman; 通过,事实上所有返回值都是大写的,因为 <varname>romanNumeralMap</varname> 定义的罗马字符都是以大写字母表示的。 因此这个测试已经通过了。</para>  
    567 567 </callout>  
    568 568 <callout arearefs="roman.stage2.2.2">  
    569   <para>Here's the big news: this version of the &toroman; function passes the <link linkend="roman.testtoromanknownvalues.example">known values test</link>.  Remember, it's not comprehensive, but it does put the function through its paces with a variety of good inputs, including inputs that produce every single-character Roman numeral, the largest possible input (<literal>3999</literal>), and the input that produces the longest possible Roman numeral (<literal>3888</literal>).  At this point, you can be reasonably confident that the function works for any good input value you could throw at it.</para>  
      569 <para>这有个大新闻:这个版本的 &toroman; 函数能够通过 <link linkend="roman.testtoromanknownvalues.example">已知值测试</link>。 记住,这并不能证明完全没问题,但至少已经通过了大量的测试:包括生成单一罗马数字字符,最大可能输入(<literal>3999</literal>),以及最长的罗马数字表示(<literal>3888</literal> 对应的) 。从这点来看,你可以有理由自信的人为这个函数对于任何有效输入不会出问题。</para>  
    569 569 </callout>  
    570 570 <callout arearefs="roman.stage2.2.3">  
    571   <para>However, the function does not <quote>work</quote> for bad values; it fails every single <link linkend="roman.tobadinput.example">bad input test</link>.  That makes sense, because you didn't include any checks for bad input.  Those test cases look for specific exceptions to be raised (via &assertRaises;), and you're never raising them.  You'll do that in the next stage.</para>  
      571 <para>但是,函数对无效输入仍然不 <quote>起作用</quote>,每个 <link linkend="roman.tobadinput.example">无效输入测试</link> 都失败。这可以理解,你还没有对无效输入进行检查,独立测试希望捕捉到特定的异常(通过 &assertRaises;),而你根本没有让这些异常引发。 这是你下一步的工作。</para>  
    571 571 </callout>  
    572 572 </calloutlist>  
    573   <para>Here's the rest of the output of the unit test, listing the details of all the failures.  You're down to 10.</para>  
      573 <para>这是单元测试结果的剩余部分,列出了所有的失败,你已经让它降到了10个</para>  
    573 573 <screen><computeroutput>  
    574 574 ======================================================================  
     
    674 674 <section id="roman.stage3">  
    675 675 <?dbhtml filename="unit_testing/stage_3.html"?>  
    676   <title>&roman_filename;, stage 3</title>  
      676 <title>&roman_filename;, 第 3 步</title>  
    676 676 <abstract>  
    677 677 <title/>  
    678   <para>Now that &toroman; behaves correctly with good input (integers from <literal>1</literal> to <literal>3999</literal>), it's time to make it behave correctly with bad input (everything else).</para>  
      678 <para>现在 &toroman; 对于有效的输入(<literal>1</literal> 到 <literal>3999</literal> 整数)已能正确工作,是正确处理那些无效输入(任何其他输入)的时候了。</para>  
    678 678 </abstract>  
    679 679 <example>  
    680 680 <title>&roman3_filename;</title>  
    681   <para>This file is available in <filename>py/roman/stage3/</filename> in the examples directory.</para>  
      681 <para>这个文件可以从 <filename>py/roman/stage3/</filename> 的 examples 目录中获得。</para>  
    681 681 &para_download;  
    682 682 <programlisting>  
     
    727 727 <calloutlist>  
    728 728 <callout arearefs="roman.stage3.1.1">  
    729   <para>This is a nice &python;ic shortcut: multiple comparisons at once.  This is equivalent to <literal>if not ((0 &lt; n) and (n &lt; 4000))</literal>, but it's much easier to read.  This is the range check, and it should catch inputs that are too large, negative, or zero.</para>  
      729 <para>这是 &python; 的一个很棒的缩写:多重比较可以写在一起。 这等价于<literal>if not ((0 &lt; n) and (n &lt; 4000))</literal>, 但是读起来简单了很多。这是一个范围检查,可以将过大的数,负数和零查出来。</para>  
    729 729 </callout>  
    730 730 <callout arearefs="roman.stage3.1.2">  
    731   <para>You raise exceptions yourself with the <literal>raise</literal> statement.  You can raise any of the built-in exceptions, or you can raise any of your custom exceptions that you've defined.  The second parameter, the error message, is optional; if given, it is displayed in the traceback that is printed if the exception is never handled.</para>  
      731 <para>你使用 <literal>raise</literal> 语句引发自己的异常。 你可以引发任何内建异常或者已定义的自定义异常。第二个参数是可选的,如果给定,则在异常未被处理时显示于追踪之中。</para>  
    731 731 </callout>  
    732 732 <callout arearefs="roman.stage3.1.3">  
    733   <para>This is the non-integer check.  Non-integers can not be converted to Roman numerals.</para>  
      733 <para>这是一个非整数检查。非整数无法转化为罗马数字表示。</para>  
    733 733 </callout>  
    734 734 <callout arearefs="roman.stage3.1.4">  
    735   <para>The rest of the function is unchanged.</para>  
      735 <para>函数的其他部分未被更改。</para>  
    735 735 </callout>  
    736 736 </calloutlist>  
    737 737 </example>  
    738 738 <example>  
    739   <title>Watching &toroman; handle bad input</title>  
      739 <title>查看 &toroman; 处理无效输入</title>  
    739 739 <screen>  
    740 740 &prompt;<userinput>import roman3</userinput>  
     
    759 759 </example>  
    760 760 <example>  
    761   <title>Output of &romantest3_filename; against &roman3_filename;</title>  
      761 <title>以 &romantest3_filename; 测试 &roman3_filename; 的输出</title>  
    761 761 <screen><computeroutput>fromRoman should only accept uppercase input ... FAIL  
    762 762 toRoman should always return uppercase ... ok  
     
    774 774 <calloutlist>  
    775 775 <callout arearefs="roman.stage3.2.1">  
    776   <para>&toroman; still passes the <link linkend="roman.testtoromanknownvalues.example">known values test</link>, which is comforting.  All the tests that passed in <link linkend="roman.stage2">stage 2</link> still pass, so the latest code hasn't broken anything.</para>  
      776 <para>&toroman; 仍然能通过 <link linkend="roman.testtoromanknownvalues.example">已知值测试</link>,这令人很舒服。 所有 <link linkend="roman.stage2">第 2 步</link> 通过的测试都通过了,也就是说新的代码没有对原有代码构成任何负面影响。</para>  
    776 776 </callout>  
    777 777 <callout arearefs="roman.stage3.2.2">  
    778   <para>More exciting is the fact that all of the <link linkend="roman.tobadinput.example">bad input tests</link> now pass.  This test, <function>testNonInteger</function>, passes because of the <literal>int(n) &lt;> n</literal> check.  When a non-integer is passed to &toroman;, the <literal>int(n) &lt;> n</literal> check notices it and raises the <errorcode>NotIntegerError</errorcode> exception, which is what <function>testNonInteger</function> is looking for.</para>  
      778 <para>更令人振奋的是所有的 <link linkend="roman.tobadinput.example">无效输入测试</link> 现在都通过了。 测试 <function>testNonInteger</function> 能够通过是因为有了 <literal>int(n) &lt;> n</literal> 检查。 当一个非整数传递给 &toroman;, <literal>int(n) &lt;> n</literal> 检查出问题并引发 <errorcode>NotIntegerError</errorcode> 异常,这正是 <function>testNonInteger</function> 所期待的。</para>  
    778 778 </callout>  
    779 779 <callout arearefs="roman.stage3.2.3">  
    780   <para>This test, <function>testNegative</function>, passes because of the <literal>not (0 &lt; n &lt; 4000)</literal> check, which raises an <errorcode>OutOfRangeError</errorcode> exception, which is what <function>testNegative</function> is looking for.</para>  
      780 <para>测试 <function>testNegative</function> 能够通过适应为,当 <literal>not (0 &lt; n &lt; 4000)</literal> 检查引发了 <function>testNegative</function> 期待的 <errorcode>OutOfRangeError</errorcode> 异常。</para>  
    780 780 </callout>  
    781 781 </calloutlist>  
     
    844 844 <calloutlist>  
    845 845 <callout arearefs="roman.stage3.3.1">  
    846   <para>You're down to 6 failures, and all of them involve &fromroman;: the known values test, the three separate bad input tests, the case check, and the sanity check.  That means that &toroman; has passed all the tests it can pass by itself.  (It's involved in the sanity check, but that also requires that &fromroman; be written, which it isn't yet.)  Which means that you must stop coding &toroman; now.  No tweaking, no twiddling, no extra checks <quote>just in case</quote>.  Stop.  Now.  Back away from the keyboard.</para>  
      846 <para>你已将失败降至6个,而且他们都是关于 &fromroman; 的:已知值测试,三种不同的无效输入测试,大小写检查和回旋检查。这意味着 &toroman; 通过了所有可以独立通过的测试(回旋测试也测试它,但需要 &fromroman; 编写后一起测试)。 这就是说,你应该停止对 &toroman; 的代码编写。不必再推敲,不必再做额外的检查 <quote>恰到好处</quote>。 停下来吧! 现在,别再敲键盘了。</para>  
    846 846 </callout>  
    847 847 </calloutlist>  
    848 848 </example>  
    849 849 <note>  
    850   <title>Know when to stop coding</title>  
    851   <para>The most important thing that comprehensive unit testing can tell you is when to stop coding.  When all the unit tests for a function pass, stop coding the function.  When all the unit tests for an entire module pass, stop coding the module.</para>  
      850 <title>知道什么是后停止编写代码</title>  
      851 <para>全面的单元测试能够告诉你什么时候停止编写代码。当一个函数的所有单元测试都通过了,停止编写这个函数。一旦一个完整模块的单元测试通过了,停止编写这个模块。</para>  
    852 852 </note>  
    853 853 </section>  
    854 854 <section id="roman.stage4">  
    855 855 <?dbhtml filename="unit_testing/stage_4.html"?>  
    856   <title>&roman_filename;, stage 4</title>  
      856 <title>&roman_filename;, 第 4 步</title>  
    856 856 <abstract>  
    857 857 <title/>  
    858   <para>Now that &toroman; is done, it's time to start coding &fromroman;.  Thanks to the rich data structure that maps individual Roman numerals to integer values, this is no more difficult than the &toroman; function.</para>  
      858 <para>现在 &toroman; 完成了,是开始编写 &fromroman; 的时候了。感谢将逐个罗马数字和对应整数关连的完美数据结构,这个工作不比 &toroman; 函数复杂。</para>  
    858 858 </abstract>  
    859 859 <example>  
    860 860 <title>&roman4_filename;</title>  
    861   <para>This file is available in <filename>py/roman/stage4/</filename> in the examples directory.</para>  
      861 <para>这个文件可以从 <filename>py/roman/stage4/</filename> 的 examples 目录中获得</para>  
    861 861 &para_download;  
    862 862 <programlisting>  
     
    902 902 <calloutlist>  
    903 903 <callout arearefs="roman.stage4.1.1">  
    904   <para>The pattern here is the same as <link linkend="roman.stage2.example">&toroman;</link>.  You iterate through your Roman numeral data structure (a tuple of tuples), and instead of matching the highest integer values as often as possible, you match the <quote>highest</quote> Roman numeral character strings as often as possible.</para>  
      904 <para>这和 <link linkend="roman.stage2.example">&toroman;</link> 的工作模式很相像。你遍历整个罗马数字数据结构(一个元组的元组),与前面不同的是不去一个个搜寻最大的整数,而是搜寻 <quote>最大的</quote>罗马数字字符串。</para>  
    904 904 </callout>  
    905 905 </calloutlist>  
    906 906 </example>  
    907 907 <example>  
    908   <title>How &fromroman; works</title>  
    909   <para>If you're not clear how &fromroman; works, add a <function>print</function> statement to the end of the <literal>while</literal> loop:</para>  
      908 <title>&fromroman; 如何工作</title>  
      909 <para>如果你不清楚 &fromroman; 如何工作,在 <literal>while</literal> 结尾处添加一个 <function>print</function> 语句</para>  
    910 910 <programlisting>  
    911 911         while s[index:index+len(numeral)] == numeral:  
     
    927 927 </example>  
    928 928 <example>  
    929   <title>Output of &romantest4_filename; against &roman4_filename;</title>  
      929 <title>以 &romantest4_filename; 测试 &roman4_filename; 的输出</title>  
    929 929 <screen><computeroutput>fromRoman should only accept uppercase input ... FAIL  
    930 930 toRoman should always return uppercase ... ok  
     
    942 942 <calloutlist>  
    943 943 <callout arearefs="roman.stage4.2.1">  
    944   <para>Two pieces of exciting news here.  The first is that &fromroman; works for good input, at least for all the <link linkend="roman.testtoromanknownvalues.example">known values</link> you test.</para>  
      944 <para>这儿有两个激动的消息。 一个是 &fromroman; 对于所有有效输入运转正常,至少对于你测试的 <link linkend="roman.testtoromanknownvalues.example">已知值</link> 是这样。</para>  
    944 944 </callout>  
    945 945 <callout arearefs="roman.stage4.2.2">  
    946   <para>The second is that the <link linkend="roman.sanity.example">sanity check</link> also passed.  Combined with the known values tests, you can be reasonably sure that both &toroman; and &fromroman; work properly for all possible good values.  (This is not guaranteed; it is theoretically possible that &toroman; has a bug that produces the wrong Roman numeral for some particular set of inputs, <emphasis>and</emphasis> that &fromroman; has a reciprocal bug that produces the same wrong integer values for exactly that set of Roman numerals that &toroman; generated incorrectly.  Depending on your application and your requirements, this possibility may bother you; if so, write more comprehensive test cases until it doesn't bother you.)</para>  
      946 <para>第二个好消息是, <link linkend="roman.sanity.example">回旋测试</link>也通过了。 与已知值测试的通过一起来看,你有理由相信 &toroman; 和 &fromroman; 对于所有有效输入值工作正常( 这并不是完全肯定的,理论上讲 &toroman; 存在错误而导致一些特定输入产生错误罗马数字表示 <emphasis>和</emphasis>  &fromroman; 存在相应的错误,把特定 &toroman; 错误产生的这些罗马数字错误地转换为最初的整数。 取决于你的应用程序和你的要求,你或许需要考虑这个可能性。如果是这样,编写更全面的独立测试直到解决这个问题)。</para>  
    946 946 </callout>  
    947 947 </calloutlist>  
     
    993 993 <section id="roman.stage5">  
    994 994 <?dbhtml filename="unit_testing/stage_5.html"?>  
    995   <title>&roman_filename;, stage 5</title>  
      995 <title>&roman_filename;, 第 5 步</title>  
    995 995 <abstract>  
    996 996 <title/>  
    997   <para>Now that &fromroman; works properly with good input, it's time to fit in the last piece of the puzzle: making it work properly with bad input.  That means finding a way to look at a string and determine if it's a valid Roman numeral.  This is inherently more difficult than <link linkend="roman.stage3">validating numeric input</link> in &toroman;, but you have a powerful tool at your disposal: regular expressions.</para>  
      997 <para>先在 &fromroman; 对于有效输入能够正常工作,是揭开最后一个谜底的时候了:无效输入的情形下的正常运转。这意味着要找出一个方法查看输入字符串并确定它是不是一个有效的罗马数字。这比 &toroman; 中<link linkend="roman.stage3">有效数字输入的查看</link> 困难, 但是你可以使用一个强大的工具:正则表达式。</para>  
    997 997 </abstract>  
    998   <para>If you're not familiar with regular expressions and didn't read <xref linkend="re"/>, now would be a good time.</para>  
    999   <para>As you saw in <xref linkend="re.roman"/>, there are several simple rules for constructing a Roman numeral, using the letters <literal>M</literal>, <literal>D</literal>, <literal>C</literal>, <literal>L</literal>, <literal>X</literal>, <literal>V</literal>, and <literal>I</literal>.  Let's review the rules:</para>  
      998 <para>如果你不熟悉正则表达式,并且没有读过 <xref linkend="re"/>,现在是该好好读读的时候了。</para>  
      999 <para>如你在 <xref linkend="re.roman"/>中所见到的,有很多构建罗马数则的简单方法:使用字母 <literal>M</literal>, <literal>D</literal>, <literal>C</literal>, <literal>L</literal>, <literal>X</literal>, <literal>V</literal>,和 <literal>I</literal>。让我们回顾一下这些规则:</para>  
    1000 1000 <orderedlist>  
    1001   <listitem><para>Characters are additive.  <literal>I</literal> is &one;, <literal>II</literal> is <literal>2</literal>, and <literal>III</literal> is <literal>3</literal>.  <literal>VI</literal> is <literal>6</literal> (literally, <quote><literal>5</literal> and <literal>1</literal></quote>), <literal>VII</literal> is <literal>7</literal>, and <literal>VIII</literal> is <literal>8</literal>.</para></listitem>  
    1002   <listitem><para>The tens characters (<literal>I</literal>, <literal>X</literal>, <literal>C</literal>, and <literal>M</literal>) can be repeated up to three times.  At <literal>4</literal>, you need to subtract from the next highest fives character.  You can't represent <literal>4</literal> as <literal>IIII</literal>; instead, it is represented as <literal>IV</literal> (<quote><literal>1</literal> less than <literal>5</literal></quote>).  <literal>40</literal> is written as <literal>XL</literal> (<quote><literal>10</literal> less than <literal>50</literal></quote>), <literal>41</literal> as <literal>XLI</literal>, <literal>42</literal> as <literal>XLII</literal>, <literal>43</literal> as <literal>XLIII</literal>, and then <literal>44</literal> as <literal>XLIV</literal> (<quote><literal>10</literal> less than <literal>50</literal>, then <literal>1</literal> less than <literal>5</literal></quote>).</para></listitem>  
    1003   <listitem><para>Similarly, at <literal>9</literal>, you need to subtract from the next highest tens character: <literal>8</literal> is <literal>VIII</literal>, but <literal>9</literal> is <literal>IX</literal> (<quote><literal>1</literal> less than <literal>10</literal></quote>), not <literal>VIIII</literal> (since the <literal>I</literal> character can not be repeated four times).  <literal>90</literal> is <literal>XC</literal>, <literal>900</literal> is <literal>CM</literal>.</para></listitem>  
    1004   <listitem><para>The fives characters can not be repeated.  <literal>10</literal> is always represented as <literal>X</literal>, never as <literal>VV</literal>.  <literal>100</literal> is always <literal>C</literal>, never <literal>LL</literal>.</para></listitem>  
    1005   <listitem><para>Roman numerals are always written highest to lowest, and read left to right, so order of characters matters very much.  <literal>DC</literal> is <literal>600</literal>; <literal>CD</literal> is a completely different number (<literal>400</literal>, <quote><literal>100</literal> less than <literal>500</literal></quote>).  <literal>CI</literal> is <literal>101</literal>; <literal>IC</literal> is not even a valid Roman numeral (because you can't subtract <literal>1</literal> directly from <literal>100</literal>; you would need to write it as <literal>XCIX</literal>, <quote><literal>10</literal> less than <literal>100</literal>, then <literal>1</literal> less than <literal>10</literal></quote>).</para></listitem>  
      1001 <listitem><para>字符是被堆放在一起的: <literal>I</literal> 是 &one;, <literal>II</literal> 是 <literal>2</literal>, <literal>III</literal> 是 <literal>3</literal>。  <literal>VI</literal> 是 <literal>6</literal> (看上去就是 <quote><literal>5</literal> 和 <literal>1</literal></quote>), <literal>VII</literal> 是 <literal>7</literal>, <literal>VIII</literal> 是 <literal>8</literal>。</para></listitem>  
      1002 <listitem><para>这十个字符( <literal>I</literal>, <literal>X</literal>, <literal>C</literal>, 和 <literal>M</literal>)可以重复最多三次。 对于 <literal>4</literal>,你需要由接下来的五做减法。你不能把 <literal>4</literal> 表示为 <literal>IIII</literal> 而应该表示为 <literal>IV</literal> (<quote>比 <literal>5</literal> 小 <literal>1</literal> </quote>)。  <literal>40</literal> 则被写作 <literal>XL</literal> (<quote>比 <literal>50</literal> 小 <literal>10</literal></quote>), <literal>41</literal> 表示为 <literal>XLI</literal>, <literal>42</literal> 表示为 <literal>XLII</literal>, <literal>43</literal> 表示为 <literal>XLIII</literal>, <literal>44</literal> 表示为 <literal>XLIV</literal> (<quote>比<literal>50</literal>小<literal>10</literal>,又比 <literal>5</literal> 小 <literal>1</literal></quote>).</para></listitem>  
      1003 <listitem><para>如法炮制,<literal>9</literal>需要从十做减法而得:<literal>8</literal> 是 <literal>VIII</literal>,而 <literal>9</literal> 是 <literal>IX</literal> (<quote>比 <literal>10</literal> 小 <literal>1</literal></quote>),而不是 <literal>VIIII</literal> (由于 <literal>I</literal> 不能重复四次)。  <literal>90</literal> 表示为 <literal>XC</literal>, <literal>900</literal> 表示为 <literal>CM</literal>,</para></listitem>  
      1004 <listitem><para>有五个字符不能被重复: <literal>10</literal> 应该表示为 <literal>X</literal>, 而不会是  <literal>VV</literal>。 <literal>100</literal> 应该表示为 <literal>C</literal>,而不是 <literal>LL</literal>.</para></listitem>  
      1005 <listitem><para>罗马数字总是有大到小书写,从左到右读,所以字符的顺序很重要。 <literal>DC</literal> 是 <literal>600</literal>, <literal>CD</literal> 是完全另外一个数 (<literal>400</literal>,<quote>比 <literal>500</literal> 少 <literal>100</literal></quote>)。 <literal>CI</literal> 是 <literal>101</literal>,而 <literal>IC</literal> 根本就不是一个有效的罗马数字(因为你无法从<literal>100</literal>直接减<literal>1</literal>应该写成 <literal>XCIX</literal>, <quote>比 <literal>100</literal> 少 <literal>10</literal>,比 <literal>10</literal> 少 <literal>1</literal></quote>)。</para></listitem>  
    1006 1006 </orderedlist>  
    1007 1007  
    1008 1008 <example>  
    1009 1009 <title>&roman5_filename;</title>  
    1010   <para>This file is available in <filename>py/roman/stage5/</filename> in the examples directory.</para>  
      1010 <para>这个文件可以从<filename>py/roman/stage5/</filename> 的 examples 目录获得</para>  
    1010 1010 &para_download;  
    1011 1011 <programlisting>  
     
    1069 1069 <calloutlist>  
    1070 1070 <callout arearefs="roman.stage5.3.1">  
    1071   <para>This is just a continuation of the pattern you discussed in <xref linkend="re.roman"/>.  The tens places is either <literal>XC</literal> (<literal>90</literal>), <literal>XL</literal> (<literal>40</literal>), or an optional <literal>L</literal> followed by 0 to 3 optional <literal>X</literal> characters.  The ones place is either <literal>IX</literal> (<literal>9</literal>), <literal>IV</literal> (<literal>4</literal>), or an optional <literal>V</literal> followed by 0 to 3 optional <literal>I</literal> characters.</para>  
      1071 <para>这只是在 <xref linkend="re.roman"/> 中讨论的匹配模版的继续。 十位可能是<literal>XC</literal> (<literal>90</literal>), <literal>XL</literal> (<literal>40</literal>),或者是一个可能有的 <literal>L</literal> ,后面跟着 0 到 3 个 <literal>X</literal> 字符。 个位则可能是 <literal>IX</literal> (<literal>9</literal>), <literal>IV</literal> (<literal>4</literal>),或者是一个可能有的<literal>V</literal> ,后面跟着 0 到 3 个 <literal>I</literal> 字符。</para>  
    1071 1071 </callout>  
    1072 1072 <callout arearefs="roman.stage5.3.2">  
    1073   <para>Having encoded all that logic into a regular expression, the code to check for invalid Roman numerals becomes trivial.  If <function>re.search</function> returns an object, then the regular expression matched and the input is valid; otherwise, the input is invalid.</para>  
      1073 <para>把所有的逻辑编码成正则表达式,检查无效罗马字符便轻而易举了。 如果 <function>re.search</function> 返回一个对象则正则表达式检查输入,匹配则是有效的,否则输入无效。</para>  
    1073 1073 </callout>  
    1074 1074 </calloutlist>  
    1075 1075 </example>  
    1076   <para>At this point, you are allowed to be skeptical that that big ugly regular expression could possibly catch all the types of invalid Roman numerals.  But don't take my word for it, look at the results:</para>  
      1076 <para>这里你可能会怀疑这个面目可憎的正则表达式是否真能查出错误的罗马字符表示。没关系,不必完全听我的,不妨看看下面的结果:</para>  
    1076 1076 <example>  
    1077   <title>Output of &romantest5_filename; against &roman5_filename;</title>  
      1077 <title>以 &romantest5_filename; 测试 &roman5_filename; 的输出</title>  
    1077 1077 <screen><computeroutput>  
    1078 1078 fromRoman should only accept uppercase input ... ok          </computeroutput><co id="roman.stage5.4.1"/><computeroutput>  
     
    1099 1099 <calloutlist>  
    1100 1100 <callout arearefs="roman.stage5.4.1">  
    1101   <para>One thing I didn't mention about regular expressions is that, by default, they are case-sensitive.  Since the regular expression <varname>romanNumeralPattern</varname> was expressed in uppercase characters, the <function>re.search</function> check will reject any input that isn't completely uppercase.  So the uppercase input test passes.</para>  
      1101 <para>有件事我未曾讲过,那就是默认情况下正则表达式大小写敏感。 由于正则表达式 <varname>romanNumeralPattern</varname> 是以大写字母构造的,<function>re.search</function> 将拒绝不全部是大写字母构成的输入。 因此大写输入的检查也通过了。</para>  
    1101 1101 </callout>  
    1102 1102 <callout arearefs="roman.stage5.4.2">  
    1103   <para>More importantly, the bad input tests pass.  For instance, the malformed antecedents test checks cases like <literal>MCMC</literal>.  As you've seen, this does not match the regular expression, so &fromroman; raises an <errorcode>InvalidRomanNumeralError</errorcode> exception, which is what the malformed antecedents test case is looking for, so the test passes.</para>  
      1103 <para>更重要的是,无效输入测试通过了。 例如,前面那个独立测试需要查看 <literal>MCMC</literal> 之类的情形。 正如你所见,这不符合正则表达式, 因此 &fromroman; 引发一个独立测试正在等待的 <errorcode>InvalidRomanNumeralError</errorcode> 异常,所以测试通过了。</para>  
    1103 1103 </callout>  
    1104 1104 <callout arearefs="roman.stage5.4.3">  
    1105   <para>In fact, all the bad input tests pass.  This regular expression catches everything you could think of when you made your test cases.</para>  
      1105 <para>事实上,所有的无效输入测试都能通过。 正则表达式捕捉了所有你在编写独立测试时所能预见的所有情况。</para>  
    1105 1105 </callout>  
    1106 1106 <callout arearefs="roman.stage5.4.4">  
    1107   <para>And the anticlimax award of the year goes to the word <quote><literal>OK</literal></quote>, which is printed by the &unittest_module; module when all the tests pass.</para>  
      1107 <para>一步步缩减错误个数最终迎来了 <quote><literal>OK</literal></quote>这个 &unittest_module; 模块在所有测试都通过后打印出来的单词。</para>  
    1107 1107 </callout>  
    1108 1108 </calloutlist>  
    1109 1109 </example>  
    1110 1110 <note>  
    1111   <title>What to do when all of your tests pass</title>  
    1112   <para>When all of your tests pass, stop coding.</para>  
      1111 <title>所有测试都通过后做什么呢?</title>  
      1112 <para>当所有测试都通过了,停止编程。</para>  
    1113 1113 </note>  
    1114 1114 </section>  
     
    1120 1120 <chapter id="roman2">  
    1121 1121 <?dbhtml filename="refactoring/index.html"?>  
    1122   <title>Refactoring</title>  
    1123   <titleabbrev id="roman2.numberonly">Chapter 15</titleabbrev>  
      1122 <title>重构</title>  
      1123 <titleabbrev id="roman2.numberonly">第 15 章</titleabbrev>  
    1124 1124 <section id="roman.bugs">  
    1125 1125 <?dbhtml filename="refactoring/handling_bugs.html"?>  
    1126   <title>Handling bugs</title>  
      1126 <title>bugs 处理</title>  
    1126 1126 <abstract>  
    1127 1127 <title/>  
    1128   <para>Despite your best efforts to write comprehensive unit tests, bugs happen.  What do I mean by <quote>bug</quote>?  A bug is a test case you haven't written yet.</para>  
      1128 <para>尽管你很努力的编写全面的单元测试,但是 bug 还是会出现。 我所说的 <quote>bug</quote> 是什么呢? Bug 是你还没有编写的一个独立测试。</para>  
    1128 1128 </abstract>  
    1129 1129 <example>  
    1130   <title>The bug</title>  
      1130 <title>关于 Bug</title>  
    1130 1130 <screen>&prompt;<userinput>import roman5</userinput>  
    1131 1131 &prompt;<userinput>roman5.fromRoman("")</userinput> <co id="roman.bugs.1.1"/>  
     
    1136 1136 <calloutlist>  
    1137 1137 <callout arearefs="roman.bugs.1.1">  
    1138   <para>Remember in the <link linkend="roman.stage5">previous section</link> when you kept seeing that an empty string would match the regular expression you were using to check for valid Roman numerals?  Well, it turns out that this is still true for the final version of the regular expression.  And that's a bug; you want an empty string to raise an <errorcode>InvalidRomanNumeralError</errorcode> exception just like any other sequence of characters that don't represent a valid Roman numeral.</para>  
      1138 <para>在前面的 <link linkend="roman.stage5">章节中</link> 你看到用以检查罗马数字有效性的正则表达式认为空字符串有效了吗? 对于最终版本中的正则表达式这一点仍然没有改变。这就是一个 Bug ,你希望对于空字符串能够像对待其他无效的罗马数字表示一样引发 <errorcode>InvalidRomanNumeralError</errorcode> 异常。</para>  
    1138 1138 </callout>  
    1139 1139 </calloutlist>  
    1140 1140 </example>  
    1141   <para>After reproducing the bug, and before fixing it, you should write a test case that fails, thus illustrating the bug.</para>  
      1141 <para>在再现 Bug 和去除它之前你应该编写一个失败独立测试来描述这个 Bug。</para>  
    1141 1141 <example>  
    1142   <title>Testing for the bug (&romantest61_filename;)</title>  
      1142 <title>测试 bug (&romantest61_filename;)</title>  
    1142 1142 <programlisting>  
    1143 1143 &romantest_frombadinputdef;  
     
    1154 1154 <calloutlist>  
    1155 1155 <callout arearefs="roman.bugs.2.1">  
    1156   <para>Pretty simple stuff here.  Call &fromroman; with an empty string and make sure it raises an <errorcode>InvalidRomanNumeralError</errorcode> exception.  The hard part was finding the bug; now that you know about it, testing for it is the easy part.</para>  
      1156 <para>这里很简单。以空字符串调用 &fromroman; 并确保引发一个 <errorcode>InvalidRomanNumeralError</errorcode> 异常。 难点在于找出 Bug,现在你知道了它,测试它就很简单了。</para>  
    1156 1156 </callout>  
    1157 1157 </calloutlist>  
    1158 1158 </example>  
    1159   <para>Since your code has a bug, and you now have a test case that tests this bug, the test case will fail:</para>  
      1159 <para>因为你的代码存在一个 Bug,并且你编写了测试这个 Bug 的独立测试,所以独立测试将会失败:</para>  
    1159 1159 <example>  
    1160   <title>Output of &romantest61_filename; against &roman61_filename;</title>  
      1160 <title>以 &romantest61_filename; 测试 &roman61_filename; 的输出</title>  
    1160 1160 <screen><computeroutput>fromRoman should only accept uppercase input ... ok  
    1161 1161 toRoman should always return uppercase ... ok  
     
    1189 1189 FAILED (failures=1)</computeroutput></screen>  
    1190 1190 </example>  
    1191   <para><emphasis>Now</emphasis> you can fix the bug.</para>  
      1191 <para><emphasis>现在</emphasis> 你可以修改这个 Bug了。</para>  
    1191 1191 <example>  
    1192   <title>Fixing the bug (&roman62_filename;)</title>  
    1193   <para>This file is available in <filename>py/roman/stage6/</filename> in the examples directory.</para>  
      1192 <title>修改 Bug (&roman62_filename;)</title>  
      1193 <para>这个文件可以从 <filename>py/roman/stage6/</filename> 的 examples 目录获得。</para>  
    1194 1194 <programlisting>  
    1195 1195 def fromRoman(s):  
     
    1211 1211 <calloutlist>  
    1212 1212 <callout arearefs="roman.bugs.4.1">  
    1213   <para>Only two lines of code are required: an explicit check for an empty string, and a &raise; statement.</para>  
      1213 <para>只需要两行代码:直白的检查空字符串和一条 &raise; 语句。</para>  
    1213 1213 </callout>  
    1214 1214 </calloutlist>  
    1215 1215 </example>  
    1216 1216 <example>  
    1217   <title>Output of &romantest62_filename; against &roman62_filename;</title>  
      1217 <title>以 &romantest62_filename; 测试 &roman62_filename; 的结果</title>  
    1217 1217 <screen><computeroutput>fromRoman should only accept uppercase input ... ok  
    1218 1218 toRoman should always return uppercase ... ok  
     
    1237 1237 <calloutlist>  
    1238 1238 <callout arearefs="roman.bugs.5.1">  
    1239   <para>The blank string test case now passes, so the bug is fixed.</para>  
      1239 <para>空字符串独立测试现在通过了,说明 Bug 被改正了。</para>  
    1239 1239 </callout>  
    1240 1240 <callout arearefs="roman.bugs.5.2">  
    1241   <para>All the other test cases still pass, which means that this bug fix didn't break anything else.  Stop coding.</para>  
      1241 <para>所有其他独立测试依然通过,证明这个 Bug 修正没有影响到其他部分。 停止编程。</para>  
    1241 1241 </callout>  
    1242 1242 </calloutlist>  
    1243 1243 </example>  
    1244   <para>Coding this way does not make fixing bugs any easier.  Simple bugs (like this one) require simple test cases; complex bugs will require complex test cases.  In a testing-centric environment, it may <emphasis>seem</emphasis> like it takes longer to fix a bug, since you need to articulate in code exactly what the bug is (to write the test case), then fix the bug itself.  Then if the test case doesn't pass right away, you need to figure out whether the fix was wrong, or whether the test case itself has a bug in it.  However, in the long run, this back-and-forth between test code and code tested pays for itself, because it makes it more likely that bugs are fixed correctly the first time.  Also, since you can easily re-run <emphasis>all</emphasis> the test cases along with your new one, you are much less likely to break old code when fixing new code.  Today's unit test is tomorrow's regression test.</para>  
      1244 <para> 这样编程,并没有令 Bug 修正变得简单。 简单的 Bug (就像这一个)需要简单的独立测试,复杂 Bug 则需要复杂的独立测试。 在测试为核心的氛围下,这 <emphasis>看起来</emphasis> 似乎延长了修正 Bug 的时间,因为你需要先贴切地描述出 Bug (编写独立测试)然后才去修正它。 如果独立测试没能正确通过,你需要思量这个修改错了还是独立测试本身出现了 Bug。无论如何,从长远上讲,这样在测试代码和代码之间的反复是值得的,因为通常 Bug 在第一次就被修正过来了。 当然,由于任何新的更改后你都可以轻易的重新运行 <emphasis>所有</emphasis> 独立测试,新代码对老代码产生负面影响的机会也变得微乎其微。 今天的单元测试就是明天的衰退测试(regression test)。</para>  
    1244 1244 </section>  
    1245 1245 <section id="roman.change">  
    1246 1246 <?dbhtml filename="refactoring/handling_changing_requirements.html"?>  
    1247   <title>Handling changing requirements</title>  
      1247 <title>应对要求变化</title>  
    1247 1247 <abstract>  
    1248 1248 <title/>  
    1249   <para>Despite your best efforts to pin your customers to the ground and extract exact requirements from them on pain of horrible nasty things involving scissors and hot wax, requirements will change.  Most customers don't know what they want until they see it, and even if they do, they aren't that good at articulating what they want precisely enough to be useful.  And even if they do, they'll want more in the next release anyway.  So be prepared to update your test cases as requirements change.</para>  
      1249 <para>尽管你竭尽努力地分析你的客户需求,并点灯熬油地提炼出相应的要求,但要求是变化的。 大部分客户在看到产品前不知道他们要什么。即便知道,也不能很好地精确表述出他们的有效需求。即便能表述出来,他们下一次一定要更多的功能。 因此你需要让你的独立测试做好更新准备以应对要求的改变。</para>  
    1249 1249 </abstract>  
    1250   <para>Suppose, for instance, that you wanted to expand the range of the Roman numeral conversion functions.  Remember <link linkend="roman.divein">the rule</link> that said that no character could be repeated more than three times?  Well, the Romans were willing to make an exception to that rule by having 4 <literal>M</literal> characters in a row to represent <literal>4000</literal>.  If you make this change, you'll be able to expand the range of convertible numbers from <literal>1..3999</literal> to <literal>1..4999</literal>.  But first, you need to make some changes to the test cases.</para>  
      1250 <para>假设你想要扩展罗马数字转换的范围。记住 <link linkend="roman.divein">这条原则</link>:没有哪个字符可以重复三遍以上? 啊哈!罗马数字可以连续出现 4 次<literal>M</literal> 字符来表示 <literal>4000</literal> 便是一个例外。 如果你做出这个改变,你则可以把转换范围从 <literal>1..3999</literal> 扩展到 <literal>1..4999</literal>。 但首先,你先要对独立测试进行修改。</para>  
    1250 1250 <example>  
    1251   <title>Modifying test cases for new requirements (&romantest71_filename;)</title>  
    1252   <para>This file is available in <filename>py/roman/stage7/</filename> in the examples directory.</para>  
      1251 <title>修改独立测试以适应新要求 (&romantest71_filename;)</title>  
      1252 <para>该文件可以从 <filename>py/roman/stage7/</filename> 的 examples 目录获得。</para>  
    1253 1253 &para_download;  
    1254 1254 <programlisting>  
     
    1402 1402 <calloutlist>  
    1403 1403 <callout arearefs="roman.change.1.1">  
    1404   <para>The existing known values don't change (they're all still reasonable values to test), but you need to add a few more in the <literal>4000</literal> range.  Here I've included <literal>4000</literal> (the shortest), <literal>4500</literal> (the second shortest), <literal>4888</literal> (the longest), and <literal>4999</literal> (the largest).</para>  
      1404 <para>原来的已知值没有改变(它们仍然是合理的测试值)但你需要添加几个大于 <literal>4000</literal> 的值。 这里我添加了 <literal>4000</literal> (罗马数字表示最短的一个), <literal>4500</literal> (次短的一个), <literal>4888</literal> (最长的一个)和 <literal>4999</literal> (值最大的一个)。</para>  
    1404 1404 </callout>  
    1405 1405 <callout arearefs="roman.change.1.2">  
    1406   <para>The definition of <quote>large input</quote> has changed.  This test used to call &toroman; with <literal>4000</literal> and expect an error; now that <literal>4000-4999</literal> are good values, you need to bump this up to <literal>5000</literal>.</para>  
      1406 <para><quote>最大输入</quote>的概念改变了。 以前是以 <literal>4000</literal> 调用 &toroman; 并期待一个错误;而现在 <literal>4000-4999</literal> 成为了有效输入,需要将这个测试的对象提升至 <literal>5000</literal>。</para>  
    1406 1406 </callout>  
    1407 1407 <callout arearefs="roman.change.1.3">  
    1408   <para>The definition of <quote>too many repeated numerals</quote> has also changed.  This test used to call &fromroman; with <literal>'MMMM'</literal> and expect an error; now that <literal>MMMM</literal> is considered a valid Roman numeral, you need to bump this up to <literal>'MMMMM'</literal>.</para>  
      1408 <para><quote>过多字符重复</quote> 的概念也改变了。 这个测试以前是以 <literal>'MMMM'</literal> 调用并期待一个错误;而现在 <literal>MMMM</literal> 被认为是一个有效的罗马数字表示,需要将这个测试的对象提升至 <literal>'MMMMM'</literal>。</para>  
    1408 1408 </callout>  
    1409 1409 <callout arearefs="roman.change.1.4">  
    1410   <para>The sanity check and case checks loop through every number in the range, from &one; to <literal>3999</literal>.  Since the range has now expanded, these &for; loops need to be updated as well to go up to <literal>4999</literal>.</para>  
      1410 <para>回旋测试和大小写测试在 &one; 到 <literal>3999</literal> 范围内循环。由于现在范围扩展了,这个 &for; 循环需要将范围提升至 <literal>4999</literal>。</para>  
    1410 1410 </callout>  
    1411 1411 </calloutlist>  
    1412 1412 </example>  
    1413   <para>Now your test cases are up to date with the new requirements, but your code is not, so you expect several of the test cases to fail.</para>  
      1413 <para>现在你的独立测试已经根据要求完成了更新, 但是你的程序代码还没有,因此几个独立测试的失败是意料之中的事。</para>  
    1413 1413 <example>  
    1414   <title>Output of &romantest71_filename; against &roman71_filename;</title>  
      1414 <title>以 &romantest71_filename; 测试 &roman71_filename; 的输出</title>  
    1414 1414 <screen><computeroutput>  
    1415 1415 fromRoman should only accept uppercase input ... ERROR        </computeroutput><co id="roman.change.2.1"/><computeroutput>  
     
    1435 1435 <calloutlist>  
    1436 1436 <callout arearefs="roman.change.2.1">  
    1437   <para>Our case checks now fail because they loop from &one; to <literal>4999</literal>, but &toroman; only accepts numbers from &one; to <literal>3999</literal>, so it will fail as soon the test case hits <literal>4000</literal>.</para>  
      1437 <para>我们的独立测试失败是因循环范围是 &one; 到 <literal>4999</literal> 而导致,但是 &toroman; 只接受 &one; 到 <literal>3999</literal>的范围,因此测试到达 <literal>4000</literal> 就会失败。</para>  
    1437 1437 </callout>  
    1438 1438 <callout arearefs="roman.change.2.2">  
    1439   <para>The &fromroman; known values test will fail as soon as it hits <literal>'MMMM'</literal>, because &fromroman; still thinks this is an invalid Roman numeral.</para>  
      1439 <para>&fromroman; 的已知值测试在遇到 <literal>'MMMM'</literal> 就会失败,因为 &fromroman; 还认为这是一个无效的罗马数字表示。</para>  
    1439 1439 </callout>  
    1440 1440 <callout arearefs="roman.change.2.3">  
    1441   <para>The &toroman; known values test will fail as soon as it hits <literal>4000</literal>, because &toroman; still thinks this is out of range.</para>  
      1441 <para>&toroman; 的已知值测试在遇到 <literal>4000</literal> 就会失败,因为 &toroman; 仍旧认为这越出了有效值范围。</para>  
    1441 1441 </callout>  
    1442 1442 <callout arearefs="roman.change.2.4">  
    1443   <para>The sanity check will also fail as soon as it hits <literal>4000</literal>, because &toroman; still thinks this is out of range.</para>  
      1443 <para>回旋测试在遇到 <literal>4000</literal> 便失败,因为 &toroman; 还认为这越出了有效值范围。</para>  
    1443 1443 </callout>  
    1444 1444 </calloutlist>  
     
    1498 1498 FAILED (errors=5)</computeroutput></screen>  
    1499 1499 </example>  
    1500   <para>Now that you have test cases that fail due to the new requirements, you can think about fixing the code to bring it in line with the test cases.  (One thing that takes some getting used to when you first start coding unit tests is that the code being tested is never <quote>ahead</quote> of the test cases.  While it's behind, you still have some work to do, and as soon as it catches up to the test cases, you stop coding.)</para>  
      1500 <para>现在你遭遇了要求改变带来的独立测试失败,你可以考虑修改代码使它再能通过独立测试。(在引入单元测试之后的一个必然现象是:被测试代码永远不会走在独立测试<quote>之前</quote>。正因为如此,你还有一些工作要做,直到它赶上独立测试才停下来。)</para>  
    1500 1500 <example>  
    1501   <title>Coding the new requirements (&roman72_filename;)</title>  
    1502   <para>This file is available in <filename>py/roman/stage7/</filename> in the examples directory.</para>  
      1501 <title>根据新要求修改代码 (&roman72_filename;)</title>  
      1502 <para>这个文件可以从 <filename>py/roman/stage7/</filename> 的 examples 目录获得。</para>  
    1503 1503 <programlisting>  
    1504 1504 """Convert to and from Roman numerals"""  
     
    1562 1562 <calloutlist>  
    1563 1563 <callout arearefs="roman.change.3.1">  
    1564   <para>&toroman; only needs one small change, in the range check.  Where you used to check <literal>0 &lt; n &lt; 4000</literal>, you now check <literal>0 &lt; n &lt; 5000</literal>.  And you change the error message that you &raise; to reflect the new acceptable range (<literal>1..4999</literal> instead of <literal>1..3999</literal>).  You don't need to make any changes to the rest of the function; it handles the new cases already.  (It merrily adds <literal>'M'</literal> for each thousand that it finds; given <literal>4000</literal>, it will spit out <literal>'MMMM'</literal>.  The only reason it didn't do this before is that you explicitly stopped it with the range check.)</para>  
      1564 <para>&toroman; 只需要在取值范围检查一处做出微小修改。将原来的 <literal>0 &lt; n &lt; 4000</literal>,更改为现在的检查 <literal>0 &lt; n &lt; 5000</literal>。 你还要更改你 &raise; 的错误信息以反映接受新取值范围(<literal>1..4999</literal> 而不再是 <literal>1..3999</literal>)。 你不需要改变函数的其他部分,它们已经适用于新的变化。(它们会欣然地为新的一千添加<literal>'M'</literal>,以 <literal>4000</literal>为例,他们会返回 <literal>'MMMM'</literal> )之前没能这样做是因为范围检查时就被停了下来。)</para>  
    1564 1564 </callout>  
    1565 1565 <callout arearefs="roman.change.3.2">  
    1566   <para>You don't need to make any changes to &fromroman; at all.  The only change is to <varname>romanNumeralPattern</varname>; if you look closely, you'll notice that you added another optional <literal>M</literal> in the first section of the regular expression.  This will allow up to 4 <literal>M</literal> characters instead of 3, meaning you will allow the Roman numeral equivalents of <literal>4999</literal> instead of <literal>3999</literal>.  The actual &fromroman; function is completely general; it just looks for repeated Roman numeral characters and adds them up, without caring how many times they repeat.  The only reason it didn't handle <literal>'MMMM'</literal> before is that you explicitly stopped it with the regular expression pattern matching.</para>  
      1566 <para>你对 &fromroman; 也不需要做过多的修改。 唯一的修改就在 <varname>romanNumeralPattern</varname>:如果你注意的话,你会发现你只需在正则表达式的第一部分增加一个可选的 <literal>M</literal> 。 可以允许最多 4 个 <literal>M</literal> 字符而不再是 3 个,这意味着你允许的罗马数字可以到相当于 <literal>4999</literal> 而不再是 <literal>3999</literal>。 &fromroman; 函数本身是普遍适用的,它并不在意字符被多少次的重复,只是根据重复的罗马字符对应的数值进行累加。 以前没能处理 <literal>'MMMM'</literal> 是因为你通过正则表达式的检查强行停止了。</para>  
    1566 1566 </callout>  
    1567 1567 </calloutlist>  
    1568   <para>You may be skeptical that these two small changes are all that you need.  Hey, don't take my word for it; see for yourself:</para>  
      1568 <para>你可能会怀疑两条的微小改变竟是你需要做的一切。不必相信我,你自己看看吧:</para>  
    1568 1568 <example id="roman.roman72.output">  
    1569 1569 <title>Output of &romantest72_filename; against &roman72_filename;</title>  
     
    1591 1591 <calloutlist>  
    1592 1592 <callout arearefs="roman.change.4.1">  
    1593   <para>All the test cases pass.  Stop coding.</para>  
      1593 <para>通过了所有的独立测试,停止代码编写。</para>  
    1593 1593 </callout>  
    1594 1594 </calloutlist>  
    1595 1595 </example>  
    1596   <para>Comprehensive unit testing means never having to rely on a programmer who says <quote>Trust me.</quote></para>  
      1596 <para>全面地单元测试意味着不必依赖于程序员的一面之词: <quote>相信我!</quote></para>  
    1596 1596 </section>  
    1597 1597 <section id="roman.refactoring">  
    1598 1598 <?dbhtml filename="refactoring/refactoring.html"?>  
    1599   <title>Refactoring</title>  
      1599 <title>重组</title>  
    1599 1599 <abstract>  
    1600 1600 <title/>  
    1601   <para>The best thing about comprehensive unit testing is not the feeling you get when all your test cases finally pass, or even the feeling you get when someone else blames you for breaking their code and you can actually <emphasis>prove</emphasis> that you didn't.  The best thing about unit testing is that it gives you the freedom to refactor mercilessly.</para>  
      1601 <para>完备的单元测试带来的最大好处并不是在你的全部独立测试都通过之时;也不在受到责怪时,<emphasis>证明</emphasis>你没有扰乱别人的代码。最大的好处是单元测试给了你很酷的重构自由。</para>  
    1601 1601 </abstract>  
    1602   <para>Refactoring is the process of taking working code and making it work better.  Usually, <quote>better</quote> means <quote>faster</quote>, although it can also mean <quote>using less memory</quote>, or <quote>using less disk space</quote>, or simply <quote>more elegantly</quote>.  Whatever it means to you, to your project, in your environment, refactoring is important to the long-term health of any program.</para>  
    1603   <para>Here, <quote>better</quote> means <quote>faster</quote>.  Specifically, the &fromroman; function is slower than it needs to be, because of that big nasty regular expression that you use to validate Roman numerals.  It's probably not worth trying to do away with the regular expression altogether (it would be difficult, and it might not end up any faster), but you can speed up the function by precompiling the regular expression.</para>  
      1602 <para>重构是在可运行代码的基础上使之更良好工作的工作。 通常,<quote>更好</quote>意味着<quote>更快</quote>,也可能意味着 <quote>使用更少的内存</quote>,或者 <quote>使用更少的磁盘空间</quote>,或者仅仅是<quote>更有格调</quote>。 无论是对你,对你的项目,对你的处境来讲,重构对任何程序的长期良性运转都是重要的。</para>  
      1603 <para>这里, <quote>更好</quote> 意味着 <quote>更快</quote>。更具体地说, &fromroman; 函数可以更快关键在于面目可憎的正则表达式。也许在正则表达式上的努力并不值得(这样做很难,而且可能也快不了多少),[todo]看是可以另正则表达快一些。</para>  
    1604 1604 <example>  
    1605   <title>Compiling regular expressions</title>  
      1605 <title>编译正则表达式</title>  
    1605 1605 <screen>  
    1606 1606 &prompt;<userinput>import re</userinput>  
     
    1622 1622 <calloutlist>  
    1623 1623 <callout arearefs="roman.refactoring.1.1">  
    1624   <para>This is the syntax you've seen before: <function>re.search</function> takes a regular expression as a string (<varname>pattern</varname>) and a string to match against it (<literal>'M'</literal>).  If the pattern matches, the function returns a match object which can be queried to find out exactly what matched and how.</para>  
      1624 <para>这是你曾在 <function>re.search</function> 中看到的语法。 把一个正则表达式作为字符串(<varname>pattern</varname>)并用这个字符串来匹配(<literal>'M'</literal>)。  如果能够匹配,如果匹配成功便有函数返回所匹配的对象用以确定匹配的形式和内容。</para>  
    1624 1624 </callout>  
    1625 1625 <callout arearefs="roman.refactoring.1.2">  
    1626   <para>This is the new syntax: <function>re.compile</function> takes a regular expression as a string and returns a pattern object.  Note there is no string to match here.  Compiling a regular expression has nothing to do with matching it against any specific strings (like <literal>'M'</literal>); it only involves the regular expression itself.</para>  
      1626 <para>这里是一个新的语法: <function>re.compile</function> 把一个正则表达式作为字符串并返回一个模版(pattern)对象。注意这里没有字符产的匹配。编译正则表达式和以特定字符串(<literal>'M'</literal>)进行匹配不是一回事,所牵扯的只是正则表达式本身。</para>  
    1626 1626 </callout>  
    1627 1627 <callout arearefs="roman.refactoring.1.3">  
    1628   <para>The compiled pattern object returned from <function>re.compile</function> has several useful-looking functions, including several (like <function>search</function> and <function>sub</function>) that are available directly in the &re; module.</para>  
      1628 <para><function>re.compile</function> 返回的被编译模版有几个值得关注的功能:包括了几个 &re; 模块直接提供的功能(比如: <function>search</function> 和 <function>sub</function>)。</para>  
    1628 1628 </callout>  
    1629 1629 <callout arearefs="roman.refactoring.1.4">  
    1630   <para>Calling the compiled pattern object's <function>search</function> function with the string <literal>'M'</literal> accomplishes the same thing as calling <function>re.search</function> with both the regular expression and the string <literal>'M'</literal>.  Only much, much faster.  (In fact, the <function>re.search</function> function simply compiles the regular expression and calls the resulting pattern object's <function>search</function> method for you.)</para>  
      1630 <para>以 <literal>'M'</literal> 来调用被编译模版对象的 <function>search</function> 函数与凭借正则表达式和字符串 <literal>'M'</literal> 调用 <function>re.search</function> 可以达到相同的结果,只是快了很多。(事实上,<function>re.search</function> 函数仅仅将正则表达式编译,然后为你调用的所得到的模版对象的 <function>search</function> 方法。)</para>  
    1630 1630 </callout>  
    1631 1631 </calloutlist>  
    1632 1632 </example>  
    1633 1633 <note>  
    1634   <title>Compiling regular expressions</title>  
    1635   <para>Whenever you are going to use a regular expression more than once, you should compile it to get a pattern object, then call the methods on the pattern object directly.</para>  
      1634 <title>编译正则表达式</title>  
      1635 <para>在需要不止一次使用正则表达式的情况下,应该将正则表达式进行编译以获得一个模版对象,然后直接调用模版对象的方法即可。</para>  
    1636 1636 </note>  
    1637 1637 <example>  
    1638   <title>Compiled regular expressions in &roman81_filename;</title>  
    1639   <para>This file is available in <filename>py/roman/stage8/</filename> in the examples directory.</para>  
      1638 <title>编译 &roman81_filename; 中的正则表达式</title>  
      1639 <para>这个文件可以从 <filename>py/roman/stage8/</filename> 的 examples 目录中获得。</para>  
    1640 1640 &para_download;  
    1641 1641 <programlisting>  
     
    1666 1666 <calloutlist>  
    1667 1667 <callout arearefs="roman.refactoring.2.1">  
    1668   <para>This looks very similar, but in fact a lot has changed.  <varname>romanNumeralPattern</varname> is no longer a string; it is a pattern object which was returned from <function>re.compile</function>.</para>  
      1668 <para>看起来很相似,但实质却有很大改变。 <varname>romanNumeralPattern</varname> 不再是一个字符串了,而是一个由 <function>re.compile</function> 返回的模版对象。</para>  
    1668 1668 </callout>  
    1669 1669 <callout arearefs="roman.refactoring.2.2">  
    1670   <para>That means that you can call methods on <varname>romanNumeralPattern</varname> directly.  This will be much, much faster than calling <function>re.search</function> every time.  The regular expression is compiled once and stored in <varname>romanNumeralPattern</varname> when the module is first imported; then, every time you call &fromroman;, you can immediately match the input string against the regular expression, without any intermediate steps occurring under the covers.</para>  
      1670 <para>这意味着你可以直接调用 <varname>romanNumeralPattern</varname> 的方法。这比每次调用 <function>re.search</function> 容易很多。 模块被首次引入(import)之时,正则表达式被一次编译并存储于 <varname>romanNumeralPattern</varname>。 之后每次调用 &fromroman; 时,你可以立刻以正则表达式匹配输入的字符串,而不需要在重复背后的这些工作。</para>  
    1670 1670 </callout>  
    1671 1671 </calloutlist>  
    1672 1672 </example>  
    1673   <para>So how much faster is it to compile regular expressions?  See for yourself:</para>  
      1673 <para>那么编译正则表达式可以提速多少呢? 你自己来看吧:</para>  
    1673 1673 <example id="roman.stage8.1.output">  
    1674   <title>Output of &romantest81_filename; against &roman81_filename;</title>  
      1674 <title>以 &romantest81_filename; 测试 &roman81_filename; 的输出</title>  
    1674 1674 <screen><computeroutput>.............          </computeroutput><co id="roman.refactoring.3.1"/><computeroutput>  
    1675 1675 ----------------------------------------------------------------------  
     
    1683 1683 <calloutlist>  
    1684 1684 <callout arearefs="roman.refactoring.3.1">  
    1685   <para>Just a note in passing here: this time, I ran the unit test <emphasis>without</emphasis> the <option>-v</option> option, so instead of the full &docstring; for each test, you only get a dot for each test that passes.  (If a test failed, you'd get an <literal>F</literal>, and if it had an error, you'd get an <literal>E</literal>.  You'd still get complete tracebacks for each failure and error, so you could track down any problems.)</para>  
      1685 <para>有一点说明一下:这里,我在进行单元测试时<emphasis>没有</emphasis>使用 <option>-v</option> 选项,因此输出的也不再是每个测试完整的 &docstring;,而是每个测试的通过以一个点来标示。(失败的测试标以 <literal>F</literal>, 发生错误则标以 <literal>E</literal>, 你仍旧可以获得失败和错误的完整追踪以便查找问题所在)</para>  
    1685 1685 </callout>  
    1686 1686 <callout arearefs="roman.refactoring.3.2">  
    1687   <para>You ran <literal>13</literal> tests in <literal>3.385</literal> seconds, compared to <link linkend="roman.roman72.output"><literal>3.685</literal> seconds</link> without precompiling the regular expressions.  That's an <literal>8%</literal> improvement overall, and remember that most of the time spent during the unit test is spent doing other things.  (Separately, I time-tested the regular expressions by themselves, apart from the rest of the unit tests, and found that compiling this regular expression speeds up the <function>search</function> by an average of <literal>54%</literal>.)  Not bad for such a simple fix.</para>  
      1687 <para>不预先编译正则表达式时,你可以在 <literal>3.385</literal> 秒之内完成 <literal>13</literal> 个测试。这是一个 <literal>8%</literal> 的整体提速,记住单元测试的大量时间实际上花在做其他工作上。(我另外测试了正则表达式部分的耗时,不考虑单元测试的其他环节,正则表达式编译可以另 <function>search</function> 提速 <literal>54%</literal> 。)小小修改还真是值得。</para>  
    1687 1687 </callout>  
    1688 1688 <callout arearefs="roman.refactoring.3.3">  
    1689   <para>Oh, and in case you were wondering, precompiling the regular expression didn't break anything, and you just proved it.</para>  
      1689 <para>对了,预先编译正则表达式并没有对其他部分产生负面影响,前面的证实完全可以打消你的这个顾虑。</para>  
    1689 1689 </callout>  
    1690 1690 </calloutlist>  
    1691 1691 </example>  
    1692   <para>There is one other performance optimization that I want to try.  Given the complexity of regular expression syntax, it should come as no surprise that there is frequently more than one way to write the same expression.  After some discussion about this module on &clp;, someone suggested that I try using the <literal>{<replaceable>m</replaceable>,<replaceable>n</replaceable>}</literal> syntax for the optional repeated characters.</para>  
      1692 <para>我还想做另外一个性能优化工作。就复杂的正则表达式语法而言,通常有不止一种方法来构造相同的表示并不令人惊讶。   在 &clp; 上关于该模块的讨论中,有人建议我使用 <literal>{<replaceable>m</replaceable>,<replaceable>n</replaceable>}</literal> 语法来指代可选重复字符。</para>  
    1692 1692 <example>  
    1693 1693 <title>&roman82_filename;</title>  
    1694   <para>This file is available in <filename>py/roman/stage8/</filename> in the examples directory.</para>  
      1694 <para>这个文件可以从 <filename>py/roman/stage8/</filename> 的 examples 目录中获得。</para>  
    1694 1694 &para_download;  
    1695 1695 <programlisting>  
     
    1711 1711 <calloutlist>  
    1712 1712 <callout arearefs="roman.refactoring.4.1">  
    1713   <para>You have replaced <literal>M?M?M?M?</literal> with <literal>M{0,4}</literal>.  Both mean the same thing: <quote>match 0 to 4 <literal>M</literal> characters</quote>.  Similarly, <literal>C?C?C?</literal> became <literal>C{0,3}</literal> (<quote>match 0 to 3 <literal>C</literal> characters</quote>) and so forth for <literal>X</literal> and <literal>I</literal>.</para>  
      1713 <para>你可以将 <literal>M?M?M?M?</literal> 替换为 <literal>M{0,4}</literal>。 他们的含义相同: <quote>匹配 0 到 4 个 <literal>M</literal> 字符</quote>。 类似地, <literal>C?C?C?</literal> 可以改成 <literal>C{0,3}</literal> (<quote>匹配 0 到 3 个 <literal>C</literal> 字符</quote>) 接下来的修改 <literal>X</literal> 和 <literal>I</literal> 的匹配。</para>  
    1713 1713 </callout>  
    1714 1714 </calloutlist>  
    1715 1715 </example>  
    1716   <para>This form of the regular expression is a little shorter (though not any more readable).  The big question is, is it any faster?</para>  
      1716 <para>这样的正则表达简短一些 (虽然不那么直观)。 核心问题是,是否能加快速度?</para>  
    1716 1716 <example>  
    1717   <title>Output of &romantest82_filename; against &roman82_filename;</title>  
      1717 <title>以 &romantest82_filename; 测试 &roman82_filename; 的结果</title>  
    1717 1717 <screen><computeroutput>.............  
    1718 1718 ----------------------------------------------------------------------  
     
    1725 1725 <calloutlist>  
    1726 1726 <callout arearefs="roman.refactoring.5.1">  
    1727   <para>Overall, the unit tests run 2% faster with this form of regular expression.  That doesn't sound exciting, but remember that the &search; function is a small part of the overall unit test; most of the time is spent doing other things.  (Separately, I time-tested just the regular expressions, and found that the &search; function is <literal>11%</literal> faster with this syntax.)  By precompiling the regular expression and rewriting part of it to use this new syntax, you've improved the regular expression performance by over <literal>60%</literal>, and improved the overall performance of the entire unit test by over <literal>10%</literal>.</para>  
      1727 <para>总体而言, 这种正则表达使单元测试提速 2%。 这不太令人振奋,但记住 &search; 函数只是整体单元测试的一个小部分,很多时间花在了其他方面。 (我另外的测试表明这个应用新语法的正则表达另 &search; 函数提速 <literal>11%</literal> 。) 通过预先编译正则表达和以新语法写正则表达另正则表达式提速超过 <literal>60%</literal>,另单元测试的整体性能提升超过 <literal>10%</literal>.</para>  
    1727 1727 </callout>  
    1728 1728 <callout arearefs="roman.refactoring.5.2">  
    1729   <para>More important than any performance boost is the fact that the module still works perfectly.  This is the freedom I was talking about earlier: the freedom to tweak, change, or rewrite any piece of it and verify that you haven't messed anything up in the process.  This is not a license to endlessly tweak your code just for the sake of tweaking it; you had a very specific objective (<quote>make &fromroman; faster</quote>), and you were able to accomplish that objective without any lingering doubts about whether you introduced new bugs in the process.</para>  
      1729 <para>比任何的性能提升更重要的是模块仍然运转完好。 这便是我早先提到的自由:自由地挪动、修改或者重写任何部分而不至于弄出乱子。 这并不是为无端的更改代码给出借口,而是指你有很切实的目标(<quote>另 &fromroman; 更快</quote>),并且不至于因为怕引入新的错误而迟疑。</para>  
    1729 1729 </callout>  
    1730 1730 </calloutlist>  
    1731 1731 </example>  
    1732   <para>One other tweak I would like to make, and then I promise I'll stop refactoring and put this module to bed.  As you've seen repeatedly, regular expressions can get pretty hairy and unreadable pretty quickly.  I wouldn't like to come back to this module in six months and try to maintain it.  Sure, the test cases pass, so I know that it works, but if I can't figure out <emphasis>how</emphasis> it works, it's still going to be difficult to add new features, fix new bugs, or otherwise maintain it.  As you saw in <xref linkend="re.verbose"/>, &python; provides a way to document your logic line-by-line.</para>  
      1732 <para>还有另外一个我想做的改动,我保证这是最后一个,之后我会停下来,让这个模块歇歇。就像你多次看到的,正则表达式越晦涩难懂越快,我可不想在六个月内再回头试图维护它。是呀!独立测试通过了,我便知道它工作正常,但如果我搞不懂她是<emphasis>如何</emphasis>工作的,添加新功能,修改新Bug,或者维护它将变得很困难。 正如你在 <xref linkend="re.verbose"/>, 看到的, &python; 提供了以[todo]行行逻辑解释你的文档的方法。</para>  
    1732 1732 <example>  
    1733 1733 <title>&roman83_filename;</title>  
    1734   <para>This file is available in <filename>py/roman/stage8/</filename> in the examples directory.</para>  
      1734 <para>该文件可以从 <filename>py/roman/stage8/</filename> 的 examples 目录中获得。</para>  
    1734 1734 &para_download;  
    1735 1735 <programlisting>  
     
    1759 1759 <calloutlist>  
    1760 1760 <callout arearefs="roman.refactoring.6.1">  
    1761   <para>The <function>re.compile</function> function can take an optional second argument, which is a set of one or more flags that control various options about the compiled regular expression.  Here you're specifying the <literal>re.VERBOSE</literal> flag, which tells &python; that there are in-line comments within the regular expression itself.  The comments and all the whitespace around them are <emphasis>not</emphasis> considered part of the regular expression; the <function>re.compile</function> function simply strips them all out when it compiles the expression.  This new, <quote>verbose</quote> version is identical to the old version, but it is infinitely more readable.</para>  
      1761 <para><function>re.compile</function> 函数的第二个参数是可选的,通过一个或一组选项设置来控制正则表达编译中的多样化选项。 注释和他们周围的空白<emphasis>不</emphasis>是正则表达式的一部分,在编译正则表达式时 <function>re.compile</function>  函数会忽略他们。这个新 <quote>verbose</quote> 版本与老版本如出一辙,只是更具可读性。</para>  
    1761 1761 </callout>  
    1762 1762 </calloutlist>  
    1763 1763 </example>  
    1764 1764 <example>  
    1765   <title>Output of &romantest83_filename; against &roman83_filename;</title>  
      1765 <title>以 &romantest83_filename; 测试 &roman83_filename; 的输出</title>  
    1765 1765 <screen><computeroutput>.............  
    1766 1766 ----------------------------------------------------------------------  
     
    1772 1772 <calloutlist>  
    1773 1773 <callout arearefs="roman.refactoring.7.1">  
    1774   <para>This new, <quote>verbose</quote> version runs at exactly the same speed as the old version.  In fact, the compiled pattern objects are the same, since the <function>re.compile</function> function strips out all the stuff you added.</para>  
      1774 <para>新 <quote>verbose</quote> 版本和老版本的运行速度一样。 事实上, 编译的模版对象也一样,因为 <function>re.compile</function> 函数剔除掉所有你添加的内容。</para>  
    1774 1774 </callout>  
    1775 1775 <callout arearefs="roman.refactoring.7.2">  
    1776   <para>This new, <quote>verbose</quote> version passes all the same tests as the old version.  Nothing has changed, except that the programmer who comes back to this module in six months stands a fighting chance of understanding how the function works.</para>  
      1776 <para>新 <quote>verbose</quote> 版本可以通过所有老版本通过的测试。 除了六个月后程序员有了理解该函数运行机理的机会外什么都没有改变。</para>  
    1776 1776 </callout>  
    1777 1777 </calloutlist>  
     
    1782 1782 <section id="roman.postscript">  
    1783 1783 <?dbhtml filename="refactoring/postscript.html"?>  
    1784   <title>Postscript</title>  
      1784 <title>后记</title>  
    1784 1784 <abstract>  
    1785 1785 <title/>  
    1786   <para>A clever reader read the <link linkend="roman.refactoring">previous section</link> and took it to the next level.  The biggest headache (and performance drain) in the program as it is currently written is the regular expression, which is required because you have no other way of breaking down a Roman numeral.  But there's only 5000 of them; why don't you just build a lookup table once, then simply read that?  This idea gets even better when you realize that you don't need to use regular expressions at all.  As you build the lookup table for converting integers to Roman numerals, you can build the reverse lookup table to convert Roman numerals to integers.</para>  
      1786 <para>聪明的读者在学习 <link linkend="roman.refactoring">前一节</link> 时想得会更深入一层。 现在写的这个程序中最令人头痛(颇有绞尽脑汁之感)的是正则表达式,由于没有其他方法来处理罗马数字使得它成了必须。但是,只有 5000 个查询,为什么不构建一个查询表? 不必用正则表达式凸现了这个主意的好处。 你建立整数到罗马数的条格之时,罗马数到整数的相反表格也同样构建。</para>  
    1786 1786 </abstract>  
    1787   <para>And best of all, he already had a complete set of unit tests.  He changed over half the code in the module, but the unit tests stayed the same, so he could prove that his code worked just as well as the original.</para>  
      1787 <para>更大的好处在于,你有了完整的单元测试。 这样需要更改模块的一大部分,单元测试却不需要改变,你可以确定你的新代码与原代码同样工作正常。</para>  
    1787 1787 <example>  
    1788 1788 <title>&roman9_filename;</title>  
    1789   <para>This file is available in <filename>py/roman/stage9/</filename> in the examples directory.</para>  
      1789 <para>这个文件可以从 <filename>py/roman/stage9/</filename> 的 examples 目录中获得。</para>  
    1789 1789 &para_download;  
    1790 1790 <programlisting>  
     
    1825 1825 </programlisting>  
    1826 1826 </example>  
    1827   <para>So how fast is it?</para>  
      1827 <para>这样有多快呢?</para>  
    1827 1827 <example>  
    1828   <title>Output of &romantest9_filename; against &roman9_filename;</title>  
      1828 <title>以 &romantest9_filename; 测试 &roman9_filename; 的输出</title>  
    1828 1828 <screen>  
    1829 1829 <computeroutput>  
    1838 1838 </screen>  
    1839 1839 </example>  
    1840   <para>Remember, the best performance you ever got in the original version was 13 tests in 3.315 seconds.  Of course, it's not entirely a fair comparison, because this version will take longer to import (when it fills the lookup tables).  But since import is only done once, this is negligible in the long run.</para>  
    1841   <para>The moral of the story?</para>  
      1840 <para>还记得,你原有版本的最快速度是 3.315 秒。 当然,这样的比较不是完全的公平,这个新版本需要更长的时间来导入 (填充查询表)。 但是导入只需一次,在整体过程中可以忽略。</para>  
      1841 <para>这样做值得吗?</para>  
    1842 1842 <itemizedlist>  
    1843   <listitem><para>Simplicity is a virtue.</para></listitem>  
    1844   <listitem><para>Especially when regular expressions are involved.</para></listitem>  
    1845   <listitem><para>And unit tests can give you the confidence to do large-scale refactoring... even if you didn't write the original code.</para></listitem>  
      1843 <listitem><para>简洁是真理。</para></listitem>  
      1844 <listitem><para>特别是需要使用正则表达式时。</para></listitem>  
      1845 <listitem><para>并且单元测试给了你大规模重组的信心...... 就算还没写源代码也是这样。</para></listitem>  
    1846 1846 </itemizedlist>  
    1847 1847 </section>  
    1848 1848 <section id="roman.summary">  
    1849 1849 <?dbhtml filename="refactoring/summary.html"?>  
    1850   <title>Summary</title>  
      1850 <title>小结</title>  
    1850 1850 <abstract>  
    1851 1851 <title/>  
    1852   <para>Unit testing is a powerful concept which, if properly implemented, can both reduce maintenance costs and increase flexibility in any long-term project.  It is also important to understand that unit testing is not a panacea, a Magic Problem Solver, or a silver bullet.  Writing good test cases is hard, and keeping them up to date takes discipline (especially when customers are screaming for critical bug fixes).  Unit testing is not a replacement for other forms of testing, including functional testing, integration testing, and user acceptance testing.  But it is feasible, and it does work, and once you've seen it work, you'll wonder how you ever got along without it.</para>  
      1852 <para>单元测试是一个强大的概念,只要使用得当即可减少维护成本又可增加项目的长期灵活度。 同样重要的是意识到单元测试并不是万灵药。 编写好的独立测试很难,保持其更新需要规范(特别是当顾客对Bug大呼小叫之时)。 单元测试并不能取代其他形式的测试,比如说功能性测试、整体测试以及用户接受测试。但它切实可行且功效明显,一旦相识,你会反问为什么以往没有应用它。</para>  
    1852 1852 </abstract>  
    1853   <para>This chapter covered a lot of ground, and much of it wasn't even &python;-specific.  There are unit testing frameworks for many languages, all of which require you to understand the same basic concepts:</para>  
      1853 <para>这一章涵盖了很多内容,而且甚至很多不为 &python; 所特有。 很多语言都有单元测试框架,而且都要求你理解相同的基本概念:</para>  
    1853 1853 <highlights>  
    1854 1854 <itemizedlist>  
    1855   <listitem><para>Designing test cases that are specific, automated, and independent</para></listitem>  
    1856   <listitem><para>Writing test cases <emphasis>before</emphasis> the code they are testing</para></listitem>  
    1857   <listitem><para>Writing tests that <link linkend="roman.success">test good input</link> and check for proper results</para></listitem>  
    1858   <listitem><para>Writing tests that <link linkend="roman.failure">test bad input</link> and check for proper failures</para></listitem>  
    1859   <listitem><para>Writing and updating test cases to <link linkend="roman.bugs">illustrate bugs</link> or <link linkend="roman.change">reflect new requirements</link></para></listitem>  
    1860   <listitem><para><link linkend="roman.refactoring">Refactoring</link> mercilessly to improve performance, scalability, readability, maintainability, or whatever other -ility you're lacking</para></listitem>  
      1855 <listitem><para>完全独立运行,不需要人工输入。单元测试应该是自动的。</para></listitem>  
      1856 <listitem><para>可以自己决断,不论被测试函数通过还是失败都不需要人工干预结果。</para></listitem>  
      1857 <listitem><para>隔离运行,可以与其他独立测试隔离(尽管他们可能测试着同一个函数)。每个独立测试是一个孤岛。</para></listitem>  
      1858 <listitem><para>独立测试被设计成有针对性,自动独立运行。</para></listitem>  
      1859 <listitem><para>在被测试代码编写 <emphasis>之前</emphasis> 编写独立测试。</para></listitem>  
      1860 <listitem><para>编写测试 <link linkend="roman.success">来测试有效输入</link> 和恰当结果。</para></listitem>  
      1861 <listitem><para>编写测试 <link linkend="roman.failure">来测试无效输入</link> 和恰当失败。</para></listitem>  
      1862 <listitem><para>编写和升级独立测试来 <link linkend="roman.bugs">描述 Bug</link> 或 <link linkend="roman.change">反映新需求</link>。</para></listitem>  
      1863 <listitem><para><link linkend="roman.refactoring">重组</link> 集中考虑性能提升、可测控性、可读性、可维护性以及其他性能方面的欠缺。</para></listitem>  
    1861 1864 </itemizedlist>  
    1862 1865 </highlights>  
    1863   <para>Additionally, you should be comfortable doing all of the following &python;-specific things:</para>  
      1866 <para>另外,你应该能够自如地做到如下 &python; 的特有工作:</para>  
    1863 1866 <highlights>  
    1864 1867 <itemizedlist>  
    1865   <listitem><para><link linkend="roman.testtoromanknownvalues.example">Subclassing <literal>unittest.TestCase</literal></link> and writing methods for individual test cases</para></listitem>  
    1866   <listitem><para>Using <link linkend="roman.testtoromanknownvalues.example">&assertEqual;</link> to check that a function returns a known value</para></listitem>  
    1867   <listitem><para>Using <link linkend="roman.tobadinput.example">&assertRaises;</link> to check that a function raises a known exception</para></listitem>  
    1868   <listitem><para>Calling <link linkend="roman.stage1.output"><literal>unittest.main()</literal></link> in your <literal>if __name__</literal> clause to run all your test cases at once</para></listitem>  
    1869   <listitem><para>Running unit tests in <link linkend="roman.stage1.output">verbose</link> or <link linkend="roman.stage8.1.output">regular</link> mode</para></listitem>  
      1868 <listitem><para>继承 <link linkend="roman.testtoromanknownvalues.example"> <literal>unittest.TestCase</literal></link> 生成子类并为每个独立测试编写方法。</para></listitem>  
      1869 <listitem><para>使用 <link linkend="roman.testtoromanknownvalues.example">&assertEqual;</link> 检查已知结果的返回。</para></listitem>  
      1870 <listitem><para>使用 <link linkend="roman.tobadinput.example">&assertRaises;</link> 检查函数是否引发已知异常。</para></listitem>  
      1871  
      1872 <listitem><para>在 <literal>if __name__</literal> 子句调用 <link linkend="roman.stage1.output"><literal>unittest.main()</literal></link> 来一次性运行所有独立测试。</para></listitem>  
      1873 <listitem><para>以 <link linkend="roman.stage1.output">详细(verbose)</link> 或者 <link linkend="roman.stage8.1.output">普通(regular)</link> 模式运行单元测试</para></listitem>  
    1870 1874 </itemizedlist>  
    1871 1875 </highlights>  
    1872 1876 <itemizedlist role="furtherreading">  
    1873   <title>Further reading</title>  
    1874   <listitem><para><ulink url="&url_xpcom;">&xpcom;</ulink> has links to <ulink url="&url_xpcom;software.htm">download unit testing frameworks</ulink> for many different languages.</para></listitem>  
      1877 <title>进一步阅读</title>  
      1878 <listitem><para><ulink url="&url_xpcom;">&xpcom;</ulink> 有到 <ulink url="&url_xpcom;software.htm">下载单元测试模块</ulink> 不同语言版本的链接。</para></listitem>  
    1875 1879 </itemizedlist>  
    1876 1880 </section>