学习笔记
Programming
TDD
最近在读Kent Beck的《Test-Driven Development By Example》,让我对软件开发有了新的认识。
写test并不为了测试而测试,是为了增强对程序的信心,减少焦虑。那么如何减少焦虑呢,是通过一次只关心一件事情。如何一次只关心一件事情,这就是TDD。
本书分三个部分
- The Money Example
- The xUnit Example
- Patterns for Test-Driven Development
前两个部分讲了两个例子,来阐述如何进行TDD开发,最后一个部分算是总结,全书的精华所在。本文不打算讲这两个例子了,因为例子最好自己手写一遍,才可以加深了解。本文主要摘录一些TDD的模式方法(pattern)。
下面这段话来自第一章,提纲挈领:
My goal is for you to see the rhythm of Test-Driven Development (TDD), which can be summed up as follows.
- Quickly add a test.
- Run all tests and see the new one fail.
- Make a little change.
- Run all tests and see them all succeed.
- Refactor to remove duplication.
The surprises are likely to include.
- How each test can cover a small increment of functionality.
- How small and ugly the changes can be to make the new tests run.
- How often the tests are run.
- How many teensy-weensy steps make up the refactorings.
这就是TDD的精髓。按作者的说法就是Green/Red/Green/Refactor
节奏。
这也回答了我一直以来的困扰,到底应该如何设计一段程序,到底要考虑多少种情况。按照TDD的建议就是,首先写测试,然后代码怎么快怎么来,不管有多ugly,然后如果发现重复,及时做重构。不要考虑什么扩展性之类的,这个设计方式本身就能提供。因为它提高了运行测试和重构的频率,既提升了编码者对程序的信心,也降低了不合理设计的可能性。
书中引入了很多影像图(Influence Diagram),很形象的说明了,引入TDD能给我们日常的开发工作引入什么好处。
This is a positive feedback loop.
- The more stress you feel, the less testing you will do.
- The less testing you do, the more errors you will make.
- The more errors you make, the more stress you feel.
- Rinse and repeat.
能将大块的测试分割成松耦合的小测试不容易,但是好处大大的。作者说:
I never knew exactly how to achieve high cohesion and loose coupling regularly until I started writing isolated tests.
我们要怎么开始TDD呢?作者给出了很具操作性的建议:
Before you begin, write a list of all the tests you know you will have to write.
这个list上写点什么呢?
- Put on the list examples of every operation that you know you need to implement. 要哪些操作。
- for those operations that don’t already exist, put the null version of that operation on the list.哪些操作没实现,写上存根。
- List all of the refactorings that you think you will have to do in order to have clean code at the end of this session. 完成之后,列出需要重构的地方。而如果有大的重构的需要时,放到later list里面,把手里的事情处理完,再去处理大的重构。
看作者时刻在强调TDD就是tiny steps:
The pure form of TDD, wherein you are never more than one change away from a green bar, is like that three-out-of-four rule.
什么时候写测试?
答:在写code之前,因为一旦你开始写code,你可能就身陷泥潭,无暇顾及test case了。
Each test should represent one step toward your overall goal.
如何开始写测试?不要一开始就想写一个真正的测试,因为这样会一次面对太多的问题。作者建议尝试从一个简单的测试输入输出开始。这样以便于你从Greenxun迅速开始你的Green/Red/Green/Refactor节奏。作者举了一个产生多边形的例子:
1.Reducer r= new Reducer(new Polygon());
2.assertEquals(0, reducer.result().npoints);
不太懂这个问题,不过可以从中看出作者对TDD的解释。问题本身是要解一个降低多边形边数的问题。作者的启动测试就是一个“0”边多边形,那结果一定是”0”边多边形。对实际意义很荒诞,但对测试来说是一个不错的开始。符合我们的预期,简单而且又represent one step toward your overall goal
。
这一章是在说如何不用强迫的手段让非TDD的团队成员能转向TDD。当开发者向你解释他的程序时,你都可以将之转化为test case。
本书还有一个很有特色的地方是,贯穿全文,作者总是在经意不经意间流露着各种宝贵的工作经验:如何长期保持更高效的编程?如果你是经理,你如何与你的团队成员一起工作?等等。这一章就有这样一句话:
If you’re a manager or a leader, you can’t force anyone to change the way they work.
What can you do? A simple start is to start asking for explanations in terms of test cases: “Let me see if I understand what you’re saying.
回归测试是指修改了旧代码后,重新进行测试以确认修改没有引入新的错误或导致其他代码产生错误。
自动回归测试将大幅降低系统测试、维护升级等阶段的成本。回归测试作为软件生命周期的一个组成部分,在整个软件测试过程中占有很大的工作量比重,软件开发的各个阶段都会进行多次回归测试。在渐进和快速迭代开发中,新版本的连续发布使回归测试进行的更加频繁,而在极端编程方法中,更是要求每天都进行若干次回归测试。因此,通过选择正确的回归测试策略来改进回归测试的效率和有效性是非常有意义的。
—— 百度百科
本书讲的回归测试概念可能略有不同。按照我的理解,作者是想说,在进行完所有的编码以后,你已经对你的要实现的所有东西完成了unknownknown的过程。那么现在,你可以回过头看看,如果这时候重新写一份测试你应该写什么。
回到你针对这个系统的最初的想法来对整个系统做一个测试。以期对整个系统有个整体的评价。
劳逸结合对程序员是非常非常非常重要的!
Shower Methodology:If you know what to type, then type. If you don’t know what to type, then take a shower, and stay in the shower until you know what to type.
TDD Rule: If you don’t know what to type, then Fake It. If the
right design still isn’t clear, then Triangulate.
看看作者提倡的时间管理
- At the scale of hours, keep a water bottle by your keyboard so that biology provides the motivation for regular breaks.
- At the scale of a day, commitments after regular work hours can help you to stop when you need sleep before progress.
- At the scale of a week, weekend commitments help get your conscious, energysucking thoughts off work. (My wife swears I get my best ideas on Friday evening.)
- At the scale of a year, mandatory vacation policies help you refresh yourself completely. The French do this right—two contiguous weeks of vacation aren’t enough. You spend the first week decompressing, and the second week getting ready to go back to work. Therefore, three weeks, or better four, are necessary for you to be your most effective the rest of the year.
What do you do when you are feeling lost? Throw away the code and start over.
作者在这一章告诉你,不要在泥潭越陷越深,不要害怕重新开始。换个伙伴,换个思路,你可能有更好的发现。
这里说的是,如果你觉得这个测试太大,那就要把这个大的测试破成若干个小的测试,然后看看有没有进展。
How do you test an object that relies on an expensive or complicated resource? Create a fake version of the resource that answers constants.
If you want to use Mock Objects, you can’t easily store expensive resources in global variables.要记得清理现场,否则万一其他的代码把他当作真的object在使用,就会出大问题。所以编码最基本的准则是少用全局变量。
Mock Objects encourage you down the path of carefully considering the visibility of every object, reducing the coupling in your designs. 这里说的是,如果你用Mock Object,你就要思考我到底该不该把这个庞然大物暴露给这段代码。这种思考有助于减小耦合性。
How do you test that one object communicates correctly with another? Have the object under test communicate with the test case instead of with the object it expects.
Without Self Shunt
1.# ResultListenerTest
2.def testNotification(self):
3. result= TestResult()
4. listener= ResultListener()
5. result.addListener(listener)
6. WasRun("testMethod").run(result)
7. assert 1 == listener.count
8.
9.# ResultListener
10.class ResultListener:
11. def __init__(self):
12. self.count= 0
13. def startTest(self):
14. self.count= self.count + 1
With Self Shunt
1.# ResultListenerTest
2.def testNotification(self):
3. self.count= 0
4. result= TestResult()
5. result.addListener(self)
6. WasRun("testMethod").run(result)
7. assert 1 == self.count
8.def startTest(self):
9. self.count= self.count + 1
这里举得例子不是很明白。中文译本里,将self shunt翻译成自分流,感觉也是字面翻译。我理解这里想说的是,不一定要定义一个类来做当前被测试类来做的事情,可以在test case里面用该类暴露的接口来做事情。
Self Shunt may require that you use Extract Interface to get an interface to implement. You will have to decide whether extracting the interface is easier, or if testing the existing class as a black box is easier.
这里又看晕了,self shunt跟extract interface有啥关系。
当我们注意到在我们代码结构中超过一个类使用到另一个特殊类中的同一个方法时,此时应该将该类中的方法提取出来放到一个接口(interface)中,并对外提供这个接口以便被用户类访问、使用,这样有利于打破原来多个类与这个特殊类的依赖关系。这个重构手法很容易实现,更重要的是这样做有利于松耦合。 ——网络上的解释
我觉得这里作者提到的extract interface,并不是我们说的重构里的extract interface。这里说要测试A类和B类通信,我们不直接用B类,而用test case与A类通信,以测试通信的A类端接口是否正确。extract interface说的是从B类中extract接口出来到test case中去implement。或者把B类直接当黑盒子在test case中使用。
这个很有用。有些函数或者过程是没有返回值的,我们就可以用Log String的方式来查看该过程或函数有没有被调用。类似我们看trace log一样,通过看trace log能知道机器执行的代码是否正确。
How do you test error code that is unlikely to be invoked? Invoke it anyway with a special object that throws an exception instead of doing real work.
作者经常能给出一些很实用的小经验,这里又来了,看下面。
How do you leave a programming session when you’re programming alone? Leave the last test broken.
当你要转去做另外一件事情,而手头上的事情又没结束时怎么办?让最后一个测试故意的不通过。这样下次就知道从哪儿开始,然后能迅速上手。醍醐灌顶啊!以前都是认为做另外一件事情之前要把手头上的事情弄的尽善尽美,其实不然。怎么做利于后期继续进行,那就应该怎么做。
You will occasionally find a test broken in the integration suite when you try to check in. What to do?
The simplest rule is to just throw away your work and start over. The broken test is pretty strong evidence that you didn’t know enough to program what you just programmed.
什么什么?竟然要我们扔掉我们做的,然后重新开始。就因为我们的测试和integration测试结果不一致。
大师的话总是有道理的,以后要好好考虑这一点。
作者提到的所谓三角法。通俗的说,就是只在必要的时候做抽象或通用化处理。通常是在TDD的重构这一步完成这个抽象的过程。再通俗点说,就是多弄几个case,再总结共同点,之后做抽象
从另一个角度,作者又说了:
I only use Triangulation when I’m really, really unsure about the correct abstraction for the calculation. Otherwise I rely on either Obvious Implementation or Fake It.
How do you implement an operation that works with collections of objects? Implement it without the collections first, then make it work with collections.
中心思想还是一个,从显而易见的地方开始,每次只做一小步,然后一步一步到达最终的目的地。看下面的例子:
1.// 1. fake code
2.public void testSum() {
3. assertEquals(5, sum(5));
4.}
5.private int sum(int value) {
6. return value;
7.}
8.
9.// 2. add arrays
10.public void testSum() {
11. assertEquals(5, sum(5, new int[] {5}));
12.}
13.private int sum(int value, int[] values) {
14. return value;
15.}
16.
17.// 3. add real sum functionality
18.public void testSum() {
19. assertEquals(5, sum(5, new int[] {5}));
20.}
21.private int sum(int value, int[] values) {
22. int sum= 0;
23. for ( int i= 0; i<values.length; i++)
24. sum += values[i];
25. return sum;
26.}
27.
28.// 4. delete unnecessary code
29.public void testSum() {
30. assertEquals(5, sum(new int[] {5}));
31.}
32.private int sum( int[] values) {
33. int sum= 0;
34. for (int i= 0; i<values.length; i++)
35. sum += values[i];
36. return sum; sum;
37.}
细细体会其间的差别吧。
How do you check that tests worked correctly?
Write boolean expressions that automate your judgment about whether the code worked.
作者在这一章又给出了一个很有指导意义的观点。
尽量不要做白盒测试,如果你想做,说明你的设计有问题
这里作者抛出了一个问题,如果有多个测试有类似的代码怎么办?两个选择:
- test case类定义中抽象出类似setUp的函数,来做重复的工作
- 或者用类似代码生成器的东西来为所有需要的测试生成fixture
方法1有时候更符合DRY原则,但它的缺点是,你要记住setUp里面到底做了什么。两种方法都可以,选择更合适的。
所谓的外部fixture,就是要保证测试能恢复环境配置。
在所有测试开始前调setUp,所有测试结束后调tearDown。这就是External Fixture
1./* Adding to tuple spaces. */
2./* Taking from tuple spaces. */
3./** Taking a non-existent tuple. **/
4./** Taking an existing tuple. **/
5./** Taking multiple tuples. **/
6./* Reading from tuple space. */
这一节说的是,如果待测的函数需要抛出异常,用如下的方法:
1.public void testMissingRate() {
2. try {
3. exchange.findRate("USD", "GBP");
4. fail(); // 如果没抛异常就fail
5. } catch (IllegalArgumentException expected) {
6. }
7.}
How do you run all tests together? Make a suite of all the suites—one for each package, and one aggregating the package tests for the whole application.
1.public class AllTests {
2. // 这里有个main,方便直接调用程序开始测试
3. public static void main(String[] args) {
4. junit.swingui.TestRunner.run(AllTests.class);
5. }
6. // 这里有个suite包含了所有test case
7. public static Test suite() {
8. TestSuite result= new TestSuite("TFD tests");
9. result.addTestSuite(MoneyTest.class);
10. result.addTestSuite(ExchangeTest.class);
11. result.addTestSuite(IdentityRateTest.class);
12. return result;
13. }
14.}
作者在这一章引入了一些TDD中会用到的设计模式。有些是写测试时用的,有的是重构时用的,有的两者皆可。下面是本章所有模式的总纲。
What do you do when you need the invocation of a computation to be more complicated than a simple method call?
Make an object for the computation and invoke it.
这里所说的命令是说,把要调用的方法封装成一个object,然后调用那个object
把一些常量参数做成object来传递
When implementing a Value Object, every operation has to return a fresh object,
All Value Objects have to implement equality
1.public boolean setReadOnly() {
2. SecurityManager guard = System.getSecurityManager();
3. if (guard != null) { ) {
4. guard.canWrite(path);
5. }
6. return fileSystem.setReadOnly(this);
7.}
上面这个例子getSecurityManager在不成功时返回Null,然后调用者再判断返回值以决定会不会用空指针来调用成员变量。
这是一个很常见的做法,这样做不好的是,通常这种判断会非常非常多,而且给人一种“会不会有哪里没判断,以后会导致crash?”的担忧。
Null Object设计模式就是让getSecurityManager返回一个特殊的object来取代Null,这样让所有的操作都统一,以减少重复代码。
模板方法。
When you find two variants of a sequence in two subclasses, you need to gradually move them closer together. Once you’ve extracted the parts that are different from other methods, what you are left with is the Template Method.
1.// TestCase
2.public void runBare() throws Throwable {
3. setUp();
4. try {
5. runTest();
6. }
7. finally {
8. tearDown();
9. }
10.}
中文译名为插入式对象。很难理解哪里有这个所谓的“插入”。这个设计模式核心的思想就是消除if...else...
的情况。
看作者的例子,解释的很清楚:
1.// Without Pluggable Object
2.Figure selected;
3.public void mouseDown() {
4. selected= findFigure();
5. if (selected != null) // 到处是null判断
6. select(selected);
7.}
8.public void mouseMove() {
9. if (selected != null)
10. move(selected);
11. else
12. moveSelectionRectangle();
13.}
14.public void mouseUp() {
15. if (selected == null)
16. selectAll();
17.}
18.
19.// With Pluggable Object
20.SelectionMode mode;
21.public void mouseDown() {
22. selected= findFigure();
23. // 全部归结到mode子类里面去处理
24. if (selected != null)
25. mode= SingleSelection(selected);
26. else
27. mode= MultipleSelection();
28.}
29.public void mouseMove() {
30. mode.mouseMove();
31.}
32.public void mouseUp() {
33. mode.mouseUp();
34.}
这一节的设计模式和上一节的极为类似。这次要处理掉的是switch...case...
而又不用子类继承的方式来动态调用。这里使用的方法叫反射。
反射:通过自身的属性(如类名,方法名)来做相关逻辑
同样看作者的例子
1.void print() {
2. Method runMethod= getClass().getMethod(printMessage, null);
3. runMethod.invoke(this, new Class[0]);
4.}
看到了没getClass,getMethod,这就是反射。有了这个函数既不需要switch来调用不同的printxxx函数(printHtml, printXml…),也不需要定义子类来调用不同的print函数。
作者在本节最后给出了一段忠告:
Pluggable Selector can definitely be overused. The biggest problem with it is tracing code to see whether a method is invoked. Use Pluggable Selector only when you are cleaning up a fairly straightforward situation in which each of a bunch of subclasses has only one method.
作者说工厂方法提供了灵活性。我是没看出来。对比下面两段代码,这里增加了灵活性?
1.public void testMultiplication() {
2. Dollar five= Dollar five= new Dollar(5);
3. assertEquals(new Dollar(10), five.times(2));
4. assertEquals(new Dollar(15), five.times(3));
5.}
6.
7.public void testMultiplication() {
8. Dollar five = Money.dollar(5); // 工厂方法
9. assertEquals(new Dollar(10), five.times(2));
10. assertEquals(new Dollar(15), five.times(3));
11.}
12.// Money
13.static Dollar dollar( Dollar dollar(int amount) {
14. return new Dollar(amount);
15.}
作者最后给出了一点忠告
You have to remember that the method is really creating an object, even though it doesn’t look like a constructor. Use Factory Method only when you need the flexibility it creates.
中文译名为冒名顶替。根据文中的阐述,我理解这里讲的不是一种设计模式,而只是一种模式,一种特殊情况。指的是如果某一段代码,只将其中的一个名字(类、方法或者变量等等)替换成另外一个名字,就能得到一段新的代码,称为冒名顶替。发现可以Imposter的情况,其实就是发现重复。
1.// 下面这两段代码就只是将RectangleFigure换成OvalFigure,此是为Imposter冒名顶替。
2.testRectangle() {
3. Drawing d= new Drawing();
4. d.addFigure(new RectangleFigure(0, 10, 50, 100)); RectangleFigure(0, 10, 50, 100));
5. RecordingMedium brush= new RecordingMedium();
6. d.display(brush);
7. assertEquals("rectangle 0 10 50 100\n", brush.log());
8.}
9.
10.testOval() {
11. Drawing d= new Drawing();
12. d.addFigure(new OvalFigure(0, 10, 50, 100));
13. RecordingMedium brush= new RecordingMedium();
14. d.display(brush);
15. assertEquals("oval 0 10 50 100\n", brush.log());
16.}
作者还提到另外两个在重构中经常用到的设计模式,本质其实就是Imposter —— Null Object & Composite。
根据上一章作者的观点,组合其实就是一种冒名顶替。作者在本节开头说了:
How do you implement an object whose behavior is the composition of the behavior of a list of other objects? Make it an Imposter for the component objects.
让这个对象称为组成他的对象的冒名顶替者。说起来很拗口。看例子:
1.// Transaction
2.Transaction(Money value) {
3. this.value= value;
4.}
5.// Account
6.Transaction transactions[];
7.Money balance() {
8. Money sum= Money.zero();
9. for (int i= 0; i < transactions.length; i++)
10. sum= sum.plus(transactions[i].value);
11. return sum;
12.}
这个写法的缺点是,当要算OverallAccount时,没辙了,要定义一个新类来处理。一个OverallAccount是一组Account。但OverallAccount其实要做的也是一种累加,和Account做的并没有什么区别。这就是重复。
解决的办法就是所谓的Composite。把要累加的东西抽象出来称为Holdings,Account累加的是Holdings,OverallAccount累加的也是Holdings。
1.Holding holdings[];
2.Money balance() {
3. Money sum= Money.zero();
4. for ( (int i= 0; i < holdings.length; i++)
5. sum= sum.plus(holdings[i].balance());
6. return sum;
7.}
写到这里,我仍然不是特别明白何为Composite(拼合,复合的意思)。但我们是站在巨人的肩膀上,对比阅读了中文译本后有了一点点感觉。这个设计模式的中文译名为-递归组合。所谓递归,应当指的是,通过概念上的抽象,将一个大的混合类写成一些更抽象的概念的组合。例如:
OvreallAccount <- Account <- Transactions
OverallAccount <- Holding & Account <-Holding
通过递归地将OverallAccount冒名顶替成Holding,从而使OverallAccount成为一个Account来达到消除重复。
作者在本节最后给出了何时使用Composite的建议:
As is obvious from this discussion, I’m still not able to articulate how to guess when a collection of objects is just a collection of objects and when you really have a Composite. The good news is, since you’re getting good at refactoring, the moment the duplication appears, you can introduce Composite and watch program complexity disappear.
这个就是很著名的单例模式,经常会用到。作者没有详加阐释,只是告诫读者不要用全局变量(global variable)。我猜作者的意思是,如果要用全局变量,就用单例模式的类对象吧。
在TDD中,重构要做的就是,保证所有测试通过的情况下,降低重复(code之间,code与test之间)。
- How do you unify two similar looking pieces of code? Gradually bring them closer. Unify them only when they are absolutely identical. 不要强制整合,等他们完全相等了再说
- Such a leap-of-faith refactoring is exactly what we’re trying to avoid with our strategy of small steps and concrete feedback. Although you can’t always avoid leapy refactorings, you can reduce their incidence. 不要做跨越式的重构。
- Sometimes you need to approach reconciling differences backward—that is, think about how the last step of the change could be trivial, then work backward. 反向思考,尽量让每一步都非常小(trivial),这样方便回退。
隔离改动,也就是我们通常做的一些抽象封装。把变化的部分用接口包装起来,这样就达到了隔离的效果。
Some possible ways to isolate change are Extract Method (the most common), Extract Object, and Method Object.
How do you make a long, complicated method easier to read? Turn a small part of it into a separate method and call the new method. 将一个大函数打碎,从中提取一些方法出来。
- Find a region of the method that would make sense as its own method. Bodies of
loop, whole loops, and branches of conditionals are common candidates for extraction. - Make sure that there are no assignments to temporary variables declared outside the
scope of the region to be extracted. - Copy the code from the old method to the new method. Compile it.
- For each temporary variable or parameter of the original method used in the new
method, add a parameter to the new method. - Call the new method from the original method.
- 增强可读性
- 消除重复,当你发现有两个大函数,有一部分是相同的,那就可以提取出来称为新的方法。
- 通过inline把一堆函数放在一起,然后看看哪里可以extract (这一点我感觉应该放在How里面,作者是想承上启下,将inline吧)
inline很玄乎,其实就是把函数的代码复制到调用的地方。好处是什么,作者有话说:
You might like the second version better, or you might not. The point to note here is that you can use Inline Method to play around with the flow of control.
其实就是让你把抽象出来的东西放到一起重新抽象,看看会不会有更好的结果。这也是一种重构的方法。
如何将一个类抽象成一个interface。所谓的interface就是只有纯虚函数的类。
- Declare an interface. Sometimes the name of the existing class should be the name of the interface, in which case you should first rename the class.
- Have the existing class implement the interface.
- Add the necessary methods to the interface, expanding the visibility of the methods in the class if necessary.
- Change type declarations from the class to the interface where possible.
这里提到了两种情况:
- 当你需要抽象出父类时
- 当你用了Mock Object,现在要为这个mock object抽象出一个真正的接口时。作者这里提到了一个小技巧,在这种情况下命名是一个头疼的问题。不妨就把新的interface称为IXXX,例如,原来的叫
File
,新的接口就称为IFile
其实就是把一个类的方法移到另外一个类里面。但作者提到了一种情况下,这个方法不适用。就是这个方法修改了原先那个类的数据。
这个话题好像很新鲜,之前看到过很多次,但并没有深究。先看看How:
- Create an object with the same parameters as the method.
- Make the local variables also instance variables of the object.
- Create one method called run(), whose body is the same as the body of the original method.
- In the original method, create a new object and invoke run().
有什么好处?
两个好处
- 在添加复杂逻辑的时候,如果有对一个过程有多种方法,那创建一个方法对象,再扩展起来就很简单
- 在一个复杂函数里extract interface的时候,通常一段code需要5,6个参数,这样的话,虽然函数抽取出来了,但是写法并没有简化多少。这时候如果有方法对象,则在类里面是一个独立的名字空间,会提供额外的便利。
就是把方法调用的参数,变成类成员变量。为什么要这么做?
If you pass the same parameter to several different methods in the same object, then you can simplify the API by passing the parameter once (eliminating duplication). You can run this refactoring in reverse if you find that an instance variable is used in only one method.
这个方法简单实用。
经过31章的学习,作者认为我们已经完完全全有能力成为一个合格的TDD程序员了。在这一章里,作者又总结出很多TDD初学者的问题,给予回答。答案都是一些很实用的忠告,特摘取如下。
作者并没有盲目强调测试重要,也没有说任何地方都要写测试。作者说:
Unless you have reason to distrust it, don’t test code from others.
“Write tests until fear is transformed into boredom.”
只在你不相信你的code时,对他进行测试。测试也不是为了测试而测试,或者为了代码的健壮,测试只是为了减少你的焦虑。让你不要从担心变成烦躁,仅此而已。
哎!真是醍醐灌顶!相见恨晚!
什么样的测试不好?
- Long setup code. 如果为了做一个简单的测试,可能只有几行断言,而为此创建一个巨大的对象,那说明哪里肯定出问题了。
- Setup duplication. 如果你发现有几个测试拥有相同或相似的setup代码,那说明你的测试中有重复的地方。
- Long running tests. TDD tests that run a long time won’t be run often. Suites that take longer than ten minutes
inevitably get trimmed. 一个测试套件不要超过十分钟。否则,反复运行的机会就会急剧降低。 - Fragile tests. Tests that break unexpectedly suggest that one part of the application is surprisingly affecting another part. 如果测试被无情打断,说明你的程序里有耦合。
又被醍醐灌顶了!
TDD appears to stand this advice on its head: “Code for tomorrow, design for today.” Here’s what happens in practice. 不要为未来考虑那么多,简单的让测试通过,然后及时的做重构消除重复,反而能达到更好的设计框架(frameworks)。
这里说的和之前一直强调的思想很一致
If our knowledge of the implementation gives us confidence even without a test, then we will not write that test.
- The first criterion for your tests is confidence. Never delete a test if it reduces your confidence in the behavior of the system.
- The second criterion is communication. If you have two tests that exercise the same path through the code, but they speak to different scenarios for a reader, leave them alone.
这也是我想问的,作者的回答是这本书就是为了这个而写的。
There is a whole book (or books) to be written about switching to TDD when you have lots of code. What follows is necessarily only a teaser.
怎么做?
- So first we have to decide to limit the scope of our changes.
- Second, we have to break the deadlock between tests and refactoring.
- 怎么获得feedback
1) We can get feedback other ways than with tests, like working very carefully and with a partner.
2) We can get feedback at a gross level, like system-level tests that we know aren’t adequate but give us some confidence. With this feedback, we can make the areas we have to change more accepting of change.
名词解释:Extreme Programming - XP极限编程
极限编程是一个轻量级的、灵巧的软件开发方法;同时它也是一个非常严谨和周密的方法。它的基础和价值观是交流、朴素、反馈和勇气;即,任何一个软件项目都可以从四个方面入手进行改善:加强交流;从简单做起;寻求反馈;勇于实事求是。XP是一种近螺旋式的开发方法,它将复杂的开发过程分解为一个个相对比较简单的小周期;通过积极的交流、反馈以及其它一系列的方法,开发人员和客户可以非常清楚开发进度、变化、待解决的问题和潜在的困难等,并根据实际情况及时地调整开发过程。
TDD与XP的共通之处:
- Pairing
- Work fresh
- Continuous integration
- Simple design
- Refactoring
- Continuous delivery
%23Test-Driven%20Development%0A@%28%u5B66%u4E60%u7B14%u8BB0%29%5BProgramming%2C%20TDD%5D%0A%0A%5BTOC%5D%0A%0A%u6700%u8FD1%u5728%u8BFBKent%20Beck%u7684%u300ATest-Driven%20Development%20By%20Example%u300B%uFF0C%u8BA9%u6211%u5BF9%u8F6F%u4EF6%u5F00%u53D1%u6709%u4E86%u65B0%u7684%u8BA4%u8BC6%u3002%0A%3E%u5199test%u5E76%u4E0D%u4E3A%u4E86%u6D4B%u8BD5%u800C%u6D4B%u8BD5%uFF0C%u662F%u4E3A%u4E86%u589E%u5F3A%u5BF9%u7A0B%u5E8F%u7684%u4FE1%u5FC3%uFF0C%u51CF%u5C11%u7126%u8651%u3002%u90A3%u4E48%u5982%u4F55%u51CF%u5C11%u7126%u8651%u5462%uFF0C%u662F%u901A%u8FC7%u4E00%u6B21%u53EA%u5173%u5FC3%u4E00%u4EF6%u4E8B%u60C5%u3002%u5982%u4F55%u4E00%u6B21%u53EA%u5173%u5FC3%u4E00%u4EF6%u4E8B%u60C5%uFF0C%u8FD9%u5C31%u662FTDD%u3002%0A%0A%u672C%u4E66%u5206%u4E09%u4E2A%u90E8%u5206%0A1.%20The%20Money%20Example%0A2.%20The%20xUnit%20Example%0A3.%20Patterns%20for%20Test-Driven%20Development%0A%u524D%u4E24%u4E2A%u90E8%u5206%u8BB2%u4E86%u4E24%u4E2A%u4F8B%u5B50%uFF0C%u6765%u9610%u8FF0%u5982%u4F55%u8FDB%u884CTDD%u5F00%u53D1%uFF0C%u6700%u540E%u4E00%u4E2A%u90E8%u5206%u7B97%u662F%u603B%u7ED3%uFF0C%u5168%u4E66%u7684%u7CBE%u534E%u6240%u5728%u3002%u672C%u6587%u4E0D%u6253%u7B97%u8BB2%u8FD9%u4E24%u4E2A%u4F8B%u5B50%u4E86%uFF0C%u56E0%u4E3A%u4F8B%u5B50%u6700%u597D%u81EA%u5DF1%u624B%u5199%u4E00%u904D%uFF0C%u624D%u53EF%u4EE5%u52A0%u6DF1%u4E86%u89E3%u3002%u672C%u6587%u4E3B%u8981%u6458%u5F55%u4E00%u4E9BTDD%u7684%u6A21%u5F0F%u65B9%u6CD5%28pattern%29%u3002%0A%0A%u4E0B%u9762%u8FD9%u6BB5%u8BDD%u6765%u81EA%u7B2C%u4E00%u7AE0%uFF0C%u63D0%u7EB2%u6308%u9886%uFF1A%0A%3EMy%20goal%20is%20for%20you%20to%20see%20the%20rhythm%20of%20Test-Driven%20Development%20%28TDD%29%2C%20which%20can%20be%20summed%20up%20as%20follows.%0A%3E%201.%20Quickly%20add%20a%20test.%0A%3E%202.%20Run%20all%20tests%20and%20see%20the%20new%20one%20fail.%0A%3E%203.%20Make%20a%20little%20change.%0A%3E%204.%20Run%20all%20tests%20and%20see%20them%20all%20succeed.%0A%3E%205.%20Refactor%20to%20remove%20duplication.%0A%0A%3EThe%20surprises%20are%20likely%20to%20include.%0A%3E%20*%20How%20each%20test%20can%20cover%20a%20**small%20increment**%20of%20functionality.%0A%3E%20*%20How%20**small%20and%20ugly%20the%20changes**%20can%20be%20to%20make%20the%20new%20tests%20run.%0A%3E%20*%20How%20often%20the%20tests%20are%20run.%0A%3E%20*%20How%20many%20teensy-weensy%20steps%20make%20up%20the%20refactorings.%0A%0A%u8FD9%u5C31%u662FTDD%u7684%u7CBE%u9AD3%u3002%u6309%u4F5C%u8005%u7684%u8BF4%u6CD5%u5C31%u662F%60Green/Red/Green/Refactor%60%u8282%u594F%u3002%0A%u8FD9%u4E5F%u56DE%u7B54%u4E86%u6211%u4E00%u76F4%u4EE5%u6765%u7684%u56F0%u6270%uFF0C%u5230%u5E95%u5E94%u8BE5%u5982%u4F55%u8BBE%u8BA1%u4E00%u6BB5%u7A0B%u5E8F%uFF0C%u5230%u5E95%u8981%u8003%u8651%u591A%u5C11%u79CD%u60C5%u51B5%u3002%u6309%u7167TDD%u7684%u5EFA%u8BAE%u5C31%u662F%uFF0C%u9996%u5148%u5199%u6D4B%u8BD5%uFF0C%u7136%u540E%u4EE3%u7801%u600E%u4E48%u5FEB%u600E%u4E48%u6765%uFF0C%u4E0D%u7BA1%u6709%u591Augly%uFF0C%u7136%u540E%u5982%u679C%u53D1%u73B0%u91CD%u590D%uFF0C%u53CA%u65F6%u505A%u91CD%u6784%u3002%u4E0D%u8981%u8003%u8651%u4EC0%u4E48%u6269%u5C55%u6027%u4E4B%u7C7B%u7684%uFF0C%u8FD9%u4E2A%u8BBE%u8BA1%u65B9%u5F0F%u672C%u8EAB%u5C31%u80FD%u63D0%u4F9B%u3002%u56E0%u4E3A%u5B83%u63D0%u9AD8%u4E86%u8FD0%u884C%u6D4B%u8BD5%u548C%u91CD%u6784%u7684%u9891%u7387%uFF0C%u65E2%u63D0%u5347%u4E86%u7F16%u7801%u8005%u5BF9%u7A0B%u5E8F%u7684%u4FE1%u5FC3%uFF0C%u4E5F%u964D%u4F4E%u4E86%u4E0D%u5408%u7406%u8BBE%u8BA1%u7684%u53EF%u80FD%u6027%u3002%0A%0A%23%23Chapter%2025.%20Test-Driven%20Development%20Patterns%0A%u4E66%u4E2D%u5F15%u5165%u4E86%u5F88%u591A%u5F71%u50CF%u56FE%28Influence%20Diagram%29%uFF0C%u5F88%u5F62%u8C61%u7684%u8BF4%u660E%u4E86%uFF0C%u5F15%u5165TDD%u80FD%u7ED9%u6211%u4EEC%u65E5%u5E38%u7684%u5F00%u53D1%u5DE5%u4F5C%u5F15%u5165%u4EC0%u4E48%u597D%u5904%u3002%0A%21%5BAlt%20text%5D%28./1455616022147.png%29%0A%3EThis%20is%20a%20positive%20feedback%20loop.%20%0A%3E-%20The%20more%20stress%20you%20feel%2C%20the%20less%20testing%20you%20will%20do.%20%0A%3E-%20The%20less%20testing%20you%20do%2C%20the%20more%20errors%20you%20will%20make.%20%0A%3E-%20The%20more%20errors%20you%20make%2C%20the%20more%20stress%20you%20feel.%20%0A%3E-%20Rinse%20and%20repeat.%0A%0A%23%23%23Isolated%20Test%0A%u80FD%u5C06%u5927%u5757%u7684%u6D4B%u8BD5%u5206%u5272%u6210%u677E%u8026%u5408%u7684%u5C0F%u6D4B%u8BD5%u4E0D%u5BB9%u6613%uFF0C%u4F46%u662F%u597D%u5904%u5927%u5927%u7684%u3002%u4F5C%u8005%u8BF4%uFF1A%0A%3EI%20never%20knew%20exactly%20how%20to%20achieve%20high%20cohesion%20and%20loose%20coupling%20regularly%20until%20I%20started%20writing%20isolated%20tests.%0A%23%23%23Test%20List%0A%u6211%u4EEC%u8981%u600E%u4E48%u5F00%u59CBTDD%u5462%uFF1F%u4F5C%u8005%u7ED9%u51FA%u4E86%u5F88%u5177%u64CD%u4F5C%u6027%u7684%u5EFA%u8BAE%uFF1A%0A%3EBefore%20you%20begin%2C%20write%20a%20list%20of%20all%20the%20tests%20you%20know%20you%20will%20have%20to%20write.%0A%u8FD9%u4E2Alist%u4E0A%u5199%u70B9%u4EC0%u4E48%u5462%uFF1F%0A%3E%201.%20Put%20on%20the%20list%20examples%20of%20every%20operation%20that%20you%20know%20you%20need%20to%20implement.%20%u8981%u54EA%u4E9B%u64CD%u4F5C%u3002%0A%3E%202.%20for%20those%20operations%20that%20don%27t%20already%20exist%2C%20put%20the%20null%20version%20of%20that%20operation%20on%20the%20list.%u54EA%u4E9B%u64CD%u4F5C%u6CA1%u5B9E%u73B0%uFF0C%u5199%u4E0A%u5B58%u6839%u3002%0A%3E%203.%20List%20all%20of%20the%20refactorings%20that%20you%20think%20you%20will%20have%20to%20do%20in%20order%20to%20have%20clean%20code%20at%20the%20end%20of%20this%20session.%20%u5B8C%u6210%u4E4B%u540E%uFF0C%u5217%u51FA%u9700%u8981%u91CD%u6784%u7684%u5730%u65B9%u3002%u800C%u5982%u679C%u6709%u5927%u7684%u91CD%u6784%u7684%u9700%u8981%u65F6%uFF0C%u653E%u5230later%20list%u91CC%u9762%uFF0C%u628A%u624B%u91CC%u7684%u4E8B%u60C5%u5904%u7406%u5B8C%uFF0C%u518D%u53BB%u5904%u7406%u5927%u7684%u91CD%u6784%u3002%0A%0A%u770B%u4F5C%u8005%u65F6%u523B%u5728%u5F3A%u8C03TDD%u5C31%u662Ftiny%20steps%uFF1A%0A%3E%20The%20pure%20form%20of%20TDD%2C%20wherein%20you%20are%20never%20more%20than%20one%20change%20away%20from%20a%20green%20bar%2C%20is%20like%20that%20three-out-of-four%20rule.%0A%23%23%23Test%20First%0A%u4EC0%u4E48%u65F6%u5019%u5199%u6D4B%u8BD5%uFF1F%0A%u7B54%uFF1A%u5728%u5199code%u4E4B%u524D%uFF0C%u56E0%u4E3A%u4E00%u65E6%u4F60%u5F00%u59CB%u5199code%uFF0C%u4F60%u53EF%u80FD%u5C31%u8EAB%u9677%u6CE5%u6F6D%uFF0C%u65E0%u6687%u987E%u53CAtest%20case%u4E86%u3002%0A%0A%23%23Chapter%2026.%20Red%20Bar%20Patterns%0A%23%23%23One%20Step%20Test%0A%3EEach%20test%20should%20represent%20one%20step%20toward%20your%20overall%20goal.%0A%23%23%23Starter%20Test%0A%u5982%u4F55%u5F00%u59CB%u5199%u6D4B%u8BD5%uFF1F%u4E0D%u8981%u4E00%u5F00%u59CB%u5C31%u60F3%u5199%u4E00%u4E2A%u771F%u6B63%u7684%u6D4B%u8BD5%uFF0C%u56E0%u4E3A%u8FD9%u6837%u4F1A%u4E00%u6B21%u9762%u5BF9%u592A%u591A%u7684%u95EE%u9898%u3002%u4F5C%u8005%u5EFA%u8BAE%u5C1D%u8BD5%u4ECE%u4E00%u4E2A%u7B80%u5355%u7684%u6D4B%u8BD5%u8F93%u5165%u8F93%u51FA%u5F00%u59CB%u3002%u8FD9%u6837%u4EE5%u4FBF%u4E8E%u4F60%u4ECEGreenxun%u8FC5%u901F%u5F00%u59CB%u4F60%u7684Green/Red/Green/Refactor%u8282%u594F%u3002%u4F5C%u8005%u4E3E%u4E86%u4E00%u4E2A%u4EA7%u751F%u591A%u8FB9%u5F62%u7684%u4F8B%u5B50%uFF1A%0A%60%60%60%20java%0AReducer%20r%3D%20new%20Reducer%28new%20Polygon%28%29%29%3B%20%0AassertEquals%280%2C%20reducer.result%28%29.npoints%29%3B%0A%60%60%60%0A%u4E0D%u592A%u61C2%u8FD9%u4E2A%u95EE%u9898%uFF0C%u4E0D%u8FC7%u53EF%u4EE5%u4ECE%u4E2D%u770B%u51FA%u4F5C%u8005%u5BF9TDD%u7684%u89E3%u91CA%u3002%u95EE%u9898%u672C%u8EAB%u662F%u8981%u89E3%u4E00%u4E2A%u964D%u4F4E%u591A%u8FB9%u5F62%u8FB9%u6570%u7684%u95EE%u9898%u3002%u4F5C%u8005%u7684%u542F%u52A8%u6D4B%u8BD5%u5C31%u662F%u4E00%u4E2A%u201C0%u201D%u8FB9%u591A%u8FB9%u5F62%uFF0C%u90A3%u7ED3%u679C%u4E00%u5B9A%u662F%220%22%u8FB9%u591A%u8FB9%u5F62%u3002%u5BF9%u5B9E%u9645%u610F%u4E49%u5F88%u8352%u8BDE%uFF0C%u4F46%u5BF9%u6D4B%u8BD5%u6765%u8BF4%u662F%u4E00%u4E2A%u4E0D%u9519%u7684%u5F00%u59CB%u3002%u7B26%u5408%u6211%u4EEC%u7684%u9884%u671F%uFF0C%u7B80%u5355%u800C%u4E14%u53C8%60represent%20one%20step%20toward%20your%20overall%20goal%60%u3002%0A%23%23%23Explanation%20Test%0A%u8FD9%u4E00%u7AE0%u662F%u5728%u8BF4%u5982%u4F55%u4E0D%u7528%u5F3A%u8FEB%u7684%u624B%u6BB5%u8BA9%u975ETDD%u7684%u56E2%u961F%u6210%u5458%u80FD%u8F6C%u5411TDD%u3002%u5F53%u5F00%u53D1%u8005%u5411%u4F60%u89E3%u91CA%u4ED6%u7684%u7A0B%u5E8F%u65F6%uFF0C%u4F60%u90FD%u53EF%u4EE5%u5C06%u4E4B%u8F6C%u5316%u4E3Atest%20case%u3002%0A%u672C%u4E66%u8FD8%u6709%u4E00%u4E2A%u5F88%u6709%u7279%u8272%u7684%u5730%u65B9%u662F%uFF0C%u8D2F%u7A7F%u5168%u6587%uFF0C%u4F5C%u8005%u603B%u662F%u5728%u7ECF%u610F%u4E0D%u7ECF%u610F%u95F4%u6D41%u9732%u7740%u5404%u79CD%u5B9D%u8D35%u7684%u5DE5%u4F5C%u7ECF%u9A8C%uFF1A%u5982%u4F55%u957F%u671F%u4FDD%u6301%u66F4%u9AD8%u6548%u7684%u7F16%u7A0B%uFF1F%u5982%u679C%u4F60%u662F%u7ECF%u7406%uFF0C%u4F60%u5982%u4F55%u4E0E%u4F60%u7684%u56E2%u961F%u6210%u5458%u4E00%u8D77%u5DE5%u4F5C%uFF1F%u7B49%u7B49%u3002%u8FD9%u4E00%u7AE0%u5C31%u6709%u8FD9%u6837%u4E00%u53E5%u8BDD%uFF1A%0A%3EIf%20you%27re%20a%20manager%20or%20a%20leader%2C%20you%20can%27t%20force%20anyone%20to%20change%20the%20way%20they%20work.%0A%3EWhat%20can%20you%20do%3F%20A%20simple%20start%20is%20to%20start%20asking%20for%20explanations%20in%20terms%20of%20test%20cases%3A%20%22Let%20me%20see%20if%20I%20understand%20what%20you%27re%20saying.%0A%0A%23%23%23Learning%20Test%0A%u8FD9%u4E00%u7AE0%u8BB2%u4E86%u6D4B%u8BD5%u9A71%u52A8%u5F00%u53D1%u9488%u5BF9%u96C6%u6210%u7B2C%u4E09%u65B9%u5E93%u7684%u4F18%u52BF%u3002%0A%23%23%23Regression%20Test%0A%3E**%u56DE%u5F52%u6D4B%u8BD5**%u662F%u6307%u4FEE%u6539%u4E86%u65E7%u4EE3%u7801%u540E%uFF0C%u91CD%u65B0%u8FDB%u884C%u6D4B%u8BD5%u4EE5%u786E%u8BA4%u4FEE%u6539%u6CA1%u6709%u5F15%u5165%u65B0%u7684%u9519%u8BEF%u6216%u5BFC%u81F4%u5176%u4ED6%u4EE3%u7801%u4EA7%u751F%u9519%u8BEF%u3002%0A%3E%u81EA%u52A8%u56DE%u5F52%u6D4B%u8BD5%u5C06%u5927%u5E45%u964D%u4F4E%u7CFB%u7EDF%u6D4B%u8BD5%u3001%u7EF4%u62A4%u5347%u7EA7%u7B49%u9636%u6BB5%u7684%u6210%u672C%u3002%u56DE%u5F52%u6D4B%u8BD5%u4F5C%u4E3A%u8F6F%u4EF6%u751F%u547D%u5468%u671F%u7684%u4E00%u4E2A%u7EC4%u6210%u90E8%u5206%uFF0C%u5728%u6574%u4E2A%u8F6F%u4EF6%u6D4B%u8BD5%u8FC7%u7A0B%u4E2D%u5360%u6709%u5F88%u5927%u7684%u5DE5%u4F5C%u91CF%u6BD4%u91CD%uFF0C%u8F6F%u4EF6%u5F00%u53D1%u7684%u5404%u4E2A%u9636%u6BB5%u90FD%u4F1A%u8FDB%u884C%u591A%u6B21%u56DE%u5F52%u6D4B%u8BD5%u3002%u5728%u6E10%u8FDB%u548C%u5FEB%u901F%u8FED%u4EE3%u5F00%u53D1%u4E2D%uFF0C%u65B0%u7248%u672C%u7684%u8FDE%u7EED%u53D1%u5E03%u4F7F%u56DE%u5F52%u6D4B%u8BD5%u8FDB%u884C%u7684%u66F4%u52A0%u9891%u7E41%uFF0C%u800C%u5728%u6781%u7AEF%u7F16%u7A0B%u65B9%u6CD5%u4E2D%uFF0C%u66F4%u662F%u8981%u6C42%u6BCF%u5929%u90FD%u8FDB%u884C%u82E5%u5E72%u6B21%u56DE%u5F52%u6D4B%u8BD5%u3002%u56E0%u6B64%uFF0C%u901A%u8FC7%u9009%u62E9%u6B63%u786E%u7684%u56DE%u5F52%u6D4B%u8BD5%u7B56%u7565%u6765%u6539%u8FDB%u56DE%u5F52%u6D4B%u8BD5%u7684%u6548%u7387%u548C%u6709%u6548%u6027%u662F%u975E%u5E38%u6709%u610F%u4E49%u7684%u3002%0A%u2014%u2014%20**%u767E%u5EA6%u767E%u79D1**%0A%0A%u672C%u4E66%u8BB2%u7684%u56DE%u5F52%u6D4B%u8BD5%u6982%u5FF5%u53EF%u80FD%u7565%u6709%u4E0D%u540C%u3002%u6309%u7167%u6211%u7684%u7406%u89E3%uFF0C%u4F5C%u8005%u662F%u60F3%u8BF4%uFF0C%u5728%u8FDB%u884C%u5B8C%u6240%u6709%u7684%u7F16%u7801%u4EE5%u540E%uFF0C%u4F60%u5DF2%u7ECF%u5BF9%u4F60%u7684%u8981%u5B9E%u73B0%u7684%u6240%u6709%u4E1C%u897F%u5B8C%u6210%u4E86unknown%24%5Crightarrow%20%24known%u7684%u8FC7%u7A0B%u3002%u90A3%u4E48%u73B0%u5728%uFF0C%u4F60%u53EF%u4EE5%u56DE%u8FC7%u5934%u770B%u770B%uFF0C%u5982%u679C%u8FD9%u65F6%u5019%u91CD%u65B0%u5199%u4E00%u4EFD%u6D4B%u8BD5%u4F60%u5E94%u8BE5%u5199%u4EC0%u4E48%u3002%0A%0A%3E%u56DE%u5230%u4F60%u9488%u5BF9%u8FD9%u4E2A%u7CFB%u7EDF%u7684%u6700%u521D%u7684%u60F3%u6CD5%u6765%u5BF9%u6574%u4E2A%u7CFB%u7EDF%u505A%u4E00%u4E2A%u6D4B%u8BD5%u3002%u4EE5%u671F%u5BF9%u6574%u4E2A%u7CFB%u7EDF%u6709%u4E2A%u6574%u4F53%u7684%u8BC4%u4EF7%u3002%0A%23%23%23Break%0A%u52B3%u9038%u7ED3%u5408%u5BF9%u7A0B%u5E8F%u5458%u662F%u975E%u5E38%u975E%u5E38%u975E%u5E38%u91CD%u8981%u7684%uFF01%0A%3E%20**Shower%20Methodology**%3AIf%20you%20know%20what%20to%20type%2C%20then%20type.%20If%20you%20don%27t%20know%20what%20to%20type%2C%20then%20take%20a%20shower%2C%20and%20stay%20in%20the%20shower%20until%20you%20know%20what%20to%20type.%0A%3E**TDD%20Rule**%3A%20If%20you%20don%27t%20know%20what%20to%20type%2C%20then%20Fake%20It.%20If%20the%0Aright%20design%20still%20isn%27t%20clear%2C%20then%20Triangulate.%20%0A%0A%u770B%u770B%u4F5C%u8005%u63D0%u5021%u7684%u65F6%u95F4%u7BA1%u7406%0A%3E*%20At%20the%20scale%20of%20hours%2C%20keep%20a%20water%20bottle%20by%20your%20keyboard%20so%20that%20biology%20provides%20the%20motivation%20for%20regular%20breaks.%0A%3E*%20At%20the%20scale%20of%20a%20day%2C%20commitments%20after%20regular%20work%20hours%20can%20help%20you%20to%20stop%20when%20you%20need%20sleep%20before%20progress.%0A%3E*%20At%20the%20scale%20of%20a%20week%2C%20weekend%20commitments%20help%20get%20your%20conscious%2C%20energysucking%20thoughts%20off%20work.%20%28My%20wife%20swears%20I%20get%20my%20best%20ideas%20on%20Friday%20evening.%29%0A%3E*%20At%20the%20scale%20of%20a%20year%2C%20mandatory%20vacation%20policies%20help%20you%20refresh%20yourself%20completely.%20The%20French%20do%20this%20right%u2014two%20contiguous%20weeks%20of%20vacation%20aren%27t%20enough.%20You%20spend%20the%20first%20week%20decompressing%2C%20and%20the%20second%20week%20getting%20ready%20to%20go%20back%20to%20work.%20Therefore%2C%20three%20weeks%2C%20or%20better%20four%2C%20are%20necessary%20for%20you%20to%20be%20your%20most%20effective%20the%20rest%20of%20the%20year.%0A%23%23%23Do%20Over%0A%3EWhat%20do%20you%20do%20when%20you%20are%20feeling%20lost%3F%20Throw%20away%20the%20code%20and%20start%20over.%0A%u4F5C%u8005%u5728%u8FD9%u4E00%u7AE0%u544A%u8BC9%u4F60%uFF0C%u4E0D%u8981%u5728%u6CE5%u6F6D%u8D8A%u9677%u8D8A%u6DF1%uFF0C%u4E0D%u8981%u5BB3%u6015%u91CD%u65B0%u5F00%u59CB%u3002%u6362%u4E2A%u4F19%u4F34%uFF0C%u6362%u4E2A%u601D%u8DEF%uFF0C%u4F60%u53EF%u80FD%u6709%u66F4%u597D%u7684%u53D1%u73B0%u3002%0A%23%23Chapter%2027.%20Testing%20Patterns%0A%23%23%23Child%20Test%0A%u8FD9%u91CC%u8BF4%u7684%u662F%uFF0C%u5982%u679C%u4F60%u89C9%u5F97%u8FD9%u4E2A%u6D4B%u8BD5%u592A%u5927%uFF0C%u90A3%u5C31%u8981%u628A%u8FD9%u4E2A%u5927%u7684%u6D4B%u8BD5%u7834%u6210%u82E5%u5E72%u4E2A%u5C0F%u7684%u6D4B%u8BD5%uFF0C%u7136%u540E%u770B%u770B%u6709%u6CA1%u6709%u8FDB%u5C55%u3002%0A%23%23%23Mock%20Object%0A%3EHow%20do%20you%20test%20an%20object%20that%20relies%20on%20an%20expensive%20or%20complicated%20resource%3F%20Create%20a%20fake%20version%20of%20the%20resource%20that%20answers%20constants.%0A%3EIf%20you%20want%20to%20use%20Mock%20Objects%2C%20you%20can%27t%20easily%20store%20expensive%20resources%20in%20global%20variables.%u8981%u8BB0%u5F97%u6E05%u7406%u73B0%u573A%uFF0C%u5426%u5219%u4E07%u4E00%u5176%u4ED6%u7684%u4EE3%u7801%u628A%u4ED6%u5F53%u4F5C%u771F%u7684object%u5728%u4F7F%u7528%uFF0C%u5C31%u4F1A%u51FA%u5927%u95EE%u9898%u3002%u6240%u4EE5%u7F16%u7801%u6700%u57FA%u672C%u7684%u51C6%u5219%u662F%u5C11%u7528%u5168%u5C40%u53D8%u91CF%u3002%0A%3EMock%20Objects%20encourage%20you%20down%20the%20path%20of%20carefully%20considering%20the%20visibility%20of%20every%20object%2C%20reducing%20the%20coupling%20in%20your%20designs.%20%u8FD9%u91CC%u8BF4%u7684%u662F%uFF0C%u5982%u679C%u4F60%u7528Mock%20Object%uFF0C%u4F60%u5C31%u8981%u601D%u8003%u6211%u5230%u5E95%u8BE5%u4E0D%u8BE5%u628A%u8FD9%u4E2A%u5E9E%u7136%u5927%u7269%u66B4%u9732%u7ED9%u8FD9%u6BB5%u4EE3%u7801%u3002%u8FD9%u79CD%u601D%u8003%u6709%u52A9%u4E8E%u51CF%u5C0F%u8026%u5408%u6027%u3002%0A%23%23%23Self%20Shunt%0A%3EHow%20do%20you%20test%20that%20one%20object%20communicates%20correctly%20with%20another%3F%20Have%20the%20object%20under%20test%20communicate%20with%20the%20test%20case%20instead%20of%20with%20the%20object%20it%20expects.%0A%0A**Without%20Self%20Shunt**%0A%60%60%60python%0A%23%20ResultListenerTest%0Adef%20testNotification%28self%29%3A%20%0A%09result%3D%20TestResult%28%29%20%0A%09listener%3D%20ResultListener%28%29%20%0A%09result.addListener%28listener%29%20%0A%09WasRun%28%22testMethod%22%29.run%28result%29%0A%09assert%201%20%3D%3D%20listener.count%20%0A%0A%23%20ResultListener%0Aclass%20ResultListener%3A%0A%09def%20__init__%28self%29%3A%20%0A%09%09self.count%3D%200%20%0A%09def%20startTest%28self%29%3A%20%0A%09%09self.count%3D%20self.count%20+%201%20%0A%60%60%60%0A%0A**With%20Self%20Shunt**%0A%60%60%60python%0A%23%20ResultListenerTest%0Adef%20testNotification%28self%29%3A%20%0A%09self.count%3D%200%20%0A%09result%3D%20TestResult%28%29%0A%09result.addListener%28self%29%0A%09WasRun%28%22testMethod%22%29.run%28result%29%20%0A%09assert%201%20%3D%3D%20self.count%0Adef%20startTest%28self%29%3A%0A%09self.count%3D%20self.count%20+%201%0A%60%60%60%0A%0A%u8FD9%u91CC%u4E3E%u5F97%u4F8B%u5B50%u4E0D%u662F%u5F88%u660E%u767D%u3002%u4E2D%u6587%u8BD1%u672C%u91CC%uFF0C%u5C06self%20shunt%u7FFB%u8BD1%u6210%u81EA%u5206%u6D41%uFF0C%u611F%u89C9%u4E5F%u662F%u5B57%u9762%u7FFB%u8BD1%u3002%u6211%u7406%u89E3%u8FD9%u91CC%u60F3%u8BF4%u7684%u662F%uFF0C%u4E0D%u4E00%u5B9A%u8981%u5B9A%u4E49%u4E00%u4E2A%u7C7B%u6765%u505A%u5F53%u524D%u88AB%u6D4B%u8BD5%u7C7B%u6765%u505A%u7684%u4E8B%u60C5%uFF0C%u53EF%u4EE5%u5728test%20case%u91CC%u9762%u7528%u8BE5%u7C7B%u66B4%u9732%u7684%u63A5%u53E3%u6765%u505A%u4E8B%u60C5%u3002%0A%3ESelf%20Shunt%20may%20require%20that%20you%20use%20**Extract%20Interface**%20to%20get%20an%20interface%20to%20implement.%20You%20will%20have%20to%20decide%20whether%20extracting%20the%20interface%20is%20easier%2C%20or%20if%20testing%20the%20existing%20class%20as%20a%20**black%20box**%20is%20easier.%0A%0A%u8FD9%u91CC%u53C8%u770B%u6655%u4E86%uFF0Cself%20shunt%u8DDFextract%20interface%u6709%u5565%u5173%u7CFB%u3002%0A%3E%u5F53%u6211%u4EEC%u6CE8%u610F%u5230%u5728%u6211%u4EEC%u4EE3%u7801%u7ED3%u6784%u4E2D%u8D85%u8FC7%u4E00%u4E2A%u7C7B%u4F7F%u7528%u5230%u53E6%u4E00%u4E2A%u7279%u6B8A%u7C7B%u4E2D%u7684%u540C%u4E00%u4E2A%u65B9%u6CD5%u65F6%uFF0C%u6B64%u65F6%u5E94%u8BE5%u5C06%u8BE5%u7C7B%u4E2D%u7684%u65B9%u6CD5%u63D0%u53D6%u51FA%u6765%u653E%u5230%u4E00%u4E2A%u63A5%u53E3%uFF08interface%uFF09%u4E2D%uFF0C%u5E76%u5BF9%u5916%u63D0%u4F9B%u8FD9%u4E2A%u63A5%u53E3%u4EE5%u4FBF%u88AB%u7528%u6237%u7C7B%u8BBF%u95EE%u3001%u4F7F%u7528%uFF0C%u8FD9%u6837%u6709%u5229%u4E8E%u6253%u7834%u539F%u6765%u591A%u4E2A%u7C7B%u4E0E%u8FD9%u4E2A%u7279%u6B8A%u7C7B%u7684%u4F9D%u8D56%u5173%u7CFB%u3002%u8FD9%u4E2A%u91CD%u6784%u624B%u6CD5%u5F88%u5BB9%u6613%u5B9E%u73B0%uFF0C%u66F4%u91CD%u8981%u7684%u662F%u8FD9%u6837%u505A%u6709%u5229%u4E8E%u677E%u8026%u5408%u3002%20%u2014%u2014%u7F51%u7EDC%u4E0A%u7684%u89E3%u91CA%0A%0A%u6211%u89C9%u5F97%u8FD9%u91CC%u4F5C%u8005%u63D0%u5230%u7684extract%20interface%uFF0C%u5E76%u4E0D%u662F%u6211%u4EEC%u8BF4%u7684%u91CD%u6784%u91CC%u7684extract%20interface%u3002%u8FD9%u91CC%u8BF4%u8981%u6D4B%u8BD5A%u7C7B%u548CB%u7C7B%u901A%u4FE1%uFF0C%u6211%u4EEC%u4E0D%u76F4%u63A5%u7528B%u7C7B%uFF0C%u800C%u7528test%20case%u4E0EA%u7C7B%u901A%u4FE1%uFF0C%u4EE5%u6D4B%u8BD5%u901A%u4FE1%u7684A%u7C7B%u7AEF%u63A5%u53E3%u662F%u5426%u6B63%u786E%u3002extract%20interface%u8BF4%u7684%u662F%u4ECEB%u7C7B%u4E2Dextract%u63A5%u53E3%u51FA%u6765%u5230test%20case%u4E2D%u53BBimplement%u3002%u6216%u8005%u628AB%u7C7B%u76F4%u63A5%u5F53%u9ED1%u76D2%u5B50%u5728test%20case%u4E2D%u4F7F%u7528%u3002%0A%23%23%23Log%20String%0A%u8FD9%u4E2A%u5F88%u6709%u7528%u3002%u6709%u4E9B%u51FD%u6570%u6216%u8005%u8FC7%u7A0B%u662F%u6CA1%u6709%u8FD4%u56DE%u503C%u7684%uFF0C%u6211%u4EEC%u5C31%u53EF%u4EE5%u7528Log%20String%u7684%u65B9%u5F0F%u6765%u67E5%u770B%u8BE5%u8FC7%u7A0B%u6216%u51FD%u6570%u6709%u6CA1%u6709%u88AB%u8C03%u7528%u3002%u7C7B%u4F3C%u6211%u4EEC%u770Btrace%20log%u4E00%u6837%uFF0C%u901A%u8FC7%u770Btrace%20log%u80FD%u77E5%u9053%u673A%u5668%u6267%u884C%u7684%u4EE3%u7801%u662F%u5426%u6B63%u786E%u3002%0A%23%23%23Crash%20Test%20Dummy%0A%3EHow%20do%20you%20test%20error%20code%20that%20is%20unlikely%20to%20be%20invoked%3F%20Invoke%20it%20anyway%20with%20a%20special%20object%20that%20throws%20an%20exception%20instead%20of%20doing%20real%20work.%0A%23%23%23Broken%20Test%0A%u4F5C%u8005%u7ECF%u5E38%u80FD%u7ED9%u51FA%u4E00%u4E9B%u5F88%u5B9E%u7528%u7684%u5C0F%u7ECF%u9A8C%uFF0C%u8FD9%u91CC%u53C8%u6765%u4E86%uFF0C%u770B%u4E0B%u9762%u3002%0A%3EHow%20do%20you%20leave%20a%20programming%20session%20when%20you%27re%20programming%20alone%3F%20Leave%20the%20last%20test%20broken.%0A%0A%u5F53%u4F60%u8981%u8F6C%u53BB%u505A%u53E6%u5916%u4E00%u4EF6%u4E8B%u60C5%uFF0C%u800C%u624B%u5934%u4E0A%u7684%u4E8B%u60C5%u53C8%u6CA1%u7ED3%u675F%u65F6%u600E%u4E48%u529E%uFF1F%u8BA9%u6700%u540E%u4E00%u4E2A%u6D4B%u8BD5%u6545%u610F%u7684%u4E0D%u901A%u8FC7%u3002%u8FD9%u6837%u4E0B%u6B21%u5C31%u77E5%u9053%u4ECE%u54EA%u513F%u5F00%u59CB%uFF0C%u7136%u540E%u80FD%u8FC5%u901F%u4E0A%u624B%u3002%u918D%u9190%u704C%u9876%u554A%uFF01%u4EE5%u524D%u90FD%u662F%u8BA4%u4E3A%u505A%u53E6%u5916%u4E00%u4EF6%u4E8B%u60C5%u4E4B%u524D%u8981%u628A%u624B%u5934%u4E0A%u7684%u4E8B%u60C5%u5F04%u7684%u5C3D%u5584%u5C3D%u7F8E%uFF0C%u5176%u5B9E%u4E0D%u7136%u3002%u600E%u4E48%u505A%u5229%u4E8E%u540E%u671F%u7EE7%u7EED%u8FDB%u884C%uFF0C%u90A3%u5C31%u5E94%u8BE5%u600E%u4E48%u505A%u3002%0A%23%23%23Clean%20Check-in%0A%3EYou%20will%20occasionally%20find%20a%20test%20broken%20in%20the%20integration%20suite%20when%20you%20try%20to%20check%20in.%20What%20to%20do%3F%0A**The%20simplest%20rule**%20is%20to%20just%20throw%20away%20your%20work%20and%20start%20over.%20The%20broken%20test%20is%20pretty%20strong%20evidence%20that%20you%20didn%27t%20know%20enough%20to%20program%20what%20you%20just%20programmed.%0A%u4EC0%u4E48%u4EC0%u4E48%uFF1F%u7ADF%u7136%u8981%u6211%u4EEC%u6254%u6389%u6211%u4EEC%u505A%u7684%uFF0C%u7136%u540E%u91CD%u65B0%u5F00%u59CB%u3002%u5C31%u56E0%u4E3A%u6211%u4EEC%u7684%u6D4B%u8BD5%u548Cintegration%u6D4B%u8BD5%u7ED3%u679C%u4E0D%u4E00%u81F4%u3002%0A%u5927%u5E08%u7684%u8BDD%u603B%u662F%u6709%u9053%u7406%u7684%uFF0C%u4EE5%u540E%u8981%u597D%u597D%u8003%u8651%u8FD9%u4E00%u70B9%u3002%0A%23%23Chapter%2028.%20Green%20Bar%20Patterns%0A%23%23%23Fake%20It%20%28%27Til%20You%20Make%20It%29%0A%u505A%u5047%u4EE3%u7801%u8BA9%u6D4B%u8BD5%u901A%u8FC7%uFF0C%u7136%u540E%u518D%u4E00%u9879%u4E00%u9879%u5730%u5B8C%u6210%0A%23%23%23Triangulate%0A%u4F5C%u8005%u63D0%u5230%u7684%u6240%u8C13%u4E09%u89D2%u6CD5%u3002%u901A%u4FD7%u7684%u8BF4%uFF0C%u5C31%u662F%u53EA%u5728%u5FC5%u8981%u7684%u65F6%u5019%u505A%u62BD%u8C61%u6216%u901A%u7528%u5316%u5904%u7406%u3002%u901A%u5E38%u662F%u5728TDD%u7684%u91CD%u6784%u8FD9%u4E00%u6B65%u5B8C%u6210%u8FD9%u4E2A%u62BD%u8C61%u7684%u8FC7%u7A0B%u3002%u518D%u901A%u4FD7%u70B9%u8BF4%uFF0C%u5C31%u662F%u591A%u5F04%u51E0%u4E2Acase%uFF0C%u518D%u603B%u7ED3%u5171%u540C%u70B9%uFF0C%u4E4B%u540E%u505A%u62BD%u8C61%0A%u4ECE%u53E6%u4E00%u4E2A%u89D2%u5EA6%uFF0C%u4F5C%u8005%u53C8%u8BF4%u4E86%uFF1A%0A%3EI%20only%20use%20Triangulation%20when%20I%27m%20really%2C%20really%20unsure%20about%20the%20correct%20abstraction%20for%20the%20calculation.%20Otherwise%20I%20rely%20on%20either%20Obvious%20Implementation%20or%20Fake%20It.%0A%23%23%23One%20to%20Many%0A%3EHow%20do%20you%20implement%20an%20operation%20that%20works%20with%20collections%20of%20objects%3F%20Implement%20it%20without%20the%20collections%20first%2C%20then%20make%20it%20work%20with%20collections.%0A%0A%u4E2D%u5FC3%u601D%u60F3%u8FD8%u662F%u4E00%u4E2A%uFF0C%u4ECE%u663E%u800C%u6613%u89C1%u7684%u5730%u65B9%u5F00%u59CB%uFF0C%u6BCF%u6B21%u53EA%u505A%u4E00%u5C0F%u6B65%uFF0C%u7136%u540E%u4E00%u6B65%u4E00%u6B65%u5230%u8FBE%u6700%u7EC8%u7684%u76EE%u7684%u5730%u3002%u770B%u4E0B%u9762%u7684%u4F8B%u5B50%uFF1A%0A%60%60%60java%0A//%201.%20fake%20code%0Apublic%20void%20testSum%28%29%20%7B%20%0A%09assertEquals%285%2C%20sum%285%29%29%3B%20%0A%7D%20%0Aprivate%20int%20sum%28int%20value%29%20%7B%0A%09return%20value%3B%0A%7D%0A%0A//%202.%20add%20arrays%0Apublic%20void%20testSum%28%29%20%7B%0A%09assertEquals%285%2C%20sum%285%2C%20new%20int%5B%5D%20%7B5%7D%29%29%3B%0A%7D%0Aprivate%20int%20sum%28int%20value%2C%20int%5B%5D%20values%29%20%7B%0A%09return%20value%3B%0A%7D%0A%0A//%203.%20add%20real%20sum%20functionality%0Apublic%20void%20testSum%28%29%20%7B%0A%09assertEquals%285%2C%20sum%285%2C%20new%20int%5B%5D%20%7B5%7D%29%29%3B%0A%7D%0Aprivate%20int%20sum%28int%20value%2C%20int%5B%5D%20values%29%20%7B%0A%09int%20sum%3D%200%3B%20%0A%09for%20%28%20int%20i%3D%200%3B%20i%3Cvalues.length%3B%20i++%29%0A%09%09sum%20+%3D%20values%5Bi%5D%3B%0A%09return%20sum%3B%0A%7D%0A%0A//%204.%20delete%20unnecessary%20code%0Apublic%20void%20testSum%28%29%20%7B%20%0A%09assertEquals%285%2C%20sum%28new%20int%5B%5D%20%7B5%7D%29%29%3B%0A%7D%0Aprivate%20int%20sum%28%20int%5B%5D%20values%29%20%7B%0A%09int%20sum%3D%200%3B%20%0A%09for%20%28int%20i%3D%200%3B%20i%3Cvalues.length%3B%20i++%29%0A%09%09sum%20+%3D%20values%5Bi%5D%3B%0A%09return%20sum%3B%20sum%3B%0A%7D%0A%60%60%60%0A%u7EC6%u7EC6%u4F53%u4F1A%u5176%u95F4%u7684%u5DEE%u522B%u5427%u3002%0A%23%23Chapter%2029.%20xUnit%20Patterns%0A%23%23%23Assertion%0A%3EHow%20do%20you%20check%20that%20tests%20worked%20correctly%3F%20%0A%3EWrite%20boolean%20expressions%20that%20automate%20your%20judgment%20about%20whether%20the%20code%20worked.%0A%0A%u4F5C%u8005%u5728%u8FD9%u4E00%u7AE0%u53C8%u7ED9%u51FA%u4E86%u4E00%u4E2A%u5F88%u6709%u6307%u5BFC%u610F%u4E49%u7684%u89C2%u70B9%u3002%0A%3E%u5C3D%u91CF%u4E0D%u8981%u505A%u767D%u76D2%u6D4B%u8BD5%uFF0C%u5982%u679C%u4F60%u60F3%u505A%uFF0C%u8BF4%u660E%u4F60%u7684%u8BBE%u8BA1%u6709%u95EE%u9898%0A%23%23%23Fixture%0A%u8FD9%u91CC%u4F5C%u8005%u629B%u51FA%u4E86%u4E00%u4E2A%u95EE%u9898%uFF0C%u5982%u679C%u6709%u591A%u4E2A%u6D4B%u8BD5%u6709%u7C7B%u4F3C%u7684%u4EE3%u7801%u600E%u4E48%u529E%uFF1F%u4E24%u4E2A%u9009%u62E9%uFF1A%0A1.%20test%20case%u7C7B%u5B9A%u4E49%u4E2D%u62BD%u8C61%u51FA%u7C7B%u4F3CsetUp%u7684%u51FD%u6570%uFF0C%u6765%u505A%u91CD%u590D%u7684%u5DE5%u4F5C%0A2.%20%u6216%u8005%u7528%u7C7B%u4F3C%u4EE3%u7801%u751F%u6210%u5668%u7684%u4E1C%u897F%u6765%u4E3A%u6240%u6709%u9700%u8981%u7684%u6D4B%u8BD5%u751F%u6210fixture%0A%0A%u65B9%u6CD51%u6709%u65F6%u5019%u66F4%u7B26%u5408DRY%u539F%u5219%uFF0C%u4F46%u5B83%u7684%u7F3A%u70B9%u662F%uFF0C%u4F60%u8981%u8BB0%u4F4FsetUp%u91CC%u9762%u5230%u5E95%u505A%u4E86%u4EC0%u4E48%u3002%u4E24%u79CD%u65B9%u6CD5%u90FD%u53EF%u4EE5%uFF0C%u9009%u62E9%u66F4%u5408%u9002%u7684%u3002%0A%0A%23%23%23External%20Fixture%0A%u6240%u8C13%u7684%u5916%u90E8fixture%uFF0C%u5C31%u662F%u8981%u4FDD%u8BC1%u6D4B%u8BD5%u80FD%u6062%u590D%u73AF%u5883%u914D%u7F6E%u3002%0A%3E%u5728%u6240%u6709%u6D4B%u8BD5%u5F00%u59CB%u524D%u8C03setUp%uFF0C%u6240%u6709%u6D4B%u8BD5%u7ED3%u675F%u540E%u8C03tearDown%u3002%u8FD9%u5C31%u662FExternal%20Fixture%0A%23%23%23Test%20Method%0A%u8FD9%u4E00%u8282%u9488%u5BF9%u5982%u4F55%u5199%u6D4B%u8BD5%u65B9%u6CD5%u7ED9%u51FA%u4E86%u4E00%u4E9B%u5EFA%u8BAE%u3002%0A1.%20hierarchy%0A*%20Module%20%24%5Crightarrow%24%20Framework%0A*%20Class%20%24%5Crightarrow%24%20internal%20fixture%0A*%20Method%20%24%20%5Crightarrow%24%20test%20case%0A2.%20%u540D%u5B57%u957F%u6CA1%u5173%u7CFB%uFF0C%u8981%u8BA9%u4EE5%u540E%u7684%u8BFB%u8005%u6709%u8DB3%u591F%u7684%u7EBF%u7D22%u77E5%u9053%u4E3A%u4EC0%u4E48%u8981%u5199%u8FD9%u4E2Atest%0A3.%20%u6D4B%u8BD5%u4EE3%u7801%u8981%u591F%u76F4%u89C9%uFF0C%u8981%u591F%u7B80%u6D01%uFF08%u6700%u77ED3%u884C%uFF09%20%0A4.%20%u6CE8%u91CA%u91CC%u5199outline%uFF0C%u4E00%u9897%u661F%u8868%u793A%u4E00%u7EA7%uFF0C%u4F5C%u8005%u8BF4%u4ED6%u4E00%u822C%u51992-3%u7EA7outline%0A%60%60%60%20cpp%0A/*%20Adding%20to%20tuple%20spaces.%20*/%20%0A/*%20Taking%20from%20tuple%20spaces.%20*/%0A/**%20Taking%20a%20non-existent%20tuple.%20**/%20%0A/**%20Taking%20an%20existing%20tuple.%20**/%20%0A/**%20Taking%20multiple%20tuples.%20**/%20%0A/*%20Reading%20from%20tuple%20space.%20*/%0A%60%60%60%0A%23%23%23Exception%20Test%0A%u8FD9%u4E00%u8282%u8BF4%u7684%u662F%uFF0C%u5982%u679C%u5F85%u6D4B%u7684%u51FD%u6570%u9700%u8981%u629B%u51FA%u5F02%u5E38%uFF0C%u7528%u5982%u4E0B%u7684%u65B9%u6CD5%uFF1A%0A%60%60%60java%0Apublic%20void%20testMissingRate%28%29%20%7B%0A%09try%20%7B%0A%09%09exchange.findRate%28%22USD%22%2C%20%22GBP%22%29%3B%20%0A%09%09fail%28%29%3B%20//%20%u5982%u679C%u6CA1%u629B%u5F02%u5E38%u5C31fail%0A%09%7D%20catch%20%28IllegalArgumentException%20expected%29%20%7B%20%0A%09%7D%0A%7D%0A%60%60%60%0A%23%23%23All%20Tests%0A%3EHow%20do%20you%20run%20all%20tests%20together%3F%20Make%20a%20suite%20of%20all%20the%20suites%u2014one%20for%20each%20package%2C%20and%20one%20aggregating%20the%20package%20tests%20for%20the%20whole%20application.%0A%60%60%60java%0Apublic%20class%20AllTests%20%7B%0A%09//%20%u8FD9%u91CC%u6709%u4E2Amain%uFF0C%u65B9%u4FBF%u76F4%u63A5%u8C03%u7528%u7A0B%u5E8F%u5F00%u59CB%u6D4B%u8BD5%0A%09public%20static%20void%20main%28String%5B%5D%20args%29%20%7B%20%0A%09%09junit.swingui.TestRunner.run%28AllTests.class%29%3B%20%0A%09%7D%0A%09//%20%u8FD9%u91CC%u6709%u4E2Asuite%u5305%u542B%u4E86%u6240%u6709test%20case%0A%09public%20static%20Test%20suite%28%29%20%7B%20%0A%09%09TestSuite%20result%3D%20new%20TestSuite%28%22TFD%20tests%22%29%3B%20%0A%09%09result.addTestSuite%28MoneyTest.class%29%3B%20%0A%09%09result.addTestSuite%28ExchangeTest.class%29%3B%20%0A%09%09result.addTestSuite%28IdentityRateTest.class%29%3B%20%0A%09%09return%20result%3B%0A%09%7D%0A%7D%0A%60%60%60%0A%23%23Chapter%2030.%20Design%20Patterns%0A%u4F5C%u8005%u5728%u8FD9%u4E00%u7AE0%u5F15%u5165%u4E86%u4E00%u4E9BTDD%u4E2D%u4F1A%u7528%u5230%u7684%u8BBE%u8BA1%u6A21%u5F0F%u3002%u6709%u4E9B%u662F%u5199%u6D4B%u8BD5%u65F6%u7528%u7684%uFF0C%u6709%u7684%u662F%u91CD%u6784%u65F6%u7528%u7684%uFF0C%u6709%u7684%u4E24%u8005%u7686%u53EF%u3002%u4E0B%u9762%u662F%u672C%u7AE0%u6240%u6709%u6A21%u5F0F%u7684%u603B%u7EB2%u3002%0A%21%5BAlt%20text%5D%28./1456036251528.png%29%0A%23%23%23Command%0A%3EWhat%20do%20you%20do%20when%20you%20need%20the%20invocation%20of%20a%20computation%20to%20be%20more%20complicated%20than%20a%20simple%20method%20call%3F%0A%3EMake%20an%20object%20for%20the%20computation%20and%20invoke%20it.%0A%0A%u8FD9%u91CC%u6240%u8BF4%u7684%u547D%u4EE4%u662F%u8BF4%uFF0C%u628A%u8981%u8C03%u7528%u7684%u65B9%u6CD5%u5C01%u88C5%u6210%u4E00%u4E2Aobject%uFF0C%u7136%u540E%u8C03%u7528%u90A3%u4E2Aobject%0A%0A%23%23%23Value%20Object%0A%u628A%u4E00%u4E9B%u5E38%u91CF%u53C2%u6570%u505A%u6210object%u6765%u4F20%u9012%0A%3EWhen%20implementing%20a%20Value%20Object%2C%20every%20operation%20has%20to%20return%20a%20fresh%20object%2C%20%0A%3EAll%20Value%20Objects%20have%20to%20implement%20equality%20%0A%0A%23%23%23Null%20Object%0A%60%60%60java%0Apublic%20boolean%20setReadOnly%28%29%20%7B%20%0A%09SecurityManager%20guard%20%3D%20System.getSecurityManager%28%29%3B%20%0A%09if%20%28guard%20%21%3D%20null%29%20%7B%20%29%20%7B%0A%09%09guard.canWrite%28path%29%3B%0A%09%7D%20%0A%09return%20fileSystem.setReadOnly%28this%29%3B%0A%7D%0A%60%60%60%0A%u4E0A%u9762%u8FD9%u4E2A%u4F8B%u5B50getSecurityManager%u5728%u4E0D%u6210%u529F%u65F6%u8FD4%u56DENull%uFF0C%u7136%u540E%u8C03%u7528%u8005%u518D%u5224%u65AD%u8FD4%u56DE%u503C%u4EE5%u51B3%u5B9A%u4F1A%u4E0D%u4F1A%u7528%u7A7A%u6307%u9488%u6765%u8C03%u7528%u6210%u5458%u53D8%u91CF%u3002%0A%u8FD9%u662F%u4E00%u4E2A%u5F88%u5E38%u89C1%u7684%u505A%u6CD5%uFF0C%u8FD9%u6837%u505A%u4E0D%u597D%u7684%u662F%uFF0C%u901A%u5E38%u8FD9%u79CD%u5224%u65AD%u4F1A%u975E%u5E38%u975E%u5E38%u591A%uFF0C%u800C%u4E14%u7ED9%u4EBA%u4E00%u79CD%u201C%u4F1A%u4E0D%u4F1A%u6709%u54EA%u91CC%u6CA1%u5224%u65AD%uFF0C%u4EE5%u540E%u4F1A%u5BFC%u81F4crash%uFF1F%u201D%u7684%u62C5%u5FE7%u3002%0ANull%20Object%u8BBE%u8BA1%u6A21%u5F0F%u5C31%u662F%u8BA9getSecurityManager%u8FD4%u56DE%u4E00%u4E2A%u7279%u6B8A%u7684object%u6765%u53D6%u4EE3Null%uFF0C%u8FD9%u6837%u8BA9%u6240%u6709%u7684%u64CD%u4F5C%u90FD%u7EDF%u4E00%uFF0C%u4EE5%u51CF%u5C11%u91CD%u590D%u4EE3%u7801%u3002%0A%0A%23%23%23Template%20Method%0A%u6A21%u677F%u65B9%u6CD5%u3002%0A%3EWhen%20you%20find%20two%20variants%20of%20a%20sequence%20in%20two%20subclasses%2C%20you%20need%20to%20gradually%20move%20them%20closer%20together.%20Once%20you%27ve%20extracted%20the%20parts%20that%20are%20different%20from%20other%20methods%2C%20what%20you%20are%20left%20with%20is%20the%20Template%20Method.%20%0A%60%60%60java%0A//%20TestCase%0Apublic%20void%20runBare%28%29%20throws%20Throwable%20%7B%20%0A%09setUp%28%29%3B%20%0A%09try%20%7B%20%0A%09%09runTest%28%29%3B%0A%09%7D%0A%09finally%20%7B%0A%09%09tearDown%28%29%3B%0A%09%7D%0A%7D%0A%60%60%60%0A%0A%23%23%23Pluggable%20Object%0A%u4E2D%u6587%u8BD1%u540D%u4E3A%u63D2%u5165%u5F0F%u5BF9%u8C61%u3002%u5F88%u96BE%u7406%u89E3%u54EA%u91CC%u6709%u8FD9%u4E2A%u6240%u8C13%u7684%u201C%u63D2%u5165%u201D%u3002%u8FD9%u4E2A%u8BBE%u8BA1%u6A21%u5F0F%u6838%u5FC3%u7684%u601D%u60F3%u5C31%u662F%u6D88%u9664%60if...else...%60%u7684%u60C5%u51B5%u3002%0A%u770B%u4F5C%u8005%u7684%u4F8B%u5B50%uFF0C%u89E3%u91CA%u7684%u5F88%u6E05%u695A%uFF1A%0A%60%60%60java%0A//%20Without%20Pluggable%20Object%0AFigure%20selected%3B%20%0Apublic%20void%20mouseDown%28%29%20%7B%20%0A%09selected%3D%20findFigure%28%29%3B%0A%09if%20%28selected%20%21%3D%20null%29%20//%20%u5230%u5904%u662Fnull%u5224%u65AD%0A%09%09select%28selected%29%3B%20%0A%7D%0Apublic%20void%20mouseMove%28%29%20%7B%0A%09if%20%28selected%20%21%3D%20null%29%0A%09%09move%28selected%29%3B%20%0A%09else%0A%09%09moveSelectionRectangle%28%29%3B%20%0A%7D%0Apublic%20void%20mouseUp%28%29%20%7B%0A%09if%20%28selected%20%3D%3D%20null%29%0A%09%09selectAll%28%29%3B%0A%7D%20%0A%0A//%20With%20Pluggable%20Object%0ASelectionMode%20mode%3B%0Apublic%20void%20mouseDown%28%29%20%7B%0A%09selected%3D%20findFigure%28%29%3B%0A%09//%20%u5168%u90E8%u5F52%u7ED3%u5230mode%u5B50%u7C7B%u91CC%u9762%u53BB%u5904%u7406%0A%09if%20%28selected%20%21%3D%20null%29%0A%09%09mode%3D%20SingleSelection%28selected%29%3B%0A%09else%0A%09%09mode%3D%20MultipleSelection%28%29%3B%0A%7D%0Apublic%20void%20mouseMove%28%29%20%7B%0A%09mode.mouseMove%28%29%3B%0A%7D%0Apublic%20void%20mouseUp%28%29%20%7B%0A%09mode.mouseUp%28%29%3B%0A%7D%0A%60%60%60%0A%23%23%23Pluggable%20Selector%0A%u8FD9%u4E00%u8282%u7684%u8BBE%u8BA1%u6A21%u5F0F%u548C%u4E0A%u4E00%u8282%u7684%u6781%u4E3A%u7C7B%u4F3C%u3002%u8FD9%u6B21%u8981%u5904%u7406%u6389%u7684%u662F%60switch...case...%60%u800C%u53C8%u4E0D%u7528%u5B50%u7C7B%u7EE7%u627F%u7684%u65B9%u5F0F%u6765%u52A8%u6001%u8C03%u7528%u3002%u8FD9%u91CC%u4F7F%u7528%u7684%u65B9%u6CD5%u53EB%u53CD%u5C04%u3002%0A%3E%u53CD%u5C04%uFF1A%u901A%u8FC7%u81EA%u8EAB%u7684%u5C5E%u6027%uFF08%u5982%u7C7B%u540D%uFF0C%u65B9%u6CD5%u540D%uFF09%u6765%u505A%u76F8%u5173%u903B%u8F91%0A%0A%u540C%u6837%u770B%u4F5C%u8005%u7684%u4F8B%u5B50%0A%60%60%60java%0Avoid%20print%28%29%20%7B%20%0A%09Method%20runMethod%3D%20getClass%28%29.getMethod%28printMessage%2C%20null%29%3B%0A%09runMethod.invoke%28this%2C%20new%20Class%5B0%5D%29%3B%0A%7D%0A%60%60%60%0A%u770B%u5230%u4E86%u6CA1getClass%uFF0CgetMethod%uFF0C%u8FD9%u5C31%u662F%u53CD%u5C04%u3002%u6709%u4E86%u8FD9%u4E2A%u51FD%u6570%u65E2%u4E0D%u9700%u8981switch%u6765%u8C03%u7528%u4E0D%u540C%u7684printxxx%u51FD%u6570%28printHtml%2C%20printXml...%29%uFF0C%u4E5F%u4E0D%u9700%u8981%u5B9A%u4E49%u5B50%u7C7B%u6765%u8C03%u7528%u4E0D%u540C%u7684print%u51FD%u6570%u3002%0A%u4F5C%u8005%u5728%u672C%u8282%u6700%u540E%u7ED9%u51FA%u4E86%u4E00%u6BB5%u5FE0%u544A%uFF1A%0A%3EPluggable%20Selector%20can%20definitely%20be%20overused.%20The%20biggest%20problem%20with%20it%20is%20tracing%20code%20to%20see%20whether%20a%20method%20is%20invoked.%20Use%20Pluggable%20Selector%20only%20when%20you%20are%20cleaning%20up%20a%20fairly%20straightforward%20situation%20in%20which%20each%20of%20a%20bunch%20of%20**subclasses%20has%20only%20one%20method.**%0A%23%23%23Factory%20Method%0A%u4F5C%u8005%u8BF4%u5DE5%u5382%u65B9%u6CD5%u63D0%u4F9B%u4E86%u7075%u6D3B%u6027%u3002%u6211%u662F%u6CA1%u770B%u51FA%u6765%u3002%u5BF9%u6BD4%u4E0B%u9762%u4E24%u6BB5%u4EE3%u7801%uFF0C%u8FD9%u91CC%u589E%u52A0%u4E86%u7075%u6D3B%u6027%uFF1F%0A%60%60%60java%0Apublic%20void%20testMultiplication%28%29%20%7B%0A%09Dollar%20five%3D%20Dollar%20five%3D%20new%20Dollar%285%29%3B%0A%09assertEquals%28new%20Dollar%2810%29%2C%20five.times%282%29%29%3B%20%0A%09assertEquals%28new%20Dollar%2815%29%2C%20five.times%283%29%29%3B%0A%7D%0A%0Apublic%20void%20testMultiplication%28%29%20%7B%20%0A%09Dollar%20five%20%3D%20Money.dollar%285%29%3B%20//%20%u5DE5%u5382%u65B9%u6CD5%0A%09assertEquals%28new%20Dollar%2810%29%2C%20five.times%282%29%29%3B%20%0A%09assertEquals%28new%20Dollar%2815%29%2C%20five.times%283%29%29%3B%20%0A%7D%0A//%20Money%0Astatic%20Dollar%20dollar%28%20Dollar%20dollar%28int%20amount%29%20%7B%20%0A%09return%20new%20Dollar%28amount%29%3B%0A%7D%0A%60%60%60%0A%u4F5C%u8005%u6700%u540E%u7ED9%u51FA%u4E86%u4E00%u70B9%u5FE0%u544A%0A%3E%20You%20have%20to%20remember%20that%20the%20method%20is%20really%20creating%20an%20object%2C%20even%20though%20it%20doesn%27t%20look%20like%20a%20constructor.%20Use%20Factory%20Method%20only%20when%20you%20need%20the%20flexibility%20it%20creates.%20%0A%0A%23%23%23Imposter%0A%u4E2D%u6587%u8BD1%u540D%u4E3A%u5192%u540D%u9876%u66FF%u3002%u6839%u636E%u6587%u4E2D%u7684%u9610%u8FF0%uFF0C%u6211%u7406%u89E3%u8FD9%u91CC%u8BB2%u7684%u4E0D%u662F%u4E00%u79CD**%u8BBE%u8BA1**%u6A21%u5F0F%uFF0C%u800C%u53EA%u662F%u4E00%u79CD%u6A21%u5F0F%uFF0C%u4E00%u79CD%u7279%u6B8A%u60C5%u51B5%u3002%u6307%u7684%u662F%u5982%u679C%u67D0%u4E00%u6BB5%u4EE3%u7801%uFF0C%u53EA%u5C06%u5176%u4E2D%u7684%u4E00%u4E2A%u540D%u5B57%uFF08%u7C7B%u3001%u65B9%u6CD5%u6216%u8005%u53D8%u91CF%u7B49%u7B49%uFF09%u66FF%u6362%u6210%u53E6%u5916%u4E00%u4E2A%u540D%u5B57%uFF0C%u5C31%u80FD%u5F97%u5230%u4E00%u6BB5%u65B0%u7684%u4EE3%u7801%uFF0C%u79F0%u4E3A%u5192%u540D%u9876%u66FF%u3002%u53D1%u73B0%u53EF%u4EE5Imposter%u7684%u60C5%u51B5%uFF0C%u5176%u5B9E%u5C31%u662F%u53D1%u73B0%u91CD%u590D%u3002%0A%60%60%60java%0A//%20%u4E0B%u9762%u8FD9%u4E24%u6BB5%u4EE3%u7801%u5C31%u53EA%u662F%u5C06RectangleFigure%u6362%u6210OvalFigure%uFF0C%u6B64%u662F%u4E3AImposter%u5192%u540D%u9876%u66FF%u3002%0AtestRectangle%28%29%20%7B%0A%09Drawing%20d%3D%20new%20Drawing%28%29%3B%0A%09d.addFigure%28new%20RectangleFigure%280%2C%2010%2C%2050%2C%20100%29%29%3B%20RectangleFigure%280%2C%2010%2C%2050%2C%20100%29%29%3B%0A%09RecordingMedium%20brush%3D%20new%20RecordingMedium%28%29%3B%20%0A%09d.display%28brush%29%3B%20%0A%09assertEquals%28%22rectangle%200%2010%2050%20100%5Cn%22%2C%20brush.log%28%29%29%3B%0A%7D%0A%0AtestOval%28%29%20%7B%0A%09Drawing%20d%3D%20new%20Drawing%28%29%3B%0A%09d.addFigure%28new%20OvalFigure%280%2C%2010%2C%2050%2C%20100%29%29%3B%20%0A%09RecordingMedium%20brush%3D%20new%20RecordingMedium%28%29%3B%20%0A%09d.display%28brush%29%3B%0A%09assertEquals%28%22oval%200%2010%2050%20100%5Cn%22%2C%20brush.log%28%29%29%3B%20%0A%7D%0A%60%60%60%0A%u4F5C%u8005%u8FD8%u63D0%u5230%u53E6%u5916%u4E24%u4E2A%u5728%u91CD%u6784%u4E2D%u7ECF%u5E38%u7528%u5230%u7684%u8BBE%u8BA1%u6A21%u5F0F%uFF0C%u672C%u8D28%u5176%u5B9E%u5C31%u662FImposter%20%u2014%u2014%20Null%20Object%20%26%20Composite%u3002%0A%0A%23%23%23Composite%0A%u6839%u636E%u4E0A%u4E00%u7AE0%u4F5C%u8005%u7684%u89C2%u70B9%uFF0C%u7EC4%u5408%u5176%u5B9E%u5C31%u662F%u4E00%u79CD%u5192%u540D%u9876%u66FF%u3002%u4F5C%u8005%u5728%u672C%u8282%u5F00%u5934%u8BF4%u4E86%uFF1A%0A%3EHow%20do%20you%20implement%20**an%20object**%20whose%20behavior%20is%20the%20composition%20of%20the%20behavior%20of%20a%20list%20of%20other%20objects%3F%20Make%20it%20an%20**Imposter**%20for%20**the%20component%20objects**.%0A%0A%u8BA9%u8FD9%u4E2A%u5BF9%u8C61%u79F0%u4E3A%u7EC4%u6210%u4ED6%u7684%u5BF9%u8C61%u7684%u5192%u540D%u9876%u66FF%u8005%u3002%u8BF4%u8D77%u6765%u5F88%u62D7%u53E3%u3002%u770B%u4F8B%u5B50%uFF1A%0A%60%60%60java%0A//%20Transaction%0ATransaction%28Money%20value%29%20%7B%0A%09this.value%3D%20value%3B%0A%7D%0A//%20Account%0ATransaction%20transactions%5B%5D%3B%0AMoney%20balance%28%29%20%7B%0A%09Money%20sum%3D%20Money.zero%28%29%3B%0A%09for%20%28int%20i%3D%200%3B%20i%20%3C%20transactions.length%3B%20i++%29%20%0A%09%09sum%3D%20sum.plus%28transactions%5Bi%5D.value%29%3B%0A%09return%20sum%3B%0A%7D%0A%60%60%60%0A%u8FD9%u4E2A%u5199%u6CD5%u7684%u7F3A%u70B9%u662F%uFF0C%u5F53%u8981%u7B97OverallAccount%u65F6%uFF0C%u6CA1%u8F99%u4E86%uFF0C%u8981%u5B9A%u4E49%u4E00%u4E2A%u65B0%u7C7B%u6765%u5904%u7406%u3002%u4E00%u4E2AOverallAccount%u662F%u4E00%u7EC4Account%u3002%u4F46OverallAccount%u5176%u5B9E%u8981%u505A%u7684%u4E5F%u662F%u4E00%u79CD%u7D2F%u52A0%uFF0C%u548CAccount%u505A%u7684%u5E76%u6CA1%u6709%u4EC0%u4E48%u533A%u522B%u3002%u8FD9%u5C31%u662F%u91CD%u590D%u3002%0A%u89E3%u51B3%u7684%u529E%u6CD5%u5C31%u662F%u6240%u8C13%u7684Composite%u3002%u628A%u8981%u7D2F%u52A0%u7684%u4E1C%u897F%u62BD%u8C61%u51FA%u6765%u79F0%u4E3AHoldings%uFF0CAccount%u7D2F%u52A0%u7684%u662FHoldings%uFF0COverallAccount%u7D2F%u52A0%u7684%u4E5F%u662FHoldings%u3002%0A%60%60%60java%0AHolding%20holdings%5B%5D%3B%0AMoney%20balance%28%29%20%7B%0A%09Money%20sum%3D%20Money.zero%28%29%3B%0A%09for%20%28%20%28int%20i%3D%200%3B%20i%20%3C%20holdings.length%3B%20i++%29%0A%09sum%3D%20sum.plus%28holdings%5Bi%5D.balance%28%29%29%3B%0A%09return%20sum%3B%0A%7D%0A%60%60%60%0A%u5199%u5230%u8FD9%u91CC%uFF0C%u6211%u4ECD%u7136%u4E0D%u662F%u7279%u522B%u660E%u767D%u4F55%u4E3AComposite%uFF08%u62FC%u5408%uFF0C%u590D%u5408%u7684%u610F%u601D%uFF09%u3002%u4F46%u6211%u4EEC%u662F%u7AD9%u5728%u5DE8%u4EBA%u7684%u80A9%u8180%u4E0A%uFF0C%u5BF9%u6BD4%u9605%u8BFB%u4E86%u4E2D%u6587%u8BD1%u672C%u540E%u6709%u4E86%u4E00%u70B9%u70B9%u611F%u89C9%u3002%u8FD9%u4E2A%u8BBE%u8BA1%u6A21%u5F0F%u7684%u4E2D%u6587%u8BD1%u540D%u4E3A-**%u9012%u5F52%u7EC4%u5408**%u3002%u6240%u8C13%u9012%u5F52%uFF0C%u5E94%u5F53%u6307%u7684%u662F%uFF0C%u901A%u8FC7%u6982%u5FF5%u4E0A%u7684%u62BD%u8C61%uFF0C%u5C06%u4E00%u4E2A%u5927%u7684%u6DF7%u5408%u7C7B%u5199%u6210%u4E00%u4E9B%u66F4%u62BD%u8C61%u7684%u6982%u5FF5%u7684%u7EC4%u5408%u3002%u4F8B%u5982%3A%20%0A%3EOvreallAccount%20%3C-%20Account%20%3C-%20Transactions%0A%3EOverallAccount%20%3C-%20Holding%20%26%20Account%20%3C-Holding%0A%0A%u901A%u8FC7%u9012%u5F52%u5730%u5C06OverallAccount%u5192%u540D%u9876%u66FF%u6210Holding%uFF0C%u4ECE%u800C%u4F7FOverallAccount%u6210%u4E3A%u4E00%u4E2AAccount%u6765%u8FBE%u5230%u6D88%u9664%u91CD%u590D%u3002%0A%u4F5C%u8005%u5728%u672C%u8282%u6700%u540E%u7ED9%u51FA%u4E86%u4F55%u65F6%u4F7F%u7528Composite%u7684%u5EFA%u8BAE%uFF1A%0A%3EAs%20is%20obvious%20from%20this%20discussion%2C%20I%27m%20still%20not%20able%20to%20articulate%20how%20to%20guess%20when%20a%20collection%20of%20objects%20is%20just%20a%20collection%20of%20objects%20and%20when%20you%20really%20have%20a%20Composite.%20The%20good%20news%20is%2C%20since%20you%27re%20getting%20good%20at%20refactoring%2C%20%3Cb%3E%3Cfont%20color%3D%22IndianRed%22%3Ethe%20moment%20the%20duplication%20appears%3C/font%3E%3C/b%3E%2C%20you%20can%20introduce%20Composite%20and%20watch%20program%20complexity%20disappear.%0A%0A%23%23%23Collecting%20Parameter%0A%u770B%u4E0D%u61C2%uFF0C%u4E0D%u77E5%u6240%u4E91%0A%0A%23%23%23Singleton%0A%u8FD9%u4E2A%u5C31%u662F%u5F88%u8457%u540D%u7684%u5355%u4F8B%u6A21%u5F0F%uFF0C%u7ECF%u5E38%u4F1A%u7528%u5230%u3002%u4F5C%u8005%u6CA1%u6709%u8BE6%u52A0%u9610%u91CA%uFF0C%u53EA%u662F%u544A%u8BEB%u8BFB%u8005%u4E0D%u8981%u7528%u5168%u5C40%u53D8%u91CF%28global%20variable%29%u3002%u6211%u731C%u4F5C%u8005%u7684%u610F%u601D%u662F%uFF0C%u5982%u679C%u8981%u7528%u5168%u5C40%u53D8%u91CF%uFF0C%u5C31%u7528%u5355%u4F8B%u6A21%u5F0F%u7684%u7C7B%u5BF9%u8C61%u5427%u3002%0A%0A%23%23Chapter%2031.%20Refactoring%0A%u5728TDD%u4E2D%uFF0C%u91CD%u6784%u8981%u505A%u7684%u5C31%u662F%uFF0C%u4FDD%u8BC1%u6240%u6709%u6D4B%u8BD5%u901A%u8FC7%u7684%u60C5%u51B5%u4E0B%uFF0C%u964D%u4F4E%u91CD%u590D%uFF08code%u4E4B%u95F4%uFF0Ccode%u4E0Etest%u4E4B%u95F4%uFF09%u3002%0A%23%23%23Reconcile%20Differences%0A%3E-%20How%20do%20you%20unify%20two%20similar%20looking%20pieces%20of%20code%3F%20Gradually%20bring%20them%20closer.%20Unify%20them%20only%20when%20they%20are%20absolutely%20identical.%20%u4E0D%u8981%u5F3A%u5236%u6574%u5408%uFF0C%u7B49%u4ED6%u4EEC%u5B8C%u5168%u76F8%u7B49%u4E86%u518D%u8BF4%0A%3E-%20Such%20a%20leap-of-faith%20refactoring%20is%20exactly%20what%20we%27re%20trying%20to%20avoid%20with%20our%20strategy%20of%20small%20steps%20and%20concrete%20feedback.%20Although%20you%20can%27t%20always%20avoid%20leapy%20refactorings%2C%20you%20can%20reduce%20their%20incidence.%20%u4E0D%u8981%u505A%u8DE8%u8D8A%u5F0F%u7684%u91CD%u6784%u3002%0A%3E-%20Sometimes%20you%20need%20to%20approach%20reconciling%20differences%20backward%u2014that%20is%2C%20think%20about%20how%20the%20last%20step%20of%20the%20change%20could%20be%20trivial%2C%20then%20work%20backward.%20%u53CD%u5411%u601D%u8003%uFF0C%u5C3D%u91CF%u8BA9%u6BCF%u4E00%u6B65%u90FD%u975E%u5E38%u5C0F%28trivial%29%uFF0C%u8FD9%u6837%u65B9%u4FBF%u56DE%u9000%u3002%0A%0A%23%23%23Isolate%20Change%0A%u9694%u79BB%u6539%u52A8%uFF0C%u4E5F%u5C31%u662F%u6211%u4EEC%u901A%u5E38%u505A%u7684%u4E00%u4E9B%u62BD%u8C61%u5C01%u88C5%u3002%u628A%u53D8%u5316%u7684%u90E8%u5206%u7528%u63A5%u53E3%u5305%u88C5%u8D77%u6765%uFF0C%u8FD9%u6837%u5C31%u8FBE%u5230%u4E86%u9694%u79BB%u7684%u6548%u679C%u3002%0A%3ESome%20possible%20ways%20to%20isolate%20change%20are%20Extract%20Method%20%28the%20most%20common%29%2C%20Extract%20Object%2C%20and%20Method%20Object.%0A%0A%23%23%23Migrate%20Data%0A%u4E0D%u77E5%u6240%u4E91%0A%0A%23%23%23Extract%20Method%0A%3EHow%20do%20you%20make%20a%20long%2C%20complicated%20method%20easier%20to%20read%3F%20Turn%20a%20small%20part%20of%20it%20into%20a%20separate%20method%20and%20call%20the%20new%20method.%20%u5C06%u4E00%u4E2A%u5927%u51FD%u6570%u6253%u788E%uFF0C%u4ECE%u4E2D%u63D0%u53D6%u4E00%u4E9B%u65B9%u6CD5%u51FA%u6765%u3002%0A%23%23%23%23%20How%0A%3E1.%20Find%20a%20region%20of%20the%20method%20that%20would%20make%20sense%20as%20its%20own%20method.%20Bodies%20of%0Aloop%2C%20whole%20loops%2C%20and%20branches%20of%20conditionals%20are%20common%20candidates%20for%20extraction.%0A%3E2.%20Make%20sure%20that%20there%20are%20no%20assignments%20to%20temporary%20variables%20declared%20outside%20the%0Ascope%20of%20the%20region%20to%20be%20extracted.%0A%3E3.%20Copy%20the%20code%20from%20the%20old%20method%20to%20the%20new%20method.%20Compile%20it.%0A%3E4.%20For%20each%20temporary%20variable%20or%20parameter%20of%20the%20original%20method%20used%20in%20the%20new%0Amethod%2C%20add%20a%20parameter%20to%20the%20new%20method.%0A%3E5.%20Call%20the%20new%20method%20from%20the%20original%20method.%0A%0A%23%23%23%23Why%0A1.%20%u589E%u5F3A%u53EF%u8BFB%u6027%0A2.%20%u6D88%u9664%u91CD%u590D%uFF0C%u5F53%u4F60%u53D1%u73B0%u6709%u4E24%u4E2A%u5927%u51FD%u6570%uFF0C%u6709%u4E00%u90E8%u5206%u662F%u76F8%u540C%u7684%uFF0C%u90A3%u5C31%u53EF%u4EE5%u63D0%u53D6%u51FA%u6765%u79F0%u4E3A%u65B0%u7684%u65B9%u6CD5%u3002%0A3.%20%u901A%u8FC7inline%u628A%u4E00%u5806%u51FD%u6570%u653E%u5728%u4E00%u8D77%uFF0C%u7136%u540E%u770B%u770B%u54EA%u91CC%u53EF%u4EE5extract%20%uFF08%u8FD9%u4E00%u70B9%u6211%u611F%u89C9%u5E94%u8BE5%u653E%u5728How%u91CC%u9762%uFF0C%u4F5C%u8005%u662F%u60F3%u627F%u4E0A%u542F%u4E0B%uFF0C%u5C06inline%u5427%uFF09%0A%0A%23%23%23Inline%20Method%0Ainline%u5F88%u7384%u4E4E%uFF0C%u5176%u5B9E%u5C31%u662F%u628A%u51FD%u6570%u7684%u4EE3%u7801%u590D%u5236%u5230%u8C03%u7528%u7684%u5730%u65B9%u3002%u597D%u5904%u662F%u4EC0%u4E48%uFF0C%u4F5C%u8005%u6709%u8BDD%u8BF4%uFF1A%0A%3EYou%20might%20like%20the%20second%20version%20better%2C%20or%20you%20might%20not.%20The%20point%20to%20note%20here%20is%20that%20you%20can%20use%20Inline%20Method%20to%20play%20around%20with%20the%20**flow%20of%20control**.%20%0A%u5176%u5B9E%u5C31%u662F%u8BA9%u4F60%u628A%u62BD%u8C61%u51FA%u6765%u7684%u4E1C%u897F%u653E%u5230%u4E00%u8D77%u91CD%u65B0%u62BD%u8C61%uFF0C%u770B%u770B%u4F1A%u4E0D%u4F1A%u6709%u66F4%u597D%u7684%u7ED3%u679C%u3002%u8FD9%u4E5F%u662F%u4E00%u79CD%u91CD%u6784%u7684%u65B9%u6CD5%u3002%0A%0A%23%23%23Extract%20Interface%0A%23%23%23%23How%0A%u5982%u4F55%u5C06%u4E00%u4E2A%u7C7B%u62BD%u8C61%u6210%u4E00%u4E2Ainterface%u3002%u6240%u8C13%u7684interface%u5C31%u662F%u53EA%u6709%u7EAF%u865A%u51FD%u6570%u7684%u7C7B%u3002%0A%3E1.%20Declare%20an%20interface.%20Sometimes%20the%20name%20of%20the%20existing%20class%20should%20be%20the%20name%20of%20the%20interface%2C%20in%20which%20case%20you%20should%20first%20rename%20the%20class.%0A%3E2.%20Have%20the%20existing%20class%20implement%20the%20interface.%0A%3E3.%20Add%20the%20necessary%20methods%20to%20the%20interface%2C%20expanding%20the%20visibility%20of%20the%20methods%20in%20the%20class%20if%20necessary.%0A%3E4.%20Change%20type%20declarations%20from%20the%20class%20to%20the%20interface%20where%20possible.%0A%0A%23%23%23%23Why%0A%u8FD9%u91CC%u63D0%u5230%u4E86%u4E24%u79CD%u60C5%u51B5%uFF1A%0A1.%20%u5F53%u4F60%u9700%u8981%u62BD%u8C61%u51FA%u7236%u7C7B%u65F6%0A2.%20%u5F53%u4F60%u7528%u4E86Mock%20Object%uFF0C%u73B0%u5728%u8981%u4E3A%u8FD9%u4E2Amock%20object%u62BD%u8C61%u51FA%u4E00%u4E2A%u771F%u6B63%u7684%u63A5%u53E3%u65F6%u3002%u4F5C%u8005%u8FD9%u91CC%u63D0%u5230%u4E86%u4E00%u4E2A%u5C0F%u6280%u5DE7%uFF0C%u5728%u8FD9%u79CD%u60C5%u51B5%u4E0B%u547D%u540D%u662F%u4E00%u4E2A%u5934%u75BC%u7684%u95EE%u9898%u3002%u4E0D%u59A8%u5C31%u628A%u65B0%u7684interface%u79F0%u4E3AIXXX%uFF0C%u4F8B%u5982%uFF0C%u539F%u6765%u7684%u53EB%60File%60%uFF0C%u65B0%u7684%u63A5%u53E3%u5C31%u79F0%u4E3A%60IFile%60%0A%0A%23%23%23Move%20Method%0A%u5176%u5B9E%u5C31%u662F%u628A%u4E00%u4E2A%u7C7B%u7684%u65B9%u6CD5%u79FB%u5230%u53E6%u5916%u4E00%u4E2A%u7C7B%u91CC%u9762%u3002%u4F46%u4F5C%u8005%u63D0%u5230%u4E86%u4E00%u79CD%u60C5%u51B5%u4E0B%uFF0C%u8FD9%u4E2A%u65B9%u6CD5%u4E0D%u9002%u7528%u3002%u5C31%u662F%u8FD9%u4E2A%u65B9%u6CD5%u4FEE%u6539%u4E86%u539F%u5148%u90A3%u4E2A%u7C7B%u7684%u6570%u636E%u3002%0A%0A%23%23%23Method%20Object%0A%u8FD9%u4E2A%u8BDD%u9898%u597D%u50CF%u5F88%u65B0%u9C9C%uFF0C%u4E4B%u524D%u770B%u5230%u8FC7%u5F88%u591A%u6B21%uFF0C%u4F46%u5E76%u6CA1%u6709%u6DF1%u7A76%u3002%u5148%u770B%u770BHow%uFF1A%0A%23%23%23%23How%0A%3E1.%20Create%20an%20object%20with%20the%20same%20parameters%20as%20the%20method.%0A%3E2.%20Make%20the%20local%20variables%20also%20instance%20variables%20of%20the%20object.%0A%3E3.%20Create%20one%20method%20called%20run%28%29%2C%20whose%20body%20is%20the%20same%20as%20the%20body%20of%20the%20original%20method.%0A%3E4.%20In%20the%20original%20method%2C%20create%20a%20new%20object%20and%20invoke%20run%28%29.%0A%0A%u6709%u4EC0%u4E48%u597D%u5904%uFF1F%0A%23%23%23%23Why%0A%u4E24%u4E2A%u597D%u5904%0A1.%20%u5728%u6DFB%u52A0%u590D%u6742%u903B%u8F91%u7684%u65F6%u5019%uFF0C%u5982%u679C%u6709%u5BF9%u4E00%u4E2A%u8FC7%u7A0B%u6709%u591A%u79CD%u65B9%u6CD5%uFF0C%u90A3%u521B%u5EFA%u4E00%u4E2A%u65B9%u6CD5%u5BF9%u8C61%uFF0C%u518D%u6269%u5C55%u8D77%u6765%u5C31%u5F88%u7B80%u5355%0A2.%20%u5728%u4E00%u4E2A%u590D%u6742%u51FD%u6570%u91CCextract%20interface%u7684%u65F6%u5019%uFF0C%u901A%u5E38%u4E00%u6BB5code%u9700%u89815%uFF0C6%u4E2A%u53C2%u6570%uFF0C%u8FD9%u6837%u7684%u8BDD%uFF0C%u867D%u7136%u51FD%u6570%u62BD%u53D6%u51FA%u6765%u4E86%uFF0C%u4F46%u662F%u5199%u6CD5%u5E76%u6CA1%u6709%u7B80%u5316%u591A%u5C11%u3002%u8FD9%u65F6%u5019%u5982%u679C%u6709%u65B9%u6CD5%u5BF9%u8C61%uFF0C%u5219%u5728%u7C7B%u91CC%u9762%u662F%u4E00%u4E2A%u72EC%u7ACB%u7684%u540D%u5B57%u7A7A%u95F4%uFF0C%u4F1A%u63D0%u4F9B%u989D%u5916%u7684%u4FBF%u5229%u3002%0A%0A%23%23%23Add%20Parameter%0A%u5C31%u662F%u52A0%u4E00%u4E2A%u53C2%u6570%uFF0C%u6CA1%u4EC0%u4E48%u7279%u522B%u7684%0A%0A%23%23%23Method%20Parameter%20to%20Constructor%20Parameter%0A%u5C31%u662F%u628A%u65B9%u6CD5%u8C03%u7528%u7684%u53C2%u6570%uFF0C%u53D8%u6210%u7C7B%u6210%u5458%u53D8%u91CF%u3002%u4E3A%u4EC0%u4E48%u8981%u8FD9%u4E48%u505A%uFF1F%0A%3EIf%20you%20pass%20the%20same%20parameter%20to%20several%20different%20methods%20in%20the%20same%20object%2C%20then%20you%20can%20simplify%20the%20API%20by%20passing%20the%20parameter%20once%20%28eliminating%20duplication%29.%20You%20can%20run%20this%20refactoring%20in%20reverse%20if%20you%20find%20that%20an%20instance%20variable%20is%20used%20in%20only%20one%20method.%0A%0A%u8FD9%u4E2A%u65B9%u6CD5%u7B80%u5355%u5B9E%u7528%u3002%0A%0A%23%23Chapter%2032.%20Mastering%20TDD%0A%u7ECF%u8FC731%u7AE0%u7684%u5B66%u4E60%uFF0C%u4F5C%u8005%u8BA4%u4E3A%u6211%u4EEC%u5DF2%u7ECF%u5B8C%u5B8C%u5168%u5168%u6709%u80FD%u529B%u6210%u4E3A%u4E00%u4E2A%u5408%u683C%u7684TDD%u7A0B%u5E8F%u5458%u4E86%u3002%u5728%u8FD9%u4E00%u7AE0%u91CC%uFF0C%u4F5C%u8005%u53C8%u603B%u7ED3%u51FA%u5F88%u591ATDD%u521D%u5B66%u8005%u7684%u95EE%u9898%uFF0C%u7ED9%u4E88%u56DE%u7B54%u3002%u7B54%u6848%u90FD%u662F%u4E00%u4E9B%u5F88%u5B9E%u7528%u7684%u5FE0%u544A%uFF0C%u7279%u6458%u53D6%u5982%u4E0B%u3002%0A%0A%23%23%23What%20don%27t%20you%20have%20to%20test%3F%0A%u4F5C%u8005%u5E76%u6CA1%u6709%u76F2%u76EE%u5F3A%u8C03%u6D4B%u8BD5%u91CD%u8981%uFF0C%u4E5F%u6CA1%u6709%u8BF4%u4EFB%u4F55%u5730%u65B9%u90FD%u8981%u5199%u6D4B%u8BD5%u3002%u4F5C%u8005%u8BF4%uFF1A%0A%3EUnless%20you%20have%20reason%20to%20distrust%20it%2C%20don%27t%20test%20code%20from%20others.%20%0A%3E%22Write%20tests%20until%20fear%20is%20transformed%20into%20boredom.%22%0A%0A%u53EA%u5728%u4F60%u4E0D%u76F8%u4FE1%u4F60%u7684code%u65F6%uFF0C%u5BF9%u4ED6%u8FDB%u884C%u6D4B%u8BD5%u3002%u6D4B%u8BD5%u4E5F%u4E0D%u662F%u4E3A%u4E86%u6D4B%u8BD5%u800C%u6D4B%u8BD5%uFF0C%u6216%u8005%u4E3A%u4E86%u4EE3%u7801%u7684%u5065%u58EE%uFF0C%u6D4B%u8BD5%u53EA%u662F%u4E3A%u4E86%u51CF%u5C11%u4F60%u7684%u7126%u8651%u3002%u8BA9%u4F60%u4E0D%u8981%u4ECE%u62C5%u5FC3%u53D8%u6210%u70E6%u8E81%uFF0C%u4EC5%u6B64%u800C%u5DF2%u3002%0A%u54CE%uFF01%u771F%u662F%u918D%u9190%u704C%u9876%uFF01%u76F8%u89C1%u6068%u665A%uFF01%0A%0A%23%23%23How%20do%20you%20know%20if%20you%20have%20good%20tests%3F%0A%u4EC0%u4E48%u6837%u7684%u6D4B%u8BD5%u4E0D%u597D%uFF1F%0A%3E1.%20Long%20setup%20code.%20%u5982%u679C%u4E3A%u4E86%u505A%u4E00%u4E2A%u7B80%u5355%u7684%u6D4B%u8BD5%uFF0C%u53EF%u80FD%u53EA%u6709%u51E0%u884C%u65AD%u8A00%uFF0C%u800C%u4E3A%u6B64%u521B%u5EFA%u4E00%u4E2A%u5DE8%u5927%u7684%u5BF9%u8C61%uFF0C%u90A3%u8BF4%u660E%u54EA%u91CC%u80AF%u5B9A%u51FA%u95EE%u9898%u4E86%u3002%0A%3E2.%20Setup%20duplication.%20%u5982%u679C%u4F60%u53D1%u73B0%u6709%u51E0%u4E2A%u6D4B%u8BD5%u62E5%u6709%u76F8%u540C%u6216%u76F8%u4F3C%u7684setup%u4EE3%u7801%uFF0C%u90A3%u8BF4%u660E%u4F60%u7684%u6D4B%u8BD5%u4E2D%u6709%u91CD%u590D%u7684%u5730%u65B9%u3002%0A%3E3.%20Long%20running%20tests.%20TDD%20tests%20that%20run%20a%20long%20time%20won%27t%20be%20run%20often.%20Suites%20that%20take%20longer%20than%20ten%20minutes%0Ainevitably%20get%20trimmed.%20%u4E00%u4E2A%u6D4B%u8BD5%u5957%u4EF6%u4E0D%u8981%u8D85%u8FC7%u5341%u5206%u949F%u3002%u5426%u5219%uFF0C%u53CD%u590D%u8FD0%u884C%u7684%u673A%u4F1A%u5C31%u4F1A%u6025%u5267%u964D%u4F4E%u3002%0A%3E4.%20Fragile%20tests.%20Tests%20that%20break%20unexpectedly%20suggest%20that%20one%20part%20of%20the%20application%20is%20surprisingly%20affecting%20another%20part.%20%u5982%u679C%u6D4B%u8BD5%u88AB%u65E0%u60C5%u6253%u65AD%uFF0C%u8BF4%u660E%u4F60%u7684%u7A0B%u5E8F%u91CC%u6709%u8026%u5408%u3002%0A%0A%23%23%23How%20does%20TDD%20lead%20to%20frameworks%3F%0A%u53C8%u88AB%u918D%u9190%u704C%u9876%u4E86%uFF01%0A%3ETDD%20appears%20to%20stand%20this%20advice%20on%20its%20head%3A%20%3Cb%3E%3Cfont%20color%3D%22IndianRed%22%3E%22Code%20for%20tomorrow%2C%20design%20for%20today.%22%20%3C/font%3E%3C/b%3EHere%27s%20what%20happens%20in%20practice.%20%u4E0D%u8981%u4E3A%u672A%u6765%u8003%u8651%u90A3%u4E48%u591A%uFF0C%u7B80%u5355%u7684%u8BA9%u6D4B%u8BD5%u901A%u8FC7%uFF0C%u7136%u540E%u53CA%u65F6%u7684%u505A%u91CD%u6784%u6D88%u9664%u91CD%u590D%uFF0C%u53CD%u800C%u80FD%u8FBE%u5230%u66F4%u597D%u7684%u8BBE%u8BA1%u6846%u67B6%uFF08frameworks%uFF09%u3002%0A%0A%23%23%23How%20much%20feedback%20do%20you%20need%3F%0A%u8FD9%u91CC%u8BF4%u7684%u548C%u4E4B%u524D%u4E00%u76F4%u5F3A%u8C03%u7684%u601D%u60F3%u5F88%u4E00%u81F4%0A%3E%20If%20our%20knowledge%20of%20the%20implementation%20gives%20us%20confidence%20even%20without%20a%20test%2C%20then%20we%20will%20not%20write%20that%20test.%20%0A%0A%23%23%23When%20should%20you%20delete%20tests%3F%0A%3E1.%20The%20first%20criterion%20for%20your%20tests%20is%20**confidence**.%20Never%20delete%20a%20test%20if%20it%20reduces%20your%20confidence%20in%20the%20behavior%20of%20the%20system.%0A%3E2.%20The%20second%20criterion%20is%20**communication**.%20If%20you%20have%20two%20tests%20that%20exercise%20the%20same%20path%20through%20the%20code%2C%20but%20they%20speak%20to%20different%20scenarios%20for%20a%20reader%2C%20leave%20them%20alone.%0A%0A%23%23%23Can%20you%20drive%20development%20with%20application-level%20tests%3F%0A%u4E0D%u8981%u5C1D%u8BD5%u505A%u5E94%u7528%u7EA7%u522B%u7684test%0A%0A%23%23%23How%20do%20you%20switch%20to%20TDD%20midstream%3F%0A%u8FD9%u4E5F%u662F%u6211%u60F3%u95EE%u7684%uFF0C%u4F5C%u8005%u7684%u56DE%u7B54%u662F%u8FD9%u672C%u4E66%u5C31%u662F%u4E3A%u4E86%u8FD9%u4E2A%u800C%u5199%u7684%u3002%0A%3EThere%20is%20a%20whole%20book%20%28or%20books%29%20to%20be%20written%20about%20switching%20to%20TDD%20when%20you%20have%20lots%20of%20code.%20What%20follows%20is%20necessarily%20only%20a%20teaser.%0A%0A%u600E%u4E48%u505A%uFF1F%0A1.%20So%20first%20we%20have%20to%20decide%20to%20limit%20the%20scope%20of%20our%20changes.%20%0A2.%20Second%2C%20we%20have%20to%20break%20the%20deadlock%20between%20tests%20and%20refactoring.%20%0A%093.%20%u600E%u4E48%u83B7%u5F97feedback%0A%09%091%29%20We%20can%20get%20feedback%20other%20ways%20than%20with%20tests%2C%20like%20working%20very%20carefully%20and%20with%20a%20partner.%20%0A%09%092%29%20We%20can%20get%20feedback%20at%20a%20gross%20level%2C%20like%20system-level%20tests%20that%20we%20know%20aren%27t%20adequate%20but%20give%20us%20some%20confidence.%20With%20this%20feedback%2C%20we%20can%20make%20the%20areas%20we%20have%20to%20change%20more%20accepting%20of%20change.%0A%0A%23%23%23How%20does%20TDD%20relate%20to%20the%20practices%20of%20Extreme%20Programming%3F%0A%u540D%u8BCD%u89E3%u91CA%uFF1A%5BExtreme%20Programming%20-%20XP%5D%28http%3A//baike.baidu.com/link%3Furl%3DNgfFcw4hxjOQHRtEArm4buq177_Xw562MDhrOykDcd-0EOv1xB_Oz3eslAn9X65cFdUel2xI7tTiqB3Gqyz3Ja%29%u6781%u9650%u7F16%u7A0B%0A%3E%u6781%u9650%u7F16%u7A0B%u662F%u4E00%u4E2A%u8F7B%u91CF%u7EA7%u7684%u3001%u7075%u5DE7%u7684%u8F6F%u4EF6%u5F00%u53D1%u65B9%u6CD5%uFF1B%u540C%u65F6%u5B83%u4E5F%u662F%u4E00%u4E2A%u975E%u5E38%u4E25%u8C28%u548C%u5468%u5BC6%u7684%u65B9%u6CD5%u3002%u5B83%u7684%u57FA%u7840%u548C%u4EF7%u503C%u89C2%u662F%u4EA4%u6D41%u3001%u6734%u7D20%u3001%u53CD%u9988%u548C%u52C7%u6C14%uFF1B%u5373%uFF0C%u4EFB%u4F55%u4E00%u4E2A%u8F6F%u4EF6%u9879%u76EE%u90FD%u53EF%u4EE5%u4ECE%u56DB%u4E2A%u65B9%u9762%u5165%u624B%u8FDB%u884C%u6539%u5584%uFF1A%u52A0%u5F3A%u4EA4%u6D41%uFF1B%u4ECE%u7B80%u5355%u505A%u8D77%uFF1B%u5BFB%u6C42%u53CD%u9988%uFF1B%u52C7%u4E8E%u5B9E%u4E8B%u6C42%u662F%u3002XP%u662F%u4E00%u79CD%u8FD1%u87BA%u65CB%u5F0F%u7684%u5F00%u53D1%u65B9%u6CD5%uFF0C%u5B83%u5C06%u590D%u6742%u7684%u5F00%u53D1%u8FC7%u7A0B%u5206%u89E3%u4E3A%u4E00%u4E2A%u4E2A%u76F8%u5BF9%u6BD4%u8F83%u7B80%u5355%u7684%u5C0F%u5468%u671F%uFF1B%u901A%u8FC7%u79EF%u6781%u7684%u4EA4%u6D41%u3001%u53CD%u9988%u4EE5%u53CA%u5176%u5B83%u4E00%u7CFB%u5217%u7684%u65B9%u6CD5%uFF0C%u5F00%u53D1%u4EBA%u5458%u548C%u5BA2%u6237%u53EF%u4EE5%u975E%u5E38%u6E05%u695A%u5F00%u53D1%u8FDB%u5EA6%u3001%u53D8%u5316%u3001%u5F85%u89E3%u51B3%u7684%u95EE%u9898%u548C%u6F5C%u5728%u7684%u56F0%u96BE%u7B49%uFF0C%u5E76%u6839%u636E%u5B9E%u9645%u60C5%u51B5%u53CA%u65F6%u5730%u8C03%u6574%u5F00%u53D1%u8FC7%u7A0B%u3002%0A%0ATDD%u4E0EXP%u7684%u5171%u901A%u4E4B%u5904%uFF1A%0A-%20Pairing%0A-%20Work%20fresh%0A-%20Continuous%20integration%0A-%20Simple%20design%0A-%20Refactoring%0A-%20Continuous%20delivery