<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">博客园_横刀天笑的技术空间</title><subtitle type="text"/><id>http://feed.cnblogs.com/blog/u/13049/rss</id><updated>2012-02-09T02:59:46Z</updated><author><name>横刀天笑</name><uri>http://www.cnblogs.com/yuyijq/</uri></author><generator>CNBlogs BlogServer</generator><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yuyijq/"/><link rel="self" type="application/atom+xml" href="http://feed.cnblogs.com/blog/u/13049/rss"/><entry><id>http://www.cnblogs.com/yuyijq/archive/2012/02/08/2343374.html</id><title type="text">Bloom Filter算法</title><summary type="text">集合数据结构一般都有这么一个方法：contains。其作用就是判断给定的元素是否存在集合中，这是一个常用的方法。其最简单的内部实现即遍历集合内的元素，一个个的判断是否与给定元素相等。为了更高效点我们甚至可以采用“更好的（好是相对的）”算法实现。比如如果该集合是已经排序的，那么我们用二分查找来实现contains肯定更好。但是，如果集合的数据量庞大到一定程度，大部分我们熟知的算法不再有什么用了。即使可以使用，但是机器内存也不允许。而Bloom Filter就是这么一个空间利用率非常高的算法。我们先来看看这个算法的原理：1 首先我们有一个长度为n的比特数组，开始的时候将这个比特数组里所有的元素都初</summary><published>2012-02-08T15:22:00Z</published><updated>2012-02-08T15:22:00Z</updated><author><name>横刀天笑</name><uri>http://www.cnblogs.com/yuyijq/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yuyijq/archive/2012/02/08/2343374.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yuyijq/archive/2012/02/08/2343374.html"/><content type="html">&lt;p&gt;集合数据结构一般都有这么一个方法：contains。其作用就是判断给定的元素是否存在集合中，这是一个常用的方法。其最简单的内部实现即遍历集合内的元素，一个个的判断是否与给定元素相等。为了更高效点我们甚至可以采用&amp;#8220;更好的（好是相对的）&amp;#8221;算法实现。比如如果该集合是已经排序的，那么我们用二分查找来实现contains肯定更好。但是，如果集合的数据量庞大到一定程度，大部分我们熟知的算法不再有什么用了。即使可以使用，但是机器内存也不允许。&lt;/p&gt;&lt;p&gt;而Bloom Filter就是这么一个空间利用率非常高的算法。我们先来看看这个算法的原理：&lt;/p&gt;&lt;p&gt;1 首先我们有一个长度为n的比特数组，开始的时候将这个比特数组里所有的元素都初始化为0&lt;/p&gt;&lt;p&gt;00000000000000000000&lt;/p&gt;&lt;p&gt;上面的比特数组n为20&lt;/p&gt;&lt;p&gt;2 然后选取k个哈希函数，这k个哈希函数产生的结果的值的范围在0到n-1之间（对于上面的比特数组，即0到19） 。对每个要添加进集合的对象进行哈希运算，然后将哈希计算结果作为数组的索引，将索引位置的比特位设置为1（不管该比特位原先为0还是为1）。&lt;/p&gt;&lt;p&gt;比如我们选取三个哈希函数，对于对象A哈希值为0，5，7。那么比特数组就为：&lt;/p&gt;&lt;p&gt;10000101000000000000&lt;/p&gt;&lt;p&gt;对象B的值为2，8，13，那么添加B后的比特数组为：&lt;/p&gt;&lt;p&gt;10100101100001000000&lt;/p&gt;&lt;p&gt;对象C为0，4，7（对象C的第一个哈希函数的值与对象A的相同了，没关系我们还是设置为1就可以了）：&lt;/p&gt;&lt;p&gt;10101101100001000000&lt;/p&gt;&lt;p&gt;现在我们的Bloom Filter里已经有3个元素了。现在我们要判断某元素X是否在该集合中。就相当于我们要实现一个contains方法。那么这个方法如何实现呢？&lt;/p&gt;&lt;p&gt;对元素X采用相同的三个哈希函数哈希，然后以这三个哈希值为索引去比特数组里找。如果三个索引位置的比特位都为1我们就认为该元素在集合中，否则不是。&lt;/p&gt;&lt;p&gt;我们可以用伪代码简单的描述一下这个算法：&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding-left:5px;padding-right:5px;"&gt;&lt;div&gt;&lt;span style="color: #0000FF;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;class&lt;/span&gt;&amp;nbsp;BloomFilter{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;private&lt;/span&gt;&amp;nbsp;bit[]&amp;nbsp;bitSet&amp;nbsp;=&amp;nbsp;&lt;span style="color: #0000FF;"&gt;new&lt;/span&gt;&amp;nbsp;bit[N];&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;void&lt;/span&gt;&amp;nbsp;add(Object&amp;nbsp;element){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;int&lt;/span&gt;[]&amp;nbsp;hashValues&amp;nbsp;=&amp;nbsp;getHashValues(element);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;for&lt;/span&gt;(&lt;span style="color: #0000FF;"&gt;int&lt;/span&gt;&amp;nbsp;i&amp;nbsp;:&amp;nbsp;hashValues){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bitSet[i]&amp;nbsp;=&amp;nbsp;&lt;span style="color: #800080;"&gt;1&lt;/span&gt;;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;public&lt;/span&gt;&amp;nbsp;boolean&amp;nbsp;contains(Object&amp;nbsp;element){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;int&lt;/span&gt;[]&amp;nbsp;hashValues&amp;nbsp;=&amp;nbsp;getHashValues(element);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;for&lt;/span&gt;(&lt;span style="color: #0000FF;"&gt;int&lt;/span&gt;&amp;nbsp;i&amp;nbsp;:&amp;nbsp;hashValues){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;if&lt;/span&gt;(bitSet[i]&amp;nbsp;!=&amp;nbsp;&lt;span style="color: #800080;"&gt;1&lt;/span&gt;)&amp;nbsp;&lt;span style="color: #0000FF;"&gt;return&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;false&lt;/span&gt;;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;return&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;true&lt;/span&gt;;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;}&lt;/p&gt;&lt;p&gt;算法还是挺直观的，对不。想想，一个很大的对象，经过一哈希，然后就变成了Bloom Filter里面的一个比特，这个空间利用效率是多么高啊。如果哈希函数的实现效率也很高的话那么不仅空间利用率高，时间复杂度也低啊。这真是一个神奇的算法对吧。&lt;/p&gt;&lt;p&gt;可能你想，以后我就把我们那个啥数组的contains方法替换成Bloom Filter的实现吧。&lt;/p&gt;&lt;p&gt;不过你仔细验证过这个算法没，它存在一些问题。这个算法有以下这么几个特征：&lt;/p&gt;&lt;p&gt;1 如果该元素真的在集合中，那么Bloom Filter的contains方法肯定会返回true，这就是Bloom Filter不会漏报的特性。&lt;/p&gt;&lt;p&gt;2 如果该元素不在集合中，但Bloom Filter的contains方法有可能返回true。因为不同的元素经过哈希之后哈希值可能发生碰撞。这是Bloom Filter有可能误报的特性。但是这个误报的几率并不高。&lt;/p&gt;&lt;p&gt;根据这两个特性Bloom Filter在大量数据时还是挺有用的。比如假设我们有一个缓存服务器集群，集群里的不同的服务器承担的缓存也不尽相同。如果一个用户请求过来了，我们如何能快速的判断出用户请求的这个url在集群里哪台服务器上呢？因为每台服务器上缓存的url对应的页面非常庞大，我们全部弄到内存里代价也很高。我们就可以在每台服务器上放一个Bloom Filter，里面添加的都是本服务器上有缓存的那些url。这样即使Bloom Filter误报了，那就是把一个url发到了一个并不持有该url对应的缓存的服务器上，结果就是缓存未命中，缓存服务器只需要将该url打到后端的上游服务器就好了。&lt;/p&gt;&lt;p&gt;根据Bloom Filter的特征我们可以看到不是所有的场景都可以用的，只有在一些能容许少量的误报的情况下使用才行。该算法用很低的误报率却换来了大量的存储空间，实在是是一个很巧妙的算法。&amp;nbsp;&lt;/p&gt;&lt;p&gt;Bloom Filter算法：http://en.wikipedia.org/wiki/Bloom_filter&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/yuyijq/aggbug/2343374.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yuyijq/archive/2012/02/08/2343374.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yuyijq/archive/2012/01/02/2291025.html</id><title type="text">memcached源代码阅读笔记（二）：网络处理部分</title><summary type="text">既然memcached是一个缓存服务器，而且要提供高效的缓存服务，那么网络层肯定要非常有效率才行。要能支撑大量的并发连接，还要有很优秀的响应速度。除此之外，因为memcached的核心业务并不是网络层，它的核心是缓存机制。那么就必须采用一种机制，将网络层隔离，以免网络通信部分缠绕在系统的各处，扰乱了核心逻辑。</summary><published>2012-01-02T03:30:00Z</published><updated>2012-01-02T03:30:00Z</updated><author><name>横刀天笑</name><uri>http://www.cnblogs.com/yuyijq/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yuyijq/archive/2012/01/02/2291025.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yuyijq/archive/2012/01/02/2291025.html"/><content type="html">&lt;p&gt;既然memcached是一个缓存服务器，而且要提供高效的缓存服务，那么网络层肯定要非常有效率才行。要能支撑大量的并发连接，还要有很优秀的响应速度。除此之外，因为memcached的核心业务并不是网络层，它的核心是缓存机制。那么就必须采用一种机制，将网络层隔离，以免网络通信部分缠绕在系统的各处，扰乱了核心逻辑。&lt;/p&gt;&lt;p&gt;在这一点上要感谢基于事件驱动的网络库libevent。memcached就是采用这个来作为它的网络层，所以对于memcache来说，即使有成千上万的连接处理起来也不是什么难事。&lt;/p&gt;&lt;p&gt;libevent的事件驱动机制除了能提高网络处理的效率外，还抽象了各个操作系统上最高效的方式：比如Linux上的epoll, FreeBSD上的kqueue 以及Windows的IOCP。就不说跨平台了，单单使用好其中一种机制就不是一件容易事。除此之外，使用事件驱动的机制，还能让你将网络处理部分与业务逻辑相分离。&amp;nbsp;&lt;/p&gt;&lt;p&gt;好，今天我们就来看看memcached是如何利用libevent构建其网络层的（注意，memcache同时支持TCP和UDP，不过本文只会关注TCP部分）。&lt;/p&gt;&lt;p&gt;为了更好的理解libevent，我们先来看看linux上如何使用epoll实现基于事件的网络编程：&amp;nbsp;&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding-left:5px;padding-right:5px;"&gt;&lt;div&gt;&lt;span style="color: #0000FF;"&gt;#define&lt;/span&gt;&amp;nbsp;MAX_EVENTS&amp;nbsp;10&lt;br /&gt;main(){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;int&lt;/span&gt;&amp;nbsp;sfd;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;int&lt;/span&gt;&amp;nbsp;flags;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;int&lt;/span&gt;&amp;nbsp;efd;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;int&lt;/span&gt;&amp;nbsp;nfds;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;int&lt;/span&gt;&amp;nbsp;i;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;struct&lt;/span&gt;&amp;nbsp;epoll_event&amp;nbsp;ev,&amp;nbsp;events[MAX_EVENTS];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;struct&lt;/span&gt;&amp;nbsp;addrinfo&amp;nbsp;*ai;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;struct&lt;/span&gt;&amp;nbsp;addrinfo&amp;nbsp;hints&amp;nbsp;=&amp;nbsp;{.ai_flags&amp;nbsp;=&amp;nbsp;AI_PASSIVE,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.ai_family&amp;nbsp;=&amp;nbsp;AF_UNSPEC};&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;hints.ai_socktype&amp;nbsp;=&amp;nbsp;SOCK_STREAM;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;char&lt;/span&gt;&amp;nbsp;port_buf[NI_MAXSERV];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;snprintf(port_buf,&amp;nbsp;&lt;span style="color: #0000FF;"&gt;sizeof&lt;/span&gt;(port_buf),&amp;nbsp;&lt;span style="color: #800000;"&gt;"&lt;/span&gt;&lt;span style="color: #800000;"&gt;%d&lt;/span&gt;&lt;span style="color: #800000;"&gt;"&lt;/span&gt;,&amp;nbsp;port);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;getaddrinfo(NULL,&amp;nbsp;port_buf,&amp;nbsp;&amp;amp;hints,&amp;nbsp;&amp;amp;ai);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;create&amp;nbsp;a&amp;nbsp;socket&amp;nbsp;to&amp;nbsp;linsten&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sfd&amp;nbsp;=&amp;nbsp;socket(ai-&amp;gt;ai_family,&amp;nbsp;ai-&amp;gt;ai_socktype,&amp;nbsp;ai-&amp;gt;ai_protocol);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;flags&amp;nbsp;=&amp;nbsp;fcntl(sfd,&amp;nbsp;F_GETFL,&amp;nbsp;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;set&amp;nbsp;nob&amp;nbsp;block&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fcntl(sfd,&amp;nbsp;F_SETFL,&amp;nbsp;flags&amp;nbsp;|&amp;nbsp;O_NONBLOCK);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;bind&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bind(sfd,&amp;nbsp;ai-&amp;gt;ai_addr,&amp;nbsp;ai-&amp;gt;ai_addrlen);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;listen&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;listen(sfd,&amp;nbsp;&lt;span style="color: #800080;"&gt;15&lt;/span&gt;);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;create&amp;nbsp;epoll&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;efd&amp;nbsp;=&amp;nbsp;epoll_create(&lt;span style="color: #800080;"&gt;10&lt;/span&gt;);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ev.events&amp;nbsp;=&amp;nbsp;EPOLLIN;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ev.data.fd&amp;nbsp;=&amp;nbsp;sfd;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;setup&amp;nbsp;epoll&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;epoll_ctl(efd,&amp;nbsp;EPOLL_CTL_ADD,&amp;nbsp;sfd,&amp;nbsp;&amp;amp;ev);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;while&lt;/span&gt;(&lt;span style="color: #0000FF;"&gt;true&lt;/span&gt;){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;wait,&amp;nbsp;block&amp;nbsp;until&amp;nbsp;a&amp;nbsp;new&amp;nbsp;connection&amp;nbsp;is&amp;nbsp;comming&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;nfds&amp;nbsp;=&amp;nbsp;epoll_wait(efd,&amp;nbsp;events,&amp;nbsp;MAX_EVENTS,&amp;nbsp;-&lt;span style="color: #800080;"&gt;1&lt;/span&gt;);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;for&lt;/span&gt;(i&amp;nbsp;=&amp;nbsp;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;;&amp;nbsp;i&amp;nbsp;&amp;lt;&amp;nbsp;nfds;&amp;nbsp;++i){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dispatch(events[i]);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;&lt;p&gt;}&amp;nbsp;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;我在上面的代码里加了少量的注释。代码的前半部分没有什么，和所有的网络编程一样，就是创建套接字，然后bind到地址，开始监听。注意，这里我们将这个文件描述符设置为NON_BLOCK的，这一点很关键。然后我们创建epoll，设置感兴趣的事件，然后在一个循环里wait事件的发生。 wait会阻塞，当一个事件发生时会继续运行，然后根据事件的类型作出不同的处理。&lt;/p&gt;&lt;p&gt;可能有人要问，我没有发现任何事件触发的意思啊，这跟.NET里我们熟悉的事件处理差别太大了：&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding-left:5px;padding-right:5px;"&gt;&lt;div&gt;btnOK.OnClick&amp;nbsp;+=&amp;nbsp;&lt;span style="color: #0000FF;"&gt;delegate&lt;/span&gt;(sender,&amp;nbsp;e){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;...&lt;/span&gt;&lt;/div&gt;&lt;p&gt;}&amp;nbsp;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;不过如果你熟悉一点Win32编程的话就不那么认为了。不熟悉的话肯定听说过消息循环吧。想想看，上面的代码是不是跟Win32里的消息循环非常类似。一个loop，然后接受事件，翻译事件，然后根据事件的类型分发给事件处理句柄。只不过在Win32里编写窗体程序时，事件的来源是鼠标点击按钮或者键盘操作，而这里的事件来源是网络：可能是一个新的连接到来，也可能是读缓冲区里数据已经准备好（要了解Win32消息循环的更多细节可以参看我这篇文章：&lt;a href="http://www.cnblogs.com/yuyijq/archive/2009/11/04/1595775.html" target="_blank"&gt;点击这里&lt;/a&gt;）。至于为何基于事件的这种机制对于大并发的系统很有用不在本文的范围内，如果要阐述清楚这个可能需要介绍以下IO模型等问题，不过你也可以通过阅读我之前写的&lt;a href="http://www.cnblogs.com/yuyijq/category/278984.html" target="_blank"&gt;并行和异步&lt;/a&gt;了解一些概念。&lt;/p&gt;&lt;p&gt;不过要让应用开发人员都亲力亲为的处理消息循环来做事件驱动的编程太过于麻烦了，不管是后来的MFC，还是VB抑或是WinForm中，那个消息循环再也没出现过。取而代之的是现在好用的事件。网络编程也一样，要亲力亲为的处理这种细节，太麻烦了，而且太容易出错。最麻烦的还有各个平台提供的机制还不一样，如是类似libevent这样的类库就应运而生。&amp;nbsp;&lt;/p&gt;&lt;p&gt;现在我们来看看memcache如何使用libevent进行高效的网络编程：&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding-left:5px;padding-right:5px;"&gt;&lt;div&gt;&lt;p style="font-family: verdana, 'courier new'; font-size: 14px; line-height: 21px; "&gt;&lt;span style="color: #008000; "&gt;//&lt;/span&gt;&lt;span style="color: #008000; "&gt;event_base&amp;nbsp;是libevent的核心数据结构&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;span style="color: #0000FF;"&gt;static&lt;/span&gt;&lt;span style="color: #0000FF;"&gt;struct&lt;/span&gt;&amp;nbsp;event_base&amp;nbsp;*main_base;&lt;br /&gt;main_base&amp;nbsp;=&amp;nbsp;event_init();&lt;br /&gt;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;然后就是创建scoket等&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;fd&amp;nbsp;=&amp;nbsp;socket(...)&lt;br /&gt;bind()&lt;br /&gt;listen()&lt;br /&gt;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;调用conn_new方法&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;conn_new(sfd,&amp;nbsp;conn_listening,EV_READ&amp;nbsp;|&amp;nbsp;EV_PERSIST,&amp;nbsp;&lt;span style="color: #800080;"&gt;1&lt;/span&gt;,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;transport,&amp;nbsp;main_base);&lt;br /&gt;&lt;br /&gt;conn&amp;nbsp;*conn_new(&lt;span style="color: #0000FF;"&gt;const&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;int&lt;/span&gt;&amp;nbsp;sfd,&amp;nbsp;&lt;span style="color: #0000FF;"&gt;enum&lt;/span&gt;&amp;nbsp;conn_states&amp;nbsp;init_state,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;const&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;int&lt;/span&gt;&amp;nbsp;event_flags,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;const&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;int&lt;/span&gt;&amp;nbsp;read_buffer_size,&amp;nbsp;&lt;span style="color: #0000FF;"&gt;enum&lt;/span&gt;&amp;nbsp;network_transport&amp;nbsp;transport,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;struct&lt;/span&gt;&amp;nbsp;event_base&amp;nbsp;*&lt;span style="color: #0000FF;"&gt;base&lt;/span&gt;)&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;c-&amp;gt;sfd&amp;nbsp;=&amp;nbsp;sfd;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;c-&amp;gt;state&amp;nbsp;=&amp;nbsp;init_state;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;设置感兴趣的事件，事件句柄是event_handler&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;event_set(&amp;amp;c-&amp;gt;&lt;span style="color: #0000FF;"&gt;event&lt;/span&gt;,&amp;nbsp;sfd,&amp;nbsp;event_flags,&amp;nbsp;event_handler,&amp;nbsp;(&lt;span style="color: #0000FF;"&gt;void&lt;/span&gt;&amp;nbsp;*)c);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;event_base_set(&lt;span style="color: #0000FF;"&gt;base&lt;/span&gt;,&amp;nbsp;&amp;amp;c-&amp;gt;&lt;span style="color: #0000FF;"&gt;event&lt;/span&gt;);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;event_add(&amp;amp;c-&amp;gt;&lt;span style="color: #0000FF;"&gt;event&lt;/span&gt;,&amp;nbsp;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;);&lt;br /&gt;}&lt;br /&gt;&lt;span style="color: #0000FF;"&gt;void&lt;/span&gt;&amp;nbsp;event_handler(&lt;span style="color: #0000FF;"&gt;const&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;int&lt;/span&gt;&amp;nbsp;fd,&amp;nbsp;&lt;span style="color: #0000FF;"&gt;const&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;short&lt;/span&gt;&amp;nbsp;which,&amp;nbsp;&lt;span style="color: #0000FF;"&gt;void&lt;/span&gt;&amp;nbsp;*arg)&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;drive_machine(c);&lt;br /&gt;}&lt;br /&gt;&lt;span style="color: #0000FF;"&gt;static&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;void&lt;/span&gt;&amp;nbsp;drive_machine(conn&amp;nbsp;*c)&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;while&lt;/span&gt;&amp;nbsp;(!stop)&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;switch&lt;/span&gt;(c-&amp;gt;state)&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;case&lt;/span&gt;&amp;nbsp;conn_listening:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sfd&amp;nbsp;=&amp;nbsp;accept(c-&amp;gt;sfd,&amp;nbsp;(&lt;span style="color: #0000FF;"&gt;struct&lt;/span&gt;&amp;nbsp;sockaddr&amp;nbsp;*)&amp;amp;addr,&amp;nbsp;&amp;amp;addrlen);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;flags&amp;nbsp;=&amp;nbsp;fcntl(sfd,&amp;nbsp;F_GETFL,&amp;nbsp;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;))&amp;nbsp;&amp;lt;&amp;nbsp;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&amp;nbsp;||&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fcntl(sfd,&amp;nbsp;F_SETFL,&amp;nbsp;flags&amp;nbsp;|&amp;nbsp;O_NONBLOCK);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dispatch_conn_new(sfd,&amp;nbsp;conn_new_cmd,&amp;nbsp;EV_READ&amp;nbsp;|&amp;nbsp;EV_PERSIST,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DATA_BUFFER_SIZE,&amp;nbsp;tcp_transport);&lt;/p&gt;&lt;p&gt;}&lt;/p&gt;&lt;/div&gt;&lt;p&gt;为了代码更清晰，我删除了错误处理等代码。上面的代码从构建libevent的核心数据结构event_base开始，然后创建socket，给该socket设置感兴趣的事件，并且设置了事件处理句柄event_handler，然后我们根据conn数据结构的state字段判断现在是什么事件并作出相应的处理（conn数据结构是memcache里处理网络连接的核心数据结构之一）。第一步当然是一个新的连接的到来：连接到了memcache服务器，这是第一个事件。然后我们通过dispatch_conn_new函数分发这个新到来的连接。在连接到来之后我们就要进入下一步处理了：比如从缓冲区读取数据，在这里就是接收memcache客户端发过来的各种命令，或者向缓冲区写出数据，比如我们接到一个get命令，然后我们就从缓存里读取相应的key对应的值，然后将该值写到缓冲区（关于memcache协议（命令）处理的内容后面的文章会有介绍，本文我们只关注网络处理部分）。&lt;/p&gt;&lt;p&gt;不知道刚才你阅读过我那篇Win32消息循环的文章没，在窗体编程中，为了在做耗时操作时不让界面假死我们常用的做法就是创建一个有别于消息循环的主线程，然后在这个线程里处理这些耗时操作。这里也一样，当连接到来后，我们要读命令，解析命令，处理命令。为了不让这些操作阻塞了主线程，即监听连接到来的线程（想想如果阻塞了结果会是怎样？），memcache也是类似的处理方法：主线程接收新连接，然后创建一些线程（可配置的）来处理命令。好，我们还是来看代码吧：&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding-left:5px;padding-right:5px;"&gt;&lt;div&gt;thread_init(settings.num_threads,&amp;nbsp;main_base);&lt;br /&gt;&lt;span style="color: #0000FF;"&gt;void&lt;/span&gt;&amp;nbsp;thread_init(&lt;span style="color: #0000FF;"&gt;int&lt;/span&gt;&amp;nbsp;nthreads,&amp;nbsp;&lt;span style="color: #0000FF;"&gt;struct&lt;/span&gt;&amp;nbsp;event_base&amp;nbsp;*main_base)&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;threads&amp;nbsp;=&amp;nbsp;calloc(nthreads,&amp;nbsp;&lt;span style="color: #0000FF;"&gt;sizeof&lt;/span&gt;(LIBEVENT_THREAD));&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dispatcher_thread.&lt;span style="color: #0000FF;"&gt;base&lt;/span&gt;&amp;nbsp;=&amp;nbsp;main_base;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dispatcher_thread.thread_id&amp;nbsp;=&amp;nbsp;pthread_self();&lt;br /&gt;&amp;nbsp; &amp;nbsp; //这里很巧妙&lt;br /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;span style="color: #0000ff; "&gt;for&lt;/span&gt;&amp;nbsp;(i&amp;nbsp;=&amp;nbsp;&lt;span style="color: #800080; "&gt;0&lt;/span&gt;;&amp;nbsp;i&amp;nbsp;&amp;lt;&amp;nbsp;nthreads;&amp;nbsp;i++)&amp;nbsp;{&lt;/div&gt;&lt;p&gt;//创建一个管道，管道对应两个描述符，一个用于写，一个用于读。&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;span style="color: #0000FF;"&gt;int&lt;/span&gt;&amp;nbsp;fds[&lt;span style="color: #800080;"&gt;2&lt;/span&gt;];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pipe(fds);&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;threads[i].notify_receive_fd&amp;nbsp;=&amp;nbsp;fds[&lt;span style="color: #800080;"&gt;0&lt;/span&gt;];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;threads[i].notify_send_fd&amp;nbsp;=&amp;nbsp;fds[&lt;span style="color: #800080;"&gt;1&lt;/span&gt;];&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setup_thread(&amp;amp;threads[i]);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stats.reserved_fds&amp;nbsp;+=&amp;nbsp;&lt;span style="color: #800080;"&gt;5&lt;/span&gt;;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;for&lt;/span&gt;&amp;nbsp;(i&amp;nbsp;=&amp;nbsp;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;;&amp;nbsp;i&amp;nbsp;&amp;lt;&amp;nbsp;nthreads;&amp;nbsp;i++)&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;create_worker(worker_libevent,&amp;nbsp;&amp;amp;threads[i]);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;只有所有的线程都初始化完毕后，这里才会继续执行，否则阻塞&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;while&lt;/span&gt;&amp;nbsp;(init_count&amp;nbsp;&amp;lt;&amp;nbsp;nthreads)&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pthread_cond_wait(&amp;amp;init_cond,&amp;nbsp;&amp;amp;init_lock);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;br /&gt;&lt;span style="color: #0000FF;"&gt;static&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;void&lt;/span&gt;&amp;nbsp;setup_thread(LIBEVENT_THREAD&amp;nbsp;*me)&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;me-&amp;gt;&lt;span style="color: #0000FF;"&gt;base&lt;/span&gt;&amp;nbsp;=&amp;nbsp;event_init();&lt;br /&gt;&lt;/p&gt;&lt;p&gt;//在这里，我们在管道的读端注册感兴趣的事件&amp;nbsp;&lt;/p&gt;&lt;p&gt;event_set(&amp;amp;me-&amp;gt;notify_event,&amp;nbsp;me-&amp;gt;notify_receive_fd,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;EV_READ&amp;nbsp;|&amp;nbsp;EV_PERSIST,&amp;nbsp;thread_libevent_process,&amp;nbsp;me);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;event_base_set(me-&amp;gt;&lt;span style="color: #0000FF;"&gt;base&lt;/span&gt;,&amp;nbsp;&amp;amp;me-&amp;gt;notify_event);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;event_add(&amp;amp;me-&amp;gt;notify_event,&amp;nbsp;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;);&lt;br /&gt;}&lt;br /&gt;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;事件处理函数，这里又调用了conn_new函数&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #0000FF;"&gt;static&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;void&lt;/span&gt;&amp;nbsp;thread_libevent_process(&lt;span style="color: #0000FF;"&gt;int&lt;/span&gt;&amp;nbsp;fd,&amp;nbsp;&lt;span style="color: #0000FF;"&gt;short&lt;/span&gt;&amp;nbsp;which,&amp;nbsp;&lt;span style="color: #0000FF;"&gt;void&lt;/span&gt;&amp;nbsp;*arg)&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LIBEVENT_THREAD&amp;nbsp;*me&amp;nbsp;=&amp;nbsp;arg;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CQ_ITEM&amp;nbsp;*item;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;//从队列里取出要处理的连接&lt;/p&gt;&lt;p&gt;item&amp;nbsp;=&amp;nbsp;cq_pop(me-&amp;gt;new_conn_queue);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn&amp;nbsp;*c&amp;nbsp;=&amp;nbsp;conn_new(item-&amp;gt;sfd,&amp;nbsp;item-&amp;gt;init_state,&amp;nbsp;item-&amp;gt;event_flags,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item-&amp;gt;read_buffer_size,&amp;nbsp;item-&amp;gt;transport,&amp;nbsp;me-&amp;gt;&lt;span style="color: #0000FF;"&gt;base&lt;/span&gt;);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;}&lt;br /&gt;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt;创建worker线程用来处理命令等&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #0000FF;"&gt;static&lt;/span&gt;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;void&lt;/span&gt;&amp;nbsp;create_worker(&lt;span style="color: #0000FF;"&gt;void&lt;/span&gt;&amp;nbsp;*(*func)(&lt;span style="color: #0000FF;"&gt;void&lt;/span&gt;&amp;nbsp;*),&amp;nbsp;&lt;span style="color: #0000FF;"&gt;void&lt;/span&gt;&amp;nbsp;*arg)&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pthread_t&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;thread;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pthread_attr_t&amp;nbsp;&amp;nbsp;attr;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;int&lt;/span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ret;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pthread_attr_init(&amp;amp;attr);&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ret&amp;nbsp;=&amp;nbsp;pthread_create(&amp;amp;thread,&amp;nbsp;&amp;amp;attr,&amp;nbsp;func,&amp;nbsp;arg);&lt;/p&gt;&lt;p&gt;}&amp;nbsp;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;上面的代码会在memcache初始化的时候就会执行，会创建一堆线程等待事件处理。那么是用什么方式将事件传递给这些线程呢，这里实现有点巧妙。注意到这几行代码：&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding-left:5px;padding-right:5px;"&gt;&lt;div&gt;&lt;span style="font-family: verdana, 'courier new'; font-size: 14px; line-height: 21px; "&gt;pipe(fds);&lt;/span&gt;&amp;nbsp;&lt;/div&gt;&lt;p&gt;threads[i].notify_receive_fd&amp;nbsp;=&amp;nbsp;fds[&lt;span style="color: #800080;"&gt;0&lt;/span&gt;];&lt;br /&gt;threads[i].notify_send_fd&amp;nbsp;=&amp;nbsp;fds[&lt;span style="color: #800080;"&gt;1&lt;/span&gt;];&lt;/p&gt;&lt;/div&gt;&lt;p&gt;这里创建了一个管道（pipe），管道对应两个描述符，一个用于写，一个读。再来看看前面一段代码里我们没有贴出的dispatch_conn_new函数：&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding-left:5px;padding-right:5px;"&gt;&lt;div&gt;&lt;span style="font-family: verdana, 'courier new'; font-size: 14px; line-height: 21px; color: #0000ff; "&gt;void&lt;/span&gt;&lt;span style="font-family: verdana, 'courier new'; font-size: 14px; line-height: 21px; "&gt;&amp;nbsp;dispatch_conn_new(&lt;/span&gt;&lt;span style="font-family: verdana, 'courier new'; font-size: 14px; line-height: 21px; color: #0000ff; "&gt;int&lt;/span&gt;&lt;span style="font-family: verdana, 'courier new'; font-size: 14px; line-height: 21px; "&gt;&amp;nbsp;sfd,&amp;nbsp;&lt;/span&gt;&lt;span style="font-family: verdana, 'courier new'; font-size: 14px; line-height: 21px; color: #0000ff; "&gt;enum&lt;/span&gt;&lt;span style="font-family: verdana, 'courier new'; font-size: 14px; line-height: 21px; "&gt;&amp;nbsp;conn_states&amp;nbsp;init_state,&amp;nbsp;&lt;/span&gt;&lt;span style="font-family: verdana, 'courier new'; font-size: 14px; line-height: 21px; color: #0000ff; "&gt;int&lt;/span&gt;&lt;span style="font-family: verdana, 'courier new'; font-size: 14px; line-height: 21px; "&gt;&amp;nbsp;event_flags,&lt;/span&gt;&lt;span style="color: #0000ff; "&gt;int&lt;/span&gt;&amp;nbsp;read_buffer_size,&amp;nbsp;&lt;span style="color: #0000ff; "&gt;enum&lt;/span&gt;&amp;nbsp;network_transport&amp;nbsp;transport)&amp;nbsp;{&lt;/div&gt;&lt;p&gt;CQ_ITEM&amp;nbsp;*item&amp;nbsp;=&amp;nbsp;cqi_new();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style="color: #0000FF;"&gt;int&lt;/span&gt;&amp;nbsp;tid&amp;nbsp;=&amp;nbsp;(last_thread&amp;nbsp;+&amp;nbsp;&lt;span style="color: #800080;"&gt;1&lt;/span&gt;)&amp;nbsp;%&amp;nbsp;settings.num_threads;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LIBEVENT_THREAD&amp;nbsp;*thread&amp;nbsp;=&amp;nbsp;threads&amp;nbsp;+&amp;nbsp;tid;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;last_thread&amp;nbsp;=&amp;nbsp;tid;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item-&amp;gt;sfd&amp;nbsp;=&amp;nbsp;sfd;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item-&amp;gt;init_state&amp;nbsp;=&amp;nbsp;init_state;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item-&amp;gt;event_flags&amp;nbsp;=&amp;nbsp;event_flags;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item-&amp;gt;read_buffer_size&amp;nbsp;=&amp;nbsp;read_buffer_size;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item-&amp;gt;transport&amp;nbsp;=&amp;nbsp;transport;&lt;br /&gt;&amp;nbsp; &amp;nbsp; //将连接放到一个队列里&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cq_push(thread-&amp;gt;new_conn_queue,&amp;nbsp;item);&amp;nbsp;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;//向管道的写端写一个字节的数字，人为的触发事件&amp;nbsp;&lt;/p&gt;&lt;p&gt;write(thread-&amp;gt;notify_send_fd,&amp;nbsp;&lt;span style="color: #800000;"&gt;""&lt;/span&gt;,&amp;nbsp;&lt;span style="color: #800080;"&gt;1&lt;/span&gt;);&lt;/p&gt;&lt;p&gt;}&amp;nbsp;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;有意思的代码就在最后一行，在接收到一个连接之后，从刚才创建的一些LIBEVENT_THREAD里，选一个。这个结构里就有刚才创建的那个管道对应的两个描述符，然后往管道的写端写如一个字节，因为上面的setup_thread里为管道的读端设置了感兴趣的事件，这个时候事件就触发了（人为的触发一个事件，并且将刚才的连接放到一个队列里（这里是每个线程一个队列），这样就将一个同步的事件转换为异步的了）。事件触发后，&lt;span style="background-color: #f5f5f5; font-family: 'Courier New'; font-size: 13px; line-height: 19px; "&gt;thread_libevent_process函数就会执行。 然后又进入到conn_new函数，进入到drive_machine函数，又根据conn的state进行事件处理。&lt;/span&gt;&lt;/p&gt;&lt;p&gt;PS：我不知道这种利用管道，然后人为的触发事件的机制是不是一种什么模式或惯用法。除了在memcache这里我见到了这种方式外，在java的NIO里也有类似的使用。在java NIO里创建一个Selector后，会创建一个管道（在Linux上，而在windows上会创建一对socket），我们可以通过向管道的写端写入一个字节来唤醒已经阻塞的selector。&lt;/p&gt;&lt;p&gt;在介绍完memcache的网络处理部分后，下一篇我们就可以看看memcache是如何从网络上读取内容，解析命令的。&amp;nbsp;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/yuyijq/aggbug/2291025.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yuyijq/archive/2012/01/02/2291025.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yuyijq/archive/2011/12/14/2287531.html</id><title type="text">关于自动化测试</title><summary type="text">自动化测试是软件开发中非常重要的一环，那么我们如何进行自动化测试呢？何时添加自动化测试呢？自动化测试又有些什么作用。本文是我在team内部分享的一些内容。</summary><published>2011-12-14T06:16:00Z</published><updated>2011-12-14T06:16:00Z</updated><author><name>横刀天笑</name><uri>http://www.cnblogs.com/yuyijq/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yuyijq/archive/2011/12/14/2287531.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yuyijq/archive/2011/12/14/2287531.html"/><content type="html">&lt;span style="color: #333333; font-family: Helvetica, Arial, sans-serif; font-size: 13px; line-height: 17px; "&gt;&#xD;
&lt;p&gt;&lt;strong&gt;概述&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;常见的三类自动化测试有：单元测试，集成测试以及功能测试。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;&lt;a name="%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95-%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95"&gt;&lt;/a&gt;单元测试&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;单元测试是一个白盒测试，一般是针对一个方法单元进行的测试，单元测试要求运行快，编写简单。所以一般单元测试有这么一些特质：&lt;/p&gt;&#xD;
&lt;ul style="font-size: 10pt; line-height: 13pt; list-style-type: disc; "&gt;&#xD;
     &lt;li style="font-size: 10pt; line-height: 13pt; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;不连接数据库&lt;/li&gt;&#xD;
     &lt;li style="font-size: 10pt; line-height: 13pt; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;不访问磁盘文件&lt;/li&gt;&#xD;
     &lt;li style="font-size: 10pt; line-height: 13pt; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;不访问远程网络&lt;/li&gt;&#xD;
&lt;/ul&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;能够在很短时间内运行完毕（比如三秒内）&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;&lt;a name="%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95-%E9%9B%86%E6%88%90%E6%B5%8B%E8%AF%95"&gt;&lt;/a&gt;集成测试&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;集成测试可以称之为灰盒测试，是指对系统中几个模块集成起来进行测试。有很多时候，单元测试无法发现的问题，通过集成测试能发现。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;&lt;a name="%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95-%E5%8A%9F%E8%83%BD%E6%B5%8B%E8%AF%95"&gt;&lt;/a&gt;功能测试&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;功能测试是黑盒测试，完全从系统的功能边界外，不对系统内部做任何假设，就以用户的方式对系统进行使用测试。功能测试运行是最慢的，&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;编写和维护都非常困难。但是对交付成功的软件的价值确实最大的。&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;测试能给我们带来收益，但是测试也需要我们投入很多精力。如何能在投入和收益间找到平衡，就需要我们在系统中结合这三种测试。一般，&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;系统中单元测试最多，集成测试次之，最少的是功能测试。这就是经典的测试三角：&amp;nbsp;&lt;img src="http://wiki.corp.qunar.com/download/attachments/9941242/%E5%9B%BE%E7%89%872.gif?version=1&amp;amp;modificationDate=1320997733000" style="border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-style: solid; border-right-style: solid; border-bottom-style: solid; border-left-style: solid; border-top-color: black; border-right-color: black; border-bottom-color: black; border-left-color: black; " alt="" /&gt;&lt;br /&gt;&#xD;
&lt;strong&gt;对自动化测试的误解&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;&lt;a name="%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95-%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E8%83%BD%E9%98%B2%E6%AD%A2bug"&gt;&lt;/a&gt;自动化测试能防止bug&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;这是对测试最常见的误解，测试不能帮助我们防止bug，写了很多测试，并不意味着我们的系统就不会出现bug。自动化测试是防止我们在演进&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;系统的过程中，引入新的bug。因为人工的测试非常缓慢，如果我们修改了代码，需要等待很长时间才能知道我们的修改是否正确，这就会给修&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;改代码带来很大的风险，有可能我们就不等待人工测试的结果就交付产品。如果我们有一套能很快运行的自动化测试集，我们就能更快的验证我&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;们的修改是否对已有功能造成破坏，是否引入了新的bug。认识到这一点，那就要调整一下心态：有可能有人看到长期以来，测试也没有帮住我们&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;什么，就不愿意写测试，觉得不值得。我觉得这是好事，那说明我们开发能力很高，很少出现bug。如果bug很多，自动化测试并没有起到防止引入&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;bug的作用，我们就应该调研，如何写质量更高的测试。&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;总结：&lt;strong&gt;自动化测试是一种快速反馈的手段&lt;/strong&gt;。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;&lt;a name="%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95-%E6%B5%8B%E8%AF%95%E4%BB%A3%E7%A0%81%E4%B8%8D%E6%98%AF%E4%BA%A4%E4%BB%98%E7%9A%84%E4%B8%80%E9%83%A8%E5%88%86"&gt;&lt;/a&gt;测试代码不是交付的一部分&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;很多人认为，我们编写的产品代码才是我们要交付给最终用户的东西，而测试代码不是。所以我们可以以随便的态度编写自动化测试代码，在产品&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;代码中应用的一些最佳实践或原则，我们可以不应用到测试代码中。这个观点也是错误的，&lt;strong&gt;不维护的自动化测试，比没有自动化测试更有危害&lt;/strong&gt;。&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;第一，不维护的自动化测试很难跟随产品代码同演进，可能已经丧失了他原有的作用，而我们心里却以为我们有自动化测试，但实际上那只是摆设。&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;只会误导我们。&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;第二，如果需求改变了，我们需要修改测试，让它体现最新的需求，但是因为测试年久失修，变得很难看懂，很难修改。那么最大的可能就是：删除&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;测试代码，那么我们花在自动化测试上的时间就付诸东流了。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;&lt;a name="%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95-%E5%8F%AA%E5%9C%A8%E6%9C%80%E5%90%8E%E9%98%B6%E6%AE%B5%E8%BF%90%E8%A1%8C%E6%B5%8B%E8%AF%95"&gt;&lt;/a&gt;只在最后阶段运行测试&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;其实这一条和第一条有相似之处，很多人认为既然是测试，那么就应该在测试阶段跑，而我们现在在开发产品代码，这是开发阶段，我们不应该运行&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;测试。按照第一条最后的总结，自动化测试是一种反馈手段，那么我们更应该持续不断地，频繁的运行测试。当我们对代码有修改时，就运行测试，&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;当发现测试失败后，立即修复，以免将这些债留到后期，那个时候修复的成本将更高昂。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;&lt;a name="%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95-%E5%AE%9E%E8%B7%B5"&gt;&lt;/a&gt;实践&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;&lt;a name="%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95-%E4%BB%A5%E5%8F%AF%E6%B5%8B%E8%AF%95%E6%80%A7%E4%B8%BA%E7%9B%AE%E6%A0%87%E6%9D%A5%E7%BC%96%E5%86%99%E4%BB%A3%E7%A0%81"&gt;&lt;/a&gt;以可测试性为目标来编写代码&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;即使我们不采用测试驱动开发（TDD）的方式开发产品，但我们也应该将可测试性这条准则铭记在心。你编写的代码无法自动化测试，或难以自动化&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;测试就必须依靠人工手动测试。人工测试会有疏漏，而且重复两次的测试可能有些少许的不同，都会疏漏一些重要的问题，或者难以重现bug。&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;比如我们要尽量避免与一些难以测试的部分紧耦合，比如连接数据库部分，向屏幕打印输出的部分，发送短信的部分等。比如你在一个类里，需要发送&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;邮件，现在你需要测试这个类里的逻辑，这个时候你并不是为了测试发送邮件的客户端。如果你这个时候直接使用发送邮件客户端的静态方法，这就是&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;一种非常强的紧耦合，如果我们想自动化测试这个类，我们就无法用花费更小，运行更快的单元测试甚至是集成测试来测试这段代码，我们就必须在功能&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;测试这个层次来测试。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;&lt;a name="%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95-%E6%B5%8B%E8%AF%95%E5%BA%94%E8%AF%A5%E5%8F%AF%E4%BB%A5%E9%87%8D%E5%A4%8D%E8%BF%90%E8%A1%8C"&gt;&lt;/a&gt;测试应该可以重复运行&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;第一个测试运行完毕后，修改了第二个测试所依赖的一个关键数据，测试运行不了了，必须人工干涉测试才能继续进行。这种自动化测试也是没有任何价值的。&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;幸好，我们有事务这个好东西，在事务开始时我们准备数据，然后运行测试。在测试完毕后，我们回滚事务，没有污染数据库。就拿基于Spring的集成测试为例：&lt;/p&gt;&#xD;
&lt;div panel"="" style="color: black; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; background-image: initial; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: #ffffff; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-style: dashed; border-right-style: dashed; border-bottom-style: dashed; border-left-style: dashed; overflow-x: auto; overflow-y: auto; border-top-color: #bbbbbb; border-right-color: #bbbbbb; border-bottom-color: #bbbbbb; border-left-color: #bbbbbb; border-top-left-radius: 5px 5px; border-top-right-radius: 5px 5px; border-bottom-right-radius: 5px 5px; border-bottom-left-radius: 5px 5px; background-position: initial initial; background-repeat: initial initial; "&gt;&#xD;
&lt;div panelheader"="" style="padding-top: 10px; padding-right: 10px; padding-bottom: 5px; padding-left: 10px; line-height: 1em; text-align: left; border-bottom-width: 1px; border-bottom-style: solid; background-image: initial; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: #f0f0f0; border-bottom-color: #f0f0f0; color: #333333; margin-bottom: 0px; border-top-right-radius: 5px 5px; border-top-left-radius: 5px 5px; background-position: initial initial; background-repeat: initial initial; "&gt;&lt;strong&gt;Integration.java&lt;/strong&gt;&lt;/div&gt;&#xD;
&lt;div panelcontent"="" style="background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; color: #333333; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 10px; padding-bottom: 0px; padding-left: 10px; text-align: left; font-size: 0.95em; background-position: initial initial; background-repeat: initial initial; "&gt;&#xD;
&lt;pre style="padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; text-align: left; overflow-x: auto; overflow-y: auto; font-family: 'Courier New', Courier, monospace; line-height: 1.3; "&gt;@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations=&lt;span style="color: #009100; background-color: inherit; "&gt;"classpath:applicationContext-test.xml"&lt;/span&gt;) @TransactionConfiguration(transactionManager = &lt;span style="color: #009100; background-color: inherit; "&gt;"txManager"&lt;/span&gt;) @Transactional &lt;span style="color: #000091; background-color: inherit; "&gt;public&lt;/span&gt; &lt;span style="color: #000091; background-color: inherit; "&gt;abstract&lt;/span&gt; class IntegrationTest { } &lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;我们添加@Transactional标签，让我们的测试在事务中运行。为了避免所有的测试类，都要带上那堆标签，为此提取一个基类。基类设为抽象的是因为，一个没有任何测试的测试类，maven运行这类测试的时候会报错误。&lt;br /&gt;&#xD;
如果在某一个测试中我们不想让事务回滚，我们可以添加@Rollback(false)标签（这一般在调试时很有用）。&lt;/p&gt;&#xD;
&lt;div panel"="" style="color: black; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; background-image: initial; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: #ffffff; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-style: dashed; border-right-style: dashed; border-bottom-style: dashed; border-left-style: dashed; overflow-x: auto; overflow-y: auto; border-top-color: #bbbbbb; border-right-color: #bbbbbb; border-bottom-color: #bbbbbb; border-left-color: #bbbbbb; border-top-left-radius: 5px 5px; border-top-right-radius: 5px 5px; border-bottom-right-radius: 5px 5px; border-bottom-left-radius: 5px 5px; background-position: initial initial; background-repeat: initial initial; "&gt;&#xD;
&lt;div panelheader"="" style="padding-top: 10px; padding-right: 10px; padding-bottom: 5px; padding-left: 10px; line-height: 1em; text-align: left; border-bottom-width: 1px; border-bottom-style: solid; background-image: initial; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: #f0f0f0; border-bottom-color: #f0f0f0; color: #333333; margin-bottom: 0px; border-top-right-radius: 5px 5px; border-top-left-radius: 5px 5px; background-position: initial initial; background-repeat: initial initial; "&gt;&lt;strong&gt;AgentServiceIntegrationTest.java&lt;/strong&gt;&lt;/div&gt;&#xD;
&lt;div panelcontent"="" style="background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; color: #333333; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 10px; padding-bottom: 0px; padding-left: 10px; text-align: left; font-size: 0.95em; background-position: initial initial; background-repeat: initial initial; "&gt;&#xD;
&lt;pre style="padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; text-align: left; overflow-x: auto; overflow-y: auto; font-family: 'Courier New', Courier, monospace; line-height: 1.3; "&gt;&lt;span style="color: #000091; background-color: inherit; "&gt;public&lt;/span&gt; class AgentServiceIntegrationTest &lt;span style="color: #000091; background-color: inherit; "&gt;extends&lt;/span&gt; IntegrationTest{ @Before &lt;span style="color: #000091; background-color: inherit; "&gt;public&lt;/span&gt; void setUp(){ } @Test &lt;span style="color: #000091; background-color: inherit; "&gt;public&lt;/span&gt; void should_find_agent_by_id(){ } @Rollback(&lt;span style="color: #000091; background-color: inherit; "&gt;false&lt;/span&gt;) @Test &lt;span style="color: #000091; background-color: inherit; "&gt;public&lt;/span&gt; void we_want_to_store_data_in_database(){ } @After &lt;span style="color: #000091; background-color: inherit; "&gt;public&lt;/span&gt; void tearDown(){ } } &lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;&lt;strong&gt;&lt;a name="%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95-%E4%BD%95%E6%97%B6%E6%B7%BB%E5%8A%A0%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95"&gt;&lt;/a&gt;何时添加自动化测试&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;&lt;a name="%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95-%E8%B0%83%E8%AF%95"&gt;&lt;/a&gt;调试&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;当你打开调试器，一步一步的调试代码来找出那个纠结的问题时，为什么不添加一个自动化测试来帮你定位问题呢。很多时候，这类调试甚至要启动一个tomcat才能进行。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;&lt;a name="%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95-%E6%89%93%E5%8D%B0%E8%BE%93%E5%87%BA"&gt;&lt;/a&gt;打印输出&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;我在有些代码里看到过System.out.println("test"); 我估计是因为我们在用这种方式来寻找bug，这个时候也是添加自动化测试的良机，不要错过。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;&lt;a name="%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95-%E5%8F%91%E7%8E%B0bug%E7%9A%84%E6%97%B6%E5%80%99"&gt;&lt;/a&gt;发现bug的时候&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p style="font-size: 10pt; line-height: 13pt; color: #333333; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; margin-top: 10px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; "&gt;每个bug的发现，就说明你开发这段代码的时候忽视了某些问题，这个时候我们就应该添加一个测试复现这个bug，然后修复bug。而不是仅仅修复了事。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;下面有个简单的ppt&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;/span&gt;&#xD;
&lt;div style="width:425px" id="__ss_10585348"&gt;&lt;strong style="display:block;margin:12px 0 4px"&gt;&lt;a href="http://www.slideshare.net/yuyijq/automation-test" title="Automation test"&gt;Automation test&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse10585348" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=automationtest-111214001319-phpapp01&amp;stripped_title=automation-test&amp;userName=yuyijq" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;param name="wmode" value="transparent"/&gt;&lt;embed name="__sse10585348" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=automationtest-111214001319-phpapp01&amp;stripped_title=automation-test&amp;userName=yuyijq" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" wmode="transparent" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div style="padding:5px 0 12px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/yuyijq"&gt;yuyijq&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;&lt;img src="http://www.cnblogs.com/yuyijq/aggbug/2287531.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yuyijq/archive/2011/12/14/2287531.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yuyijq/archive/2011/12/02/2271349.html</id><title type="text">memcached源代码阅读笔记（一）</title><summary type="text">缓存在系统设计中占有非常重要的地位。对于构建大型系统，缓存的使用非常重要。一般，缓存的目的都是为了将那些耗时又耗资源的东西，临时的保存在一个能够快速获取的地方，以此来减少资源的消耗，让我们可以更快的得到那些东西。简单的情况，比如我们可以把从数据库里取到的数据放到一个hashtable中，比如以sql为条件。在一定时间内，只要去数据库里取一次，然后就可以重复利用多次。这是个非常简单的缓存利用场景。但是随着系统规模的增长，这种缓存机制就慢慢的不足以应付我们的需求。因为hashtable的这种方式，决定着这个hashtable肯定是与我们的应用放到同一个进程的。如果系统规模大了，我们可能要部署多个应</summary><published>2011-12-02T06:28:00Z</published><updated>2011-12-02T06:28:00Z</updated><author><name>横刀天笑</name><uri>http://www.cnblogs.com/yuyijq/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yuyijq/archive/2011/12/02/2271349.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yuyijq/archive/2011/12/02/2271349.html"/><content type="html">&lt;p&gt;缓存在系统设计中占有非常重要的地位。对于构建大型系统，缓存的使用非常重要。一般，缓存的目的都是为了将那些耗时又耗资源的东西，临时的保存在一个能够快速获取的地方，以此来减少资源的消耗，让我们可以更快的得到那些东西。简单的情况，比如我们可以把从数据库里取到的数据放到一个hashtable中，比如以sql为条件。在一定时间内，只要去数据库里取一次，然后就可以重复利用多次。这是个非常简单的缓存利用场景。&lt;/p&gt;&lt;p&gt;但是随着系统规模的增长，这种缓存机制就慢慢的不足以应付我们的需求。因为hashtable的这种方式，决定着这个hashtable肯定是与我们的应用放到同一个进程的。如果系统规模大了，我们可能要部署多个应用，进程内的hashtable不能为多个不同进程的应用提供支持了，我们就必须将这个hashtable独立出来，独立到应用外，让几个应用共享这个hashtable。这样，专门的缓存服务器就出现了。而memcache就是这其中极为优秀的一款。&amp;nbsp;&lt;/p&gt;&lt;p&gt;那么从表面来看，类似memcache这种缓存服务器的结构应该是这个样子：&lt;/p&gt;&lt;p&gt;user &amp;lt;--------command and data---------&amp;gt; Network layer &amp;lt;-----------------&amp;gt;parse user command &amp;lt;------------hashtable&lt;/p&gt;&lt;p&gt;更通俗点讲就是讲一个hashtable放到了网上提供服务。&lt;/p&gt;&lt;p&gt;但是，因为这样我们访问缓存需要访问网络，所以就比访问本地的hashtable性能要差。而且一般来讲，在一个繁忙的系统种，缓存的访问是非常频繁的，如果缓存访问不频繁，那么大部分请求就会落到最终的资源上：比如数据库。这样就会给这些慢速的资源带来更大的压力。&lt;/p&gt;&lt;p&gt;所以这种缓存服务器的网络层一定要优秀，除了能提供很大的并发量外，还要能提供很好的响应能力。而作为后端的hashtable也要提供很好的访问速度。在内存分配上也要更有效率。&lt;/p&gt;&lt;p&gt;memcache&lt;/p&gt;&lt;p&gt;memcache是一个用C写的缓存服务器。它的网络层使用的是大名鼎鼎的libevent（一个基于事件驱动的网络库）。libevent在linux上可以利用epoll这柄利器，而在windows上能利用IOCP这把尖刀。除此之外，还能将网络的传输与业务逻辑处理分离开来。&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;好了，上面对缓存的基本知识做了点介绍，也简单的介绍了下memcache的结构，下面我们将先从一个用户的角度来简单看看如何使用memcache，会使用后再深入代码，也能相互做个对应。&amp;nbsp;&lt;/p&gt;&lt;p&gt;跟所有的开源代码一样，checkout出来，三步就可以使用了：&lt;/p&gt;&lt;p&gt;./configure&lt;/p&gt;&lt;p&gt;make&lt;/p&gt;&lt;p&gt;make install&lt;/p&gt;&lt;p&gt;然后进入memcache的目录，启动memcached：&lt;/p&gt;&lt;p&gt;./memcached -p 9000&lt;/p&gt;&lt;p&gt;memcached提供了tcp和udp两种方式，不过大部分时候我们都是采用tcp的方式。memcache的协议也有两种方式：文本和二进制。&lt;/p&gt;&lt;p&gt;注：现在memcache的协议基本上成了很多网络服务的标准协议了。即使一些跟memcache半点关系都没有的服务也提供memcache协议访问的方式，这样可以让memcache客户端来连接。&amp;nbsp;&lt;/p&gt;&lt;p&gt;下面我们就用telnet连上memcache服务器，简单的操作几个命令：&lt;/p&gt;&lt;p&gt;&amp;gt;telnet 127.0.0.1 9000&lt;/p&gt;&lt;p&gt;stats&lt;/p&gt;&lt;div&gt;&lt;div&gt;STAT pid 3694&lt;/div&gt;&lt;div&gt;STAT uptime 119&lt;/div&gt;&lt;div&gt;STAT time 1323700770&lt;/div&gt;&lt;div&gt;STAT version 1.4.10&lt;/div&gt;&lt;div&gt;STAT libevent 2.0.16-stable&lt;/div&gt;&lt;div&gt;STAT pointer_size 64&lt;/div&gt;&lt;div&gt;STAT rusage_user 0.003002&lt;/div&gt;&lt;div&gt;STAT rusage_system 0.005005&lt;/div&gt;&lt;div&gt;STAT curr_connections 10&lt;/div&gt;&lt;div&gt;STAT total_connections 11&lt;/div&gt;&lt;div&gt;STAT connection_structures 11&lt;/div&gt;&lt;div&gt;STAT reserved_fds 20&lt;/div&gt;&lt;div&gt;STAT cmd_get 0&lt;/div&gt;&lt;div&gt;STAT cmd_set 0&lt;/div&gt;&lt;div&gt;STAT cmd_flush 0&lt;/div&gt;&lt;div&gt;STAT cmd_touch 0&lt;/div&gt;&lt;div&gt;STAT get_hits 0&lt;/div&gt;&lt;div&gt;STAT get_misses 0&lt;/div&gt;&lt;div&gt;STAT delete_misses 0&lt;/div&gt;&lt;div&gt;STAT delete_hits 0&lt;/div&gt;&lt;div&gt;STAT incr_misses 0&lt;/div&gt;&lt;div&gt;STAT incr_hits 0&lt;/div&gt;&lt;div&gt;STAT decr_misses 0&lt;/div&gt;&lt;div&gt;STAT decr_hits 0&lt;/div&gt;&lt;div&gt;STAT cas_misses 0&lt;/div&gt;&lt;div&gt;STAT cas_hits 0&lt;/div&gt;&lt;div&gt;STAT cas_badval 0&lt;/div&gt;&lt;div&gt;STAT touch_hits 0&lt;/div&gt;&lt;div&gt;STAT touch_misses 0&lt;/div&gt;&lt;div&gt;STAT auth_cmds 0&lt;/div&gt;&lt;div&gt;STAT auth_errors 0&lt;/div&gt;&lt;div&gt;STAT bytes_read 7&lt;/div&gt;&lt;div&gt;STAT bytes_written 0&lt;/div&gt;&lt;div&gt;STAT limit_maxbytes 67108864&lt;/div&gt;&lt;div&gt;STAT accepting_conns 1&lt;/div&gt;&lt;div&gt;STAT listen_disabled_num 0&lt;/div&gt;&lt;div&gt;STAT threads 4&lt;/div&gt;&lt;div&gt;STAT conn_yields 0&lt;/div&gt;&lt;div&gt;STAT hash_power_level 16&lt;/div&gt;&lt;div&gt;STAT hash_bytes 524288&lt;/div&gt;&lt;div&gt;STAT hash_is_expanding 0&lt;/div&gt;&lt;div&gt;STAT expired_unfetched 0&lt;/div&gt;&lt;div&gt;STAT evicted_unfetched 0&lt;/div&gt;&lt;div&gt;STAT bytes 0&lt;/div&gt;&lt;div&gt;STAT curr_items 0&lt;/div&gt;&lt;div&gt;STAT total_items 0&lt;/div&gt;&lt;div&gt;STAT evictions 0&lt;/div&gt;&lt;div&gt;STAT reclaimed 0&lt;/div&gt;&lt;div&gt;END&lt;/div&gt;&lt;/div&gt;&lt;div&gt;打印出memcache的一些监控数据&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;stats settings&lt;/p&gt;&lt;div&gt;&lt;div&gt;STAT maxbytes 67108864&lt;/div&gt;&lt;div&gt;STAT maxconns 1024&lt;/div&gt;&lt;div&gt;STAT tcpport 9000&lt;/div&gt;&lt;div&gt;STAT udpport 9000&lt;/div&gt;&lt;div&gt;STAT inter NULL&lt;/div&gt;&lt;div&gt;STAT verbosity 0&lt;/div&gt;&lt;div&gt;STAT oldest 0&lt;/div&gt;&lt;div&gt;STAT evictions on&lt;/div&gt;&lt;div&gt;STAT domain_socket NULL&lt;/div&gt;&lt;div&gt;STAT umask 700&lt;/div&gt;&lt;div&gt;STAT growth_factor 1.25&lt;/div&gt;&lt;div&gt;STAT chunk_size 48&lt;/div&gt;&lt;div&gt;STAT num_threads 4&lt;/div&gt;&lt;div&gt;STAT num_threads_per_udp 4&lt;/div&gt;&lt;div&gt;STAT stat_key_prefix :&lt;/div&gt;&lt;div&gt;STAT detail_enabled no&lt;/div&gt;&lt;div&gt;STAT reqs_per_event 20&lt;/div&gt;&lt;div&gt;STAT cas_enabled yes&lt;/div&gt;&lt;div&gt;STAT tcp_backlog 1024&lt;/div&gt;&lt;div&gt;STAT binding_protocol auto-negotiate&lt;/div&gt;&lt;div&gt;STAT auth_enabled_sasl no&lt;/div&gt;&lt;div&gt;STAT item_size_max 1048576&lt;/div&gt;&lt;div&gt;STAT maxconns_fast no&lt;/div&gt;&lt;div&gt;STAT hashpower_init 0&lt;/div&gt;&lt;div&gt;END&lt;/div&gt;&lt;div&gt;打印出memcache的设置信息&lt;/div&gt;&lt;p&gt;set user1 32 0 10&lt;/p&gt;&lt;p&gt;{'uname':'yuyijq','pwd':'12345'}&lt;/p&gt;&lt;p&gt;STORED&lt;/p&gt;&lt;p&gt;上面的命令将一个User对象的JSON放到memcache中。&lt;/p&gt;&lt;p&gt;get user1&lt;/p&gt;&lt;p&gt;{'uname':'yuyijq','pwd':'12345'}&lt;/p&gt;&lt;p&gt;END&lt;/p&gt;&lt;p&gt;根据刚才存储的key，取回那个用户对象的json串。&lt;/p&gt;&lt;p&gt;因为这个协议非常简单命令，所以也很容易开发一个client，在程序里我们就可以利用上memcache这个利器了。&amp;nbsp;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;好了，下一篇我们将根据这一篇输入的几个命令，来对应的追随代码的脚印。&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/yuyijq/aggbug/2271349.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yuyijq/archive/2011/12/02/2271349.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yuyijq/archive/2011/09/03/2165347.html</id><title type="text">assertThat, assertEquals, assertTrue</title><summary type="text">昨天晚上是AgileChina 2011的Open House活动，我是Coding环节的志愿者。Coding环节主要是想让参会的开发人员体验一下结对编程、测试驱动开发以及重构的过程。我们准备了四个不同类型的编程题目，公司会有八九位同事和参会的同行一起来体验这个过程在最后一轮Pair当中，一位同学问到：为什么不使用assertEquals呢？我看到你们都是在用assertThat，好像不怎么提倡用assertEquals和assertTrue等。当时因为活动快结束了，我们要去拍合照，所以简单的回答了一下。这里再详细回答一下这个问题。</summary><published>2011-09-03T05:27:00Z</published><updated>2011-09-03T05:27:00Z</updated><author><name>横刀天笑</name><uri>http://www.cnblogs.com/yuyijq/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yuyijq/archive/2011/09/03/2165347.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yuyijq/archive/2011/09/03/2165347.html"/><content type="html">&lt;p&gt;昨天晚上是AgileChina 2011的Open House活动，我是Coding环节的志愿者。Coding环节主要是想让参会的开发人员体验一下结对编程、测试驱动开发以及重构的过程。我们准备了四个不同类型的编程题目，公司会有八九位同事和参会的同行一起来体验这个过程：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Time Sheet&lt;/li&gt;    &lt;li&gt;超市结账&lt;/li&gt;    &lt;li&gt;源代码行数统计&lt;/li&gt;    &lt;li&gt;21点游戏&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;总体说来，活动还是挺成功的，只是出现两个小插曲：&lt;/p&gt;  &lt;p&gt;1、公司为了这次活动新买的键盘和鼠标，可是敲起来太没手感了，键盘上的键都是平平的。有位参与者可能是不太熟悉键盘总是把回车敲错成斜杠很多次。&lt;/p&gt;  &lt;p&gt;2、系统的锁屏快捷键与IDE的格式化代码快捷键相冲突，导致我两次敲错了锁屏了，而且机器也不是我的，要找其他同事来帮忙输入密码，汗死。&lt;/p&gt;  &lt;p&gt;在最后一轮Pair当中，一位同学问到：为什么不使用assertEquals呢？我看到你们都是在用assertThat，好像不怎么提倡用assertEquals和assertTrue等。&lt;/p&gt;  &lt;p&gt;当时因为活动快结束了，我们要去拍合照，所以简单的回答了一下。这里再详细回答一下这个问题。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;我们想从断言里获得什么&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;对于测试里的断言，我们想从中获得什么呢？测试失败后仅仅显示一个红色条并不是我们想要的，除此之外我们还想从测试中获得一些信息：&lt;/p&gt;  &lt;p&gt;1、哪里的测试失败了，有代码行么？这个所有的断言都能提供这个功能&lt;/p&gt;  &lt;p&gt;2、测试为什么失败 这个就很难界定了。大部分断言都能提供类似这种功能：&lt;/p&gt;  &lt;p&gt;期望的结果 vs 实际的结果&lt;/p&gt;  &lt;p&gt;但是不同的断言提供的信息却不尽相同。还有一些问题，用简单的相等之类的断言是很难比较的。而且，断言还有一个非常重要的作用：文档。也就是，当你阅读测试代码时，看到断言你就像是看到了所有的期望。而且非常明了的期望。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;实际的例子&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;看看assertThat的第二个参数，它接受的是一个Matcher&amp;lt;T&amp;gt;，在org.hamcrest里有非常丰富的Matcher实现。比如现在我们的API返回一个用户对象的列表，我们想断言这个列表里包含我们期望的一个用户，hamcrest里就有hasItem这么一个Matcher：&lt;/p&gt;  &lt;div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"&gt;   &lt;div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"&gt;     &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum1"&gt;   1:&lt;/span&gt; assertThat(userDAO.findAll(), hasItem(expected));&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&lt;/div&gt;&#xD;
&lt;/div&gt;&#xD;
&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;如果用其他断言该怎么做？&lt;/p&gt;&#xD;
&#xD;
&lt;div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"&gt;&#xD;
  &lt;div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"&gt;&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum1"&gt;   1:&lt;/span&gt; assertTrue(userDAO.findAll().contains(expected));&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&lt;/div&gt;&#xD;
&lt;/div&gt;&#xD;
&#xD;
&lt;p&gt;好，这就是问题所在，如果上面两个断言都失败了，第一个断言的失败信息将是：&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;期望结果中包含expected&amp;#8230;.&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;但是实际上&amp;#8230;这里会把findAll返回的所有user都打印出来&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;而第二个断言呢？是这样的：&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;期望为true，实际上为false&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;请问哪一个更靠谱一点？显然第一个断言的失败信息更利于我们寻找问题。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;对于文档化，我们再来看这么一个要求：断言返回的用户列表里不包含给定的用户：&lt;/p&gt;&#xD;
&#xD;
&lt;div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"&gt;&#xD;
  &lt;div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"&gt;&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum1"&gt;   1:&lt;/span&gt; assertThat(userDAO.findAll(), not(hasItem(expected));&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&lt;/div&gt;&#xD;
&lt;/div&gt;&#xD;
&#xD;
&lt;p&gt;是不是很清晰明了，读这个断言你不会感觉到你是在读代码，就好像在读Spec。如果用其他的断言：&lt;/p&gt;&#xD;
&#xD;
&lt;div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"&gt;&#xD;
  &lt;div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"&gt;&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum1"&gt;   1:&lt;/span&gt; assertFalse(userDAO.findAll().contains(expected));&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&lt;/div&gt;&#xD;
&lt;/div&gt;&#xD;
&#xD;
&lt;p&gt;都没有前一个更好的文档化。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;当然，对于一些API返回的就是true或false的，或者返回的就是个单值的，你也可以用其他的断言，没有多大问题。比如这个：&lt;/p&gt;&#xD;
&#xD;
&lt;div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"&gt;&#xD;
  &lt;div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"&gt;&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum1"&gt;   1:&lt;/span&gt; assertEquals(userDAO.findBy(id), expected);&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&lt;/div&gt;&#xD;
&lt;/div&gt;&#xD;
&#xD;
&lt;p&gt;啊，什么？你说我用错了，为什么啊。查查参数说明，发现原来期望的值应该放到第一个参数，而实际值应该放到第二个参数。好无奈啊，我记忆力不好，总是会弄错这些，幸好我们有了assertThat：&lt;/p&gt;&#xD;
&#xD;
&lt;div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"&gt;&#xD;
  &lt;div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"&gt;&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum1"&gt;   1:&lt;/span&gt; assertThat(userDAO.findBy(id), is(expected));&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&lt;/div&gt;&#xD;
&lt;/div&gt;&#xD;
&#xD;
&lt;p&gt;你还会弄错么？&lt;/p&gt;&lt;img src="http://www.cnblogs.com/yuyijq/aggbug/2165347.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yuyijq/archive/2011/09/03/2165347.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yuyijq/archive/2011/08/31/2160252.html</id><title type="text">简单设计</title><summary type="text">XP（极限编程）里除了大名鼎鼎的TDD，重构等最佳实践外还有一些不怎么被人注意的实践，比如本文想谈的简单设计（Simple Design）。我们常常说设计要简单，简单即美。甚至敏捷软件开发的四个要素（沟通、反馈、简单、勇气）简单也位于其中。那到底什么是XP开发者眼中所认为的简单呢？在Kent Beck所著的《解析极限编程》里对简单设计有四条“简单”的描述，通常也被大家称为简单设计四原则</summary><published>2011-08-31T00:10:00Z</published><updated>2011-08-31T00:10:00Z</updated><author><name>横刀天笑</name><uri>http://www.cnblogs.com/yuyijq/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yuyijq/archive/2011/08/31/2160252.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yuyijq/archive/2011/08/31/2160252.html"/><content type="html">&lt;p&gt;XP（极限编程）里除了大名鼎鼎的TDD，重构等最佳实践外还有一些不怎么被人注意的实践，比如本文想谈的简单设计（Simple Design）。&lt;/p&gt;  &lt;p&gt;我们常常说设计要简单，简单即美。甚至敏捷软件开发的四个要素（沟通、反馈、简单、勇气）简单也位于其中。那到底什么是XP开发者眼中所认为的简单呢？&lt;/p&gt;  &lt;p&gt;在Kent Beck所著的《解析极限编程》里对简单设计有四条&amp;#8220;简单&amp;#8221;的描述，通常也被大家称为简单设计四原则：&lt;/p&gt;  &lt;blockquote style='border:2px solid #EFEFEF;color:#333333;padding:5px 10px;'&gt;   &lt;p&gt;通过所有测试&lt;/p&gt;    &lt;p&gt;体现所有意图&lt;/p&gt;    &lt;p&gt;消除重复&lt;/p&gt;    &lt;p&gt;类和方法尽量短小&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;可能和原文有所出入，顺序也不同（注：我这里列出的顺序不代表它的优先级），但要表达的意思是这样的。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;通过所有测试&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;对于通过所有测试这一条，它是与XP的另外一个实践TDD相辅相成的。通过所有测试不仅仅表明程序运行&amp;#8220;正确（正确是相对的）&amp;#8221;。更重要的是一种设计态度。&lt;/p&gt;  &lt;p&gt;要通过测试就得能写测试，要为程序编写测试并不是一件容易的事情，&amp;#8220;复杂&amp;#8221;或&amp;#8220;混乱&amp;#8221;的程序是很难测试的。可测试性本来就是设计质量的一项考量标准。铁板一块的程序无法很容易解耦，要对其进行测试只有在铁板的外围进行测试，测试的粒度如此之大，先不说是否好构建测试，即使测试构建好了，如果测试失败了也很难发现问题之所在。&lt;/p&gt;  &lt;p&gt;所以光满足可测试性这一条就要死伤很多脑细胞。你得让该模块所依赖的东西能轻松的&amp;#8220;注入&amp;#8221;进去，这样就能解耦，这样就能单独对这个模块进行测试，这样就能在更小的粒度上进行测试。&lt;/p&gt;  &lt;p&gt;比如很简单的一个实例，我们做一个计算税率的程序，假设税率的获取依靠第三方系统提供，那我们在开发之前可以将其划分为几个任务：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;输入金额&lt;/li&gt;    &lt;li&gt;计算（从第三方获取税率）&lt;/li&gt;    &lt;li&gt;输出结果&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;很好，我们可以这样&amp;#8220;即兴设计&amp;#8221;出一个方法：&lt;/p&gt;  &lt;p&gt;从终端读入金额，然后实例化一个第三方的&amp;#8220;本地代理对象&amp;#8221;，调用其接口获取税率，然后计算，得到结果后输出到终端。&lt;/p&gt;  &lt;p&gt;很简单是不，但是这个方法如何测试呢？你也许可以说：我们可以重定向终端到文件，这样就可以提供测试数据并得到输出，那么第三方系统呢？&lt;/p&gt;  &lt;p&gt;我们想想，对于这个小程序，我们最关心的是计算逻辑是否正确，对于从终端读入、从第三方获取税率以及输出到终端都是较难测试的，我们必须把这个依赖解耦，然后就可以对计算逻辑测试了。&lt;/p&gt;  &lt;p&gt;解开依赖实际上还不能完全满足可测试性的要求，要这个测试容易构建还得看看你到底有多少依赖。我们常常将一个测试分为三段式：&lt;/p&gt;  &lt;blockquote style='border:2px solid #EFEFEF;color:#333333;padding:5px 10px;'&gt;   &lt;p&gt;Given&lt;/p&gt;    &lt;p&gt;这里用来构建依赖，以及输入对象&lt;/p&gt;    &lt;p&gt;When&lt;/p&gt;    &lt;p&gt;这里调用被测试元素&lt;/p&gt;    &lt;p&gt;Then&lt;/p&gt;    &lt;p&gt;这里用来测试结果&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;常常有这样的测试，它的Given段非常长，而When和Then非常短小。这就是一种信号：这个玩意儿依赖的东西太多了，需要很多代码来构建依赖，如果有那么两个依赖很难构建那又更困难了。所以说解开依赖还不够，还得要容易构建依赖。实际上依赖太多常常说明一件事情：你的类或方法职责不单一，做的事情太多。这里的类有两个意思：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;要么是你的被测试类的职责不单一，干的事情太多，需要从许多地方获取所需的功能，这样如果有一个依赖改变了，那么你这个类就得变，如果有两个依赖变了，而且变化的方法还不是统一的，那么你这个类就被扯的四分五裂。&lt;/li&gt;    &lt;li&gt;要么是被测试类所依赖的类不单一，本来从一个地方获取功能就可以了，现在要从好几个地方获取。&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;好了，关于通过所有测试这一条就谈这么多吧，这块的内容要谈可以有很多。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;体现所有意图&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;不管是高德纳所说的文学编程，还是领域驱动开发里面都有体现意图这层意思。体现意图不仅要体现作者（也就是作为开发者的呢）的意图，你当时是怎么想的，还要体现当前业务的意图。我们要将业务需求通过代码来表达出来。其实表达这些意图的方式很简单：起名字。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;好的类名，好的方法名，好的变量名&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;在这些名字上多斟酌一点时间，回报是巨大的。代码阅读的次数会比编写的次数多，所以如果你想节约点阅读的时间，让你的代码生命力更长久点，那么就在这上面多花点时间吧。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;消除重复&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;其实在大部分时候，消除重复往往是我们重构的第一步，也是重构的契机。消除重复往往能诞生出更好的设计结果。因为重复消除了，你总是要使用一些抽象的手段将这些原来使用重复代码，而现在被删除了的地方连接起来。你总有可能提取新的基类或接口。消除重复不仅能降低bug（如果修改了一处重复的代码，你得修改多出，修改的越多就越容易出错），更重要的是能降低复杂性。关于消除重复《Refactor》这本书里讲了很多手段，这里就不啰嗦了。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;类和方法尽量短&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;这里有两个问题：为什么要短？到底要多短？&lt;/p&gt;  &lt;p&gt;我总觉得我的脑子太笨，一下子让我理解很多的内容我大脑会混乱。我喜欢的短的类和方法，这样我在同一时刻就可以理解少一点东西。但是也有人说每个类或方法短就会产生很多类和方法。从某种程度上是正确的，但也不对。我们不仅要强调短，我们还得强调集中（或单一职责）。我们不是为了追求短而短，不是说为了短，我把所有的类都拆成两个。短需要短的理由，如果这个方法有500行，但是它的内聚性非常好，干的事儿单一，把它任何一个方法拆出去都显得不完整，那么我觉得没有理由要让它变短。&lt;/p&gt;  &lt;p&gt;短的方法和类不仅容易理解，还容易测试，因为短的方法和类&amp;#8220;往往&amp;#8221;更趋向于职责单一（当然也有例外）。而且短的类和方法又更容易用名字来说明意图了，因为干的事儿少嘛。&lt;/p&gt;  &lt;p&gt;那么我到底该多短呢？这个太有争议了。有人说一屏幕能显示就可以了。问为什么一屏显示就可以了啊？答：这样就不用拖动滚动条了。如果你是这个回答我就不认同了。我们要短的类和方法并不是为了不拖动滚动条，如果真的实现了这个目标那也是个副产品。如果你们团队用的是一样的屏幕，你们可以建立一个Guideline说一个类一屏。但理由绝对不是不拖动滚动条。而且注意的是这是一个Guideline，不是死规定。也就是说如果别人有充足的理由，是可以打破这个Guideline的。&lt;/p&gt;  &lt;p&gt;PS：我们团队的Guideline是每个方法15行（这只是我们的Guideline，但是我们竭力去维护它）。&lt;/p&gt;  &lt;p&gt;我想，如果我们在编写代码是不是那么的随兴发挥，我们字字句句的斟酌一番记住上面简单设计的四个原则，我想离Simple Design不会太远。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/yuyijq/aggbug/2160252.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yuyijq/archive/2011/08/31/2160252.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yuyijq/archive/2011/08/31/2160246.html</id><title type="text">给Cuke4Duke添加一个AfterAll标签（一）：使用Cuke4Duke</title><summary type="text">我们团队使用的Cucumber作为我们的功能测试框架。Cucumber是一种BDD（Behaviour driven development）测试框架。感兴趣的读者可以去Cucumber的官网了解BDD和该框架更详细的信息：点击这里。在这里我就不详细描述了。 那么这和本文要说的Cuke4Duke有什么关系呢？实际上Cucumber是ruby编写的，原生的也只对ruby编写测试提供支持。而Cuke...</summary><published>2011-08-30T23:08:00Z</published><updated>2011-08-30T23:08:00Z</updated><author><name>横刀天笑</name><uri>http://www.cnblogs.com/yuyijq/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yuyijq/archive/2011/08/31/2160246.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yuyijq/archive/2011/08/31/2160246.html"/><content type="html">&lt;p&gt;我们团队使用的Cucumber作为我们的功能测试框架。Cucumber是一种BDD（Behaviour driven development）测试框架。感兴趣的读者可以去Cucumber的官网了解BDD和该框架更详细的信息：&lt;a href="http://cukes.info" target="_blank"&gt;点击这里&lt;/a&gt;。在这里我就不详细描述了。&lt;/p&gt;  &lt;p&gt;那么这和本文要说的Cuke4Duke有什么关系呢？实际上Cucumber是ruby编写的，原生的也只对ruby编写测试提供支持。而Cuke4Duke就像是给Cucumber提供一个包装器，让你可以使用Java编写测试。后文我将交换着使用Cucumber和Cuke4Duke。&lt;/p&gt;  &lt;p&gt;我们在使用Cuke4Duke的时候也碰到了一些问题。比如原生的Cucumber是可以通过ruby的at_exit回调，在测试跑完之后执行点代码，但是Cuke4Duke里却没有这么一个回调。Cuke4Duke里有Before和After等annotation，但是这些annotation标记的方法是在每个场景开始之前之后执行的，而没有提供整个测试跑完之后的回调方式。&lt;/p&gt;  &lt;p&gt;所以我就想通过修改源代码的方式添加一个AfterAll的标签。为了实现这个我们得首先对Cuke4Duke以及Cucumber有个基本的了解。本文的目的就是简单的介绍下Cuke4Duke和Cucumber是个啥东西。&lt;/p&gt;  &lt;p&gt;对于Cucumber优点我只简单描述一下：它可以更好管理测试用例，更好的协调QA和开发人员的工作（当然，我对其了解也不太深）。&lt;/p&gt;  &lt;p&gt;举个例子，如果我们要开发一个用户登录页面，QA先写好测试用例，然后开发人员来实现。而且我们想将这些测试用例和代码很好的结合起来管理，而不是放到一个Excel文件里。如果你使用Cucumber你可以这样做：QA编写下面这样的features文件：&lt;/p&gt;  &lt;div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"&gt;   &lt;div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"&gt;     &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum1"&gt;   1:&lt;/span&gt; //login.feature&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum2"&gt;   2:&lt;/span&gt;&amp;#160; &lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum3"&gt;   3:&lt;/span&gt; Feature: 用户登录页面&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum4"&gt;   4:&lt;/span&gt;     In order 访问到需要授权的内容&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum5"&gt;   5:&lt;/span&gt;     As a 未登录用户&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum6"&gt;   6:&lt;/span&gt;     I want to 登录网站&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum7"&gt;   7:&lt;/span&gt;&amp;#160; &lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum8"&gt;   8:&lt;/span&gt;     Scenario:登录网站&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum9"&gt;   9:&lt;/span&gt;         Given 我打开登录页面&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum10"&gt;  10:&lt;/span&gt;         And 输入用户名和密码&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum11"&gt;  11:&lt;/span&gt;         When 点击登录按钮&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum12"&gt;  12:&lt;/span&gt;         Then 登录成功，页面跳转到首页，并在右上角显示用户名&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&lt;/div&gt;&#xD;
&lt;/div&gt;&#xD;
&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;这就是个很典型的测试用例（当然，QA写的会更专业）。我们现在要的不仅是让这些测试用例躺在那儿，还要让它跑起来。让测试用例活起来。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;让测试用例活起来有很多方法，但是如何将你实现测试用例的代码和上面这里写的测试用例关联起来呢？如果你使用Cucumber可以这么做：&lt;/p&gt;&#xD;
&#xD;
&lt;div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"&gt;&#xD;
  &lt;div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"&gt;&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum1"&gt;   1:&lt;/span&gt; &lt;span style="color: #0000ff"&gt;public&lt;/span&gt; &lt;span style="color: #0000ff"&gt;class&lt;/span&gt; LoginSteps{&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum2"&gt;   2:&lt;/span&gt;     &lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum3"&gt;   3:&lt;/span&gt;     @Given(&lt;span style="color: #006080"&gt;&amp;quot;^我打开登录页面$&amp;quot;&lt;/span&gt;)&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum4"&gt;   4:&lt;/span&gt;     &lt;span style="color: #0000ff"&gt;public&lt;/span&gt; &lt;span style="color: #0000ff"&gt;void&lt;/span&gt; I_open_login_page(){&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum5"&gt;   5:&lt;/span&gt;         &lt;span style="color: #008000"&gt;//open login page&lt;/span&gt;&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum6"&gt;   6:&lt;/span&gt;     }&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum7"&gt;   7:&lt;/span&gt;     &lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum8"&gt;   8:&lt;/span&gt;     @And(&lt;span style="color: #006080"&gt;&amp;quot;^输入用户名和密码$&amp;quot;&lt;/span&gt;)&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum9"&gt;   9:&lt;/span&gt;     &lt;span style="color: #0000ff"&gt;public&lt;/span&gt; &lt;span style="color: #0000ff"&gt;void&lt;/span&gt; input_uname_and_pwd(){&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum10"&gt;  10:&lt;/span&gt;         &lt;span style="color: #008000"&gt;//input uname and pwd&lt;/span&gt;&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum11"&gt;  11:&lt;/span&gt;     }&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum12"&gt;  12:&lt;/span&gt;     &lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum13"&gt;  13:&lt;/span&gt;     @When(&lt;span style="color: #006080"&gt;&amp;quot;^点击登录按钮$&amp;quot;&lt;/span&gt;)&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum14"&gt;  14:&lt;/span&gt;     &lt;span style="color: #0000ff"&gt;public&lt;/span&gt; &lt;span style="color: #0000ff"&gt;void&lt;/span&gt; click_login_button(){&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum15"&gt;  15:&lt;/span&gt;         &lt;span style="color: #008000"&gt;//click login button&lt;/span&gt;&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum16"&gt;  16:&lt;/span&gt;     }&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum17"&gt;  17:&lt;/span&gt;     &lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum18"&gt;  18:&lt;/span&gt;     @Then(&lt;span style="color: #006080"&gt;&amp;quot;^登录成功，页面跳转到首页，并在右上角显示用户名$&amp;quot;&lt;/span&gt;)&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum19"&gt;  19:&lt;/span&gt;     &lt;span style="color: #0000ff"&gt;public&lt;/span&gt; &lt;span style="color: #0000ff"&gt;void&lt;/span&gt; login_success_redirect_to_homepage_show_uname(){&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum20"&gt;  20:&lt;/span&gt;         &lt;span style="color: #008000"&gt;//...&lt;/span&gt;&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum21"&gt;  21:&lt;/span&gt;     }&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum22"&gt;  22:&lt;/span&gt; }&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&lt;/div&gt;&#xD;
&lt;/div&gt;&#xD;
&#xD;
&lt;p&gt;这个测试执行时Cucumber就会读取feature文件，然后寻找对应的实现方法，然后执行之。如果没发现对应的实现方法它会报告某个场景没有实现。如果所有方法都实现了并执行成功，在执行的窗口上会显示前面的feature内容。如果某一条失败，会在对应的场景下输出错误信息。这对于测试用例管理是非常有用的。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;不仅如此我们还可以在feature中向代码中传递参数：&lt;/p&gt;&#xD;
&#xD;
&lt;div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"&gt;&#xD;
  &lt;div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"&gt;&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum1"&gt;   1:&lt;/span&gt; &lt;span style="color: #008000"&gt;//login.feature&lt;/span&gt;&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum2"&gt;   2:&lt;/span&gt;&amp;#160; &lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum3"&gt;   3:&lt;/span&gt; Feature: 用户登录页面&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum4"&gt;   4:&lt;/span&gt;     In order 访问到需要授权的内容&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum5"&gt;   5:&lt;/span&gt;     As a 未登录用户&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum6"&gt;   6:&lt;/span&gt;     I want to 登录网站&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum7"&gt;   7:&lt;/span&gt;&amp;#160; &lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum8"&gt;   8:&lt;/span&gt;     Scenario Outline:登录网站&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum9"&gt;   9:&lt;/span&gt;         Given 我打开登录页面&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum10"&gt;  10:&lt;/span&gt;         And 输入&amp;lt;用户名&amp;gt;和&amp;lt;密码&amp;gt;&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum11"&gt;  11:&lt;/span&gt;         When 点击登录按钮&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum12"&gt;  12:&lt;/span&gt;         Then 登录成功，页面跳转到首页，并在右上角显示&amp;lt;用户名&amp;gt;&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum13"&gt;  13:&lt;/span&gt;&amp;#160; &lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum14"&gt;  14:&lt;/span&gt; Examples:&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum15"&gt;  15:&lt;/span&gt; |用户名|密码|&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum16"&gt;  16:&lt;/span&gt; |abc|123|&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum17"&gt;  17:&lt;/span&gt; |bcd|456|&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum18"&gt;  18:&lt;/span&gt; |uname|pwd|&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&lt;/div&gt;&#xD;
&lt;/div&gt;&#xD;
&#xD;
&lt;p&gt;实现：&lt;/p&gt;&#xD;
&#xD;
&lt;div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"&gt;&#xD;
  &lt;div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"&gt;&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum1"&gt;   1:&lt;/span&gt; &lt;span style="color: #0000ff"&gt;public&lt;/span&gt; &lt;span style="color: #0000ff"&gt;class&lt;/span&gt; LoginSteps{&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum2"&gt;   2:&lt;/span&gt;     &lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum3"&gt;   3:&lt;/span&gt;     @Given(&lt;span style="color: #006080"&gt;&amp;quot;^我打开登录页面$&amp;quot;&lt;/span&gt;)&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum4"&gt;   4:&lt;/span&gt;     &lt;span style="color: #0000ff"&gt;public&lt;/span&gt; &lt;span style="color: #0000ff"&gt;void&lt;/span&gt; I_open_login_page(){&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum5"&gt;   5:&lt;/span&gt;         &lt;span style="color: #008000"&gt;//open login page&lt;/span&gt;&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum6"&gt;   6:&lt;/span&gt;     }&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum7"&gt;   7:&lt;/span&gt;     &lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum8"&gt;   8:&lt;/span&gt;     @And(&lt;span style="color: #006080"&gt;&amp;quot;^输入(.*)和(.*)$&amp;quot;&lt;/span&gt;)&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum9"&gt;   9:&lt;/span&gt;     &lt;span style="color: #0000ff"&gt;public&lt;/span&gt; &lt;span style="color: #0000ff"&gt;void&lt;/span&gt; input_uname_and_pwd(String uname, String pwd){&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum10"&gt;  10:&lt;/span&gt;         &lt;span style="color: #008000"&gt;//input uname and pwd&lt;/span&gt;&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum11"&gt;  11:&lt;/span&gt;     }&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum12"&gt;  12:&lt;/span&gt;     &lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum13"&gt;  13:&lt;/span&gt;     @When(&lt;span style="color: #006080"&gt;&amp;quot;^点击登录按钮$&amp;quot;&lt;/span&gt;)&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum14"&gt;  14:&lt;/span&gt;     &lt;span style="color: #0000ff"&gt;public&lt;/span&gt; &lt;span style="color: #0000ff"&gt;void&lt;/span&gt; click_login_button(){&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum15"&gt;  15:&lt;/span&gt;         &lt;span style="color: #008000"&gt;//click login button&lt;/span&gt;&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum16"&gt;  16:&lt;/span&gt;     }&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum17"&gt;  17:&lt;/span&gt;     &lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum18"&gt;  18:&lt;/span&gt;     @Then(&lt;span style="color: #006080"&gt;&amp;quot;^登录成功，页面跳转到首页，并在右上角显示(.*)$&amp;quot;&lt;/span&gt;)&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum19"&gt;  19:&lt;/span&gt;     &lt;span style="color: #0000ff"&gt;public&lt;/span&gt; &lt;span style="color: #0000ff"&gt;void&lt;/span&gt; login_success_redirect_to_homepage_show_uname(String uname){&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum20"&gt;  20:&lt;/span&gt;         &lt;span style="color: #008000"&gt;//...&lt;/span&gt;&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum21"&gt;  21:&lt;/span&gt;     }&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum22"&gt;  22:&lt;/span&gt; }&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&lt;/div&gt;&#xD;
&lt;/div&gt;&#xD;
&#xD;
&lt;p&gt;这样就会用Examples里指定的数据重复的测试。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;Cuke4Duke里提供了Before和After标签：&lt;/p&gt;&#xD;
&#xD;
&lt;div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"&gt;&#xD;
  &lt;div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"&gt;&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum1"&gt;   1:&lt;/span&gt; @After&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum2"&gt;   2:&lt;/span&gt; &lt;span style="color: #0000ff"&gt;public&lt;/span&gt; &lt;span style="color: #0000ff"&gt;void&lt;/span&gt; clear_cookies(){&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum3"&gt;   3:&lt;/span&gt;     &lt;span style="color: #008000"&gt;//...&lt;/span&gt;&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: &amp;#39;Courier New&amp;#39;, courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum4"&gt;   4:&lt;/span&gt; }&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&lt;/div&gt;&#xD;
&lt;/div&gt;&#xD;
&#xD;
&lt;p&gt;这样我们就可以在每个Scenario跑完之后清除浏览器的cookies，或者清理一些插入的数据等等。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;Cucumber就是这么一个BDD的测试框架。当然，如果我们想进行具体的测试还需要一些别的框架的支持。比如我们要测试Web，可能就需要操作浏览器，访问页面上的HTML元素等。我们使用的是selenium。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;说些题外话&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;下面简单的说说怎样进行这类的测试。编写这类的功能测试我们的代码里需要四类元素：&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;1、feature 这就是QA编写的测试用例&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;2、test steps 这是开发人员或QA自己用代码自动化上面编写的测试用例（可以跑的测试用例）&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;3、Page Object 对于Web测试中，这就是用程序代码表示的Web页面。比如上面测试登录页面，就可以有一个LoginPage class。将login以及测试中需要关注的HTML元素通过xpath等方式定位出来，并作为成员通过LoginPage暴露。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;4、Fixture 在测试中可能还要准备某些数据，比如上面登录页面，我们可能要自己准备登录用户，比如往数据库里插入几条用户信息。这就是Fixture的职责。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;这样划分在测试越来越多后你就会受益的。更详细的描述你可以参见InfoQ上这篇文章：&lt;a href="http://www.infoq.com/cn/articles/domain-web-testing" target="_blank"&gt;点击这里&lt;/a&gt;。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;下一篇我会介绍Cuke4Duke如何与Cucumber一起协作，然后我们尝试添加一个AfterAll标签。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/yuyijq/aggbug/2160246.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yuyijq/archive/2011/08/31/2160246.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yuyijq/archive/2011/06/28/2091803.html</id><title type="text">做Java开发这一年</title><summary type="text">从去年到现在，从.NET转向Java开发（只是因为项目原因，绝对与平台好坏没有关系）差不多有一年的时间了。通过这一年时间也有些感触，想从几个面比较一下这两个平台。希望能做到客观公正。</summary><published>2011-06-28T03:57:00Z</published><updated>2011-06-28T03:57:00Z</updated><author><name>横刀天笑</name><uri>http://www.cnblogs.com/yuyijq/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yuyijq/archive/2011/06/28/2091803.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yuyijq/archive/2011/06/28/2091803.html"/><content type="html">&lt;p&gt;从去年到现在，从.NET转向Java开发（只是因为项目原因，绝对与平台好坏没有关系）差不多有一年的时间了。通过这一年时间也有些感触，想从几个面比较一下这两个平台。希望能做到客观公正。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;语言&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;我原来是使用C#语言的，和现在的Java语言相比，现在的Java语言语法就停留在C# 2.0这个年代。语法结构都非常传统，中规中矩。很突出的一点是，因为缺少对闭包的支持，有些用C#很容易做到的，用Java需要写很多废话代码。&lt;/p&gt;  &lt;p&gt;前几天InfoQ上发表了一篇英国卫报逐步采用Scala替换Java的文章里一句话用的很好：看Java的代码很容易让你只见树木，不见森林。因为为了实现某个功能，你需要太多的支撑代码，而实现功能的关键代码却迷失了。&lt;/p&gt;  &lt;p&gt;举个例子：我需要一个排好序的用户列表，排序的依据是用户名字。很简单的需求对不。自然的代码肯定是这样的：&lt;/p&gt;  &lt;p&gt;IList&amp;lt;User&amp;gt; users = &amp;#8230;&lt;/p&gt;  &lt;p&gt;users.OrderBy(user =&amp;gt; user.Name);&lt;/p&gt;  &lt;p&gt;而如果用Java实现同样的功能你可能要这样写：&lt;/p&gt;  &lt;p&gt;List&amp;lt;User&amp;gt; users = &amp;#8230;&lt;/p&gt;  &lt;p&gt;Collections.sort(users,new Comparator&amp;lt;User&amp;gt;() {    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; public int compare(User left, User right) {     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return left.getName().compareTo(right.getName());     &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }     &lt;br /&gt;});&lt;/p&gt;  &lt;p&gt;第一：没有扩展方法的支持，只有借助静态的辅助类&lt;/p&gt;  &lt;p&gt;第二：没有闭包的支持，非要写个难看的匿名类&lt;/p&gt;  &lt;p&gt;其实我们只需要一个OrderBy，一看就明白，但现在多了这么多&amp;#8220;无用&amp;#8221;的代码，反而核心的价值（order by）却显得不那么重要了。这还是一个很简单的例子，在实际的项目中你会为此付出更多的代价，你要写出一堆味同嚼蜡的代码才能实现你想要的那个功能，而那个功能其实是很显而易见。&lt;/p&gt;  &lt;p&gt;所以在语言层面，Java没有任何亮点，只觉得罗里罗嗦。&lt;/p&gt;  &lt;p&gt;关于语言层面的比较，&lt;a href="http://blog.zhaojie.me/2010/04/speech-why-java-sucks-and-csharp-rocks.html"&gt;老赵写过很多&lt;/a&gt;，而且非常精彩，建议去欣赏一下。&lt;/p&gt;  &lt;p&gt;不过Java也有那么很少几个有点意思的小东西：比如静态导入（脑袋提醒，这东西很早就在VB里出现了）、以及Java对Annotation的特殊支持让我们可以做一个更有意思的事情。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;概念满天飞&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;做Java以来，让我感触最深的是在Java世界里概念满天飞。ORM，IOC，AOP，这几个在.NET的世界里也有，但没见过这么浓的，但是如果你做Java应用，你不熟悉这几个你都不好意思出去跟人打招呼，所以除了学习Java本身外还有一大堆开源框架等着你研究。&lt;/p&gt;  &lt;p&gt;还有什么View Model，Presentation Model，Validator，BRO（Business Rule Object），BPO（Business Process Object）,BDD。关键是不仅是概念上存在这样的名词，它还大量的出现在代码里。代码里将概念描述得淋漓尽致，还规规矩矩。或许我土老帽了，我开发.NET三年有余，从来没整这些玩意儿。但是我一点也不怀疑我的代码难以阅读，难以维护。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;配置文件，你能再多一点么&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;我超级厌恶Spring的配置文件（虽然你说这只是个框架，但貌似Java社区有这个趋向）。虽然Spring现在也增加了注解（Annotation）的支持，但是还有那么一些知道的和不知道的原因，项目中存在大量的配置文件。而且为了&amp;#8220;模块性&amp;#8221;，一个小小的配置文件又包含有几个配置文件。有配置controller的，有配置DAO的，有配置service的。额，还有那该死的Hibernate的hbm文件。我想，系统的复杂性就是这么一点一点的堆积而来的。&lt;/p&gt;  &lt;p&gt;ASP.NET的配置文件一度也有变得更臃肿的趋势，但最后还是大大瘦身（.NET 4.0里默认的web.config很小了）。而且Attribute在.NET的第一个版本就出现了，很多可配置的东西都提供了Attribute的API和XML的API，所以没有历史遗留包袱。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;开源，这个我喜欢&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Java里的开源软件远远超过.NET的（这可能跟微软有一定的关系吧）。如果你想完成一项工作，总会有一个开源软件适合你。比如我们要做一个定时调度的任务，马上就有Quartz跑到了你的视野，你只需实现几个接口，然后在配置文件里配置一下（又是该死的配置文件），又比如你苦于在Java里没法像C#里那样用Lambda，马上有个跟你一样想法的人开发了一个lambda4j（Java人有个说法是：语言不足类库来补，不过Java这个语言太不足了，所以有的时候类库补也补不好）。你可以在琳琅满目的开源框架和开源类库里寻找一个最合适的，然后打开这个潘多拉魔盒。最主要的是她还是开放的，你不仅可以学习其代码思想，如果你发现有问题你甚至可以提交代码，那种成就感我倒是在开发.NET时没有感觉到。比如你要开发高性能服务器，在.NET里还没见过这类的开源项目，可Java里你可以学习Netty，可以学习Mina，你甚至可以根据自己具体的业务场景，对这些开源软件进行适当的修改。当然，你可以说思想是一样的，这倒是不错。但因为IO模型在Java里和.NET里并不一样，所以还是有很多不同的（当然我觉得.NET的异步IO更容易使用，Java的NIO那是什么狗屎一样的API啊）。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;IDE&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;搞开发的肯定离不开IDE。.NET里的IDE当之无愧的是Visual Studio了。不过我却觉得Visual Studio这几年已经离开发人员越来越远了，好像他要搞什么全生命周期的软件开发工具。所以不但臃肿，而且对开发人员并不是很友好（当然，她的可视化设计器是无与伦比的，但我不觉得可视化设计器是什么开发人员的&amp;#8220;利器&amp;#8221;）。举两个例子：VS里大量使用组合快捷键。这样不仅使得快捷键过长，难以记忆，而且还好难使用啊，你必须按两次，而且时间不能间隔太长。还有VS的重构功能，太弱了。&lt;/p&gt;  &lt;p&gt;在Java里有各种各样的IDE，有免费的，有收费的。我很喜欢的一个就是Intellij Idea。Idea给我的印象就是，她真的是在关注开发人员（写代码的）这个角色。所有的快捷键都很简单，好用好记。比如，大部分东西在Idea里可以使用Alt+Enter这个万能快捷键解决（这个快捷键是上下文感知的，在不同上下文中它知道要干什么）。&lt;/p&gt;  &lt;p&gt;再就是Idea对重构的支持，如果你熟练之后，做一项大的重构你都无需手动的去修改什么代码，直接依靠IDE的支持就可以完成，这在安全的重构里是很重要的一点，手动的去修改代码重构如果在测试不完备的情况下风险是非常高的。&lt;/p&gt;  &lt;p&gt;当然VS也有很多非常好用的插件，可以提高开发效率。比如大名鼎鼎的Resharper就来自于Intellij Idea同一个公司，由这个插件你可以看到Idea是如何关注写代码的人的效率。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;JVM vs CLR&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;一般的，Java跑在JVM上，C#跑在CLR上。从技术实现上他们两平分秋色，各有各的优点，我们不能评价他们的好坏。只能说可能JVM在XXX上胜过CLR，CLR在XXX上胜过JVM。而且JVM和CLR有居多相似之处，大多数东西都可以在对方找到相应的东西。&lt;/p&gt;  &lt;p&gt;那么她们就无法比较了么？不是，经过一年的学习我表示我更喜欢JVM一点。&lt;/p&gt;  &lt;p&gt;JVM（在这里只假设是Oracle/Sun Hotspot JVM）暴露了众多的配置参数给开发人员。你可以通过这些参数间接地控制JVM的运行。就比如GC吧，JVM里有各种参数来控制各个代的大小，还可以通过参数让JVM采用什么样的垃圾收集策略。因为不同类型的应用：比如桌面的、服务器端得、内存小的等等不同类型的应用适合不同的垃圾收集策略。而CLR在垃圾收集上只给开发人员提供了Workstation（是否是concurrent GC，.net 4.0是background GC）/Server等很少的控制（不过也几乎很少用到）。当然，如果你想最大化控制CLR你就只有自己Host CLR，然后调用Host API进行控制，但是那样难度高很多。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;我很愿意承认&lt;/strong&gt;CLR是自适应的，她能自动的智能的识别出你的需求，然后自动的进行调整。不过我在这里主要想到的是，微软在这里扮演着保姆的角色。在你很小的时候，保姆能够在一定程度上保护你，免你受到伤害。但是你不能永远生活在保姆的怀抱里，如果你想变得更强大你需要自己独自一人出去看看。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;注：这一节不是比较JVM和CLR，因为我没有那个能力。只是想从JVM和CLR所表现出来的差异来看看一些&amp;#8220;看不见的东西&amp;#8221;。&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;开发人员&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;上面主要谈了技术层面的东西。现在说说软件开发中的人。&lt;/p&gt;  &lt;p&gt;我现在所在的公司面试有个特点：会让面试者做一份家庭作业，然后让公司同事Code Review。在这一年里我看了很多Java的代码，也看了很多C#代码。但是我伤心的发现：&lt;/p&gt;  &lt;p&gt;1、虽然Java的也有烂代码，但是Java代码大多更注意代码的美感。大家都非常注意选择方法名，变量名，类名等。也非常愿意写一些小的，容易理解的方法，小的目的明确的类。可我亲爱的.NET同行们，大多在这方面很随意。一个方法200行不算长，甚至一个功能就放到一个方法里实现了。我看呀看呀，都看不到尽头。更别说类职责单一了。&lt;/p&gt;  &lt;p&gt;2、测试 Java同学的代码大多有测试，虽然有的测试不怎么好，但最起码有那么几个测测核心功能。但是.NET代码呢？很难见到几个有测试的（难道这是因为VS很晚才加入对Unit Test的支持有关？）。我不是说一定要有测试，我只是描述一下这么个现象。&lt;/p&gt;  &lt;p&gt;3、你也太随意了。我见到有那么几份.NET代码，我知道你创建了一个WinForm的项目，然后你却不把VS自动生成的那几个Form1.cs,Form1.resx给删掉。&lt;/p&gt;  &lt;p&gt;4、构建 从构建这个层面就更显出问题了，Java同学提交的代码大多有构建的脚本，无论是Ant还是Maven，所以你只需要敲一个命令行，马上可以看见人家的结果。而.NET同学的基本上都是sln文件。这一点不是说谁好谁坏的，因为我之前做.NET也从来没有自动构建脚本，我只想说两个社区有些不同。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;后记&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;我在这里并不是贬低某个社区的开发人员，也不想扯进任何平台的纷争。因为这只是我看到的现象，还有很多是我没看到的，而且这也严重的受到我周围同事的影响，所以难免以偏概全。&lt;/p&gt;  &lt;p&gt;如果有不足地方请不吝指教。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/yuyijq/aggbug/2091803.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yuyijq/archive/2011/06/28/2091803.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yuyijq/archive/2011/05/28/2060733.html</id><title type="text">自动垃圾回收学习笔记-垃圾回收算法</title><summary type="text">既然是自动垃圾回收，那么平台肯定得采取一种方式发现垃圾，然后清除。这就是垃圾收集算法所关注的问题。垃圾收集算法的任务就是将活动的对象和已经死掉的对象分别出来，然后将死掉的对象的内存回收，而且为了更好的利用内存，有的算法还会对内存碎片进行压缩。下面会对常用的垃圾收集算法进行介绍</summary><published>2011-05-28T07:06:00Z</published><updated>2011-05-28T07:06:00Z</updated><author><name>横刀天笑</name><uri>http://www.cnblogs.com/yuyijq/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yuyijq/archive/2011/05/28/2060733.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yuyijq/archive/2011/05/28/2060733.html"/><content type="html">&lt;p&gt;在上一篇文章中，我们了解了显式的内存管理的复杂性，而且还容易出错。因此我们需要一种自动内存管理的策略，这就是自动垃圾回收机制。&lt;/p&gt;  &lt;p&gt;既然是自动垃圾回收，那么平台肯定得采取一种方式发现垃圾，然后清除。这就是垃圾收集算法所关注的问题。垃圾收集算法的任务就是将活动的对象和已经死掉的对象分别出来，然后将死掉的对象的内存回收，而且为了更好的利用内存，有的算法还会对内存碎片进行压缩。下面会对常用的垃圾收集算法进行介绍：&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;引用计数（Reference Counting）&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;引用计数，顾名思义，就是每个对象上有个计数器，当添加了一个对它的引用时它的计数器就会加1，当不再使用这个引用时它的计数器就会递减1。当计数器为0的时候则认为该对象是垃圾，可以被回收了，如果该对象被回收则该对象所引用的所有对象的计数器会跟着递减。这样有可能会有很多对象的引用都减为0。&lt;/p&gt;  &lt;p&gt;使用引用计数做垃圾收集的算法的一个优点是实现很简单，与其他垃圾收集算法相比还有个特点是它的垃圾收集过程不会造成程序暂停（这个后面会提到）。因为计数的递增和递减是在程序运行过程中进行的，当一发现某个对象的计数为0马上可以回收掉。&lt;/p&gt;  &lt;p&gt;但是引用计数也有自己的困难：环形引用。比如现在A对象引用B对象，B对象的计数器加1，然后B引用C，C的计数加1，后来C又引用B，B的计数加1得到2。假如现在A不再引用B了，B的计数器成为1。而由于B、C互相引用，形成一个孤岛，但是计数器又没有变成0，又无法回收。这个问题在面向对象这类语言里更加严重，因为环形引用在面向对象里是很普遍的现象。除此之外，使用引用计数实现的垃圾收集方式还会将内存管理的代码和其他代码（比如一个引用更新的时候就要更新计数器）搅混在一起，这跟软件工程所提倡的模块化的原则相违背。&lt;/p&gt;  &lt;p&gt;实际上引用计数是如此简单，又很有效的资源管理方法，在很多场景中都会得到应用。比如你可以为了高效的利用资源，不每添加一次引用就拷贝一份资源，你可以只增加引用计数，而不拷贝。&lt;/p&gt;  &lt;p&gt;我之前曾做过一个实时监视的系统，安装在客户端上的进程不断的把监视客户端的信息（比如屏幕截图）发送到服务器端。服务器端是一个Web程序，多个管理员可以监视同一个客户端。当一个管理员监视开始监视某个客户端时我就给对应的数据上的一个计数器加1，当停止监视会管理员退出登录时，就减1，当递减为0时服务器端就不再接受客户端的监视数据，并发送停止监视的指令给客户端。这样不仅避免了为每个正在监视的管理员复制一份数据，还很好的控制了数据的生命周期。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;跟踪（trace）&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;使用跟踪对象的关系图，然后进行收集。使用跟踪方式的垃圾收集算法主要有以下几种：&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;标记清扫（Mark-Sweep）&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;这种算法与引用计数的方式大不相同，当内存分配时我们不进行人和额外的操作，当堆内的可用内存分配完了（或者超过某个阀值，具体操作根据各平台有所不同），然后就触发垃圾回收。标记清扫，顾名思义，就是这种垃圾收集机制会分为两个阶段：1.标记（Mark Phase） 2.清扫（Sweep Phase） 首先垃圾收集器会确定一些根（root）：比如线程当前正在执行的方法的局部变量，方法参数，以及所在类的实例变量及所有静态变量。然后垃圾收集器会从这些根对象出发查找他们所有的引用，然后在被引用的对象上作标记（mark），当垃圾收集器遇上一个已经被标记的对象后就不再往前走了（避免循环标记，造成死循环）。当标记阶段结束后，所有被标记的对象就称为可达对象（Reachable Object）或活对象（Live Object），而所有没有被标记的对象则被认为是垃圾，可以被回收。这个阶段进行完后就进入了清扫阶段（Sweep Phase）。在清扫阶段，垃圾收集器会从堆的底部开始扫描，然后将没有做标记的对象所占的内存全部回收，比如将这些垃圾添加到一个空闲空间链表。&lt;/p&gt;  &lt;p&gt;这个算法存在一个问题就是内存碎片。在开始的时候内存可能是按顺序分配的，然后经过几次垃圾回收后，这块连续的内存空间中有的对象变成了垃圾，被回收了，而有的还是存活的对象。这样这块内存中就会出现很多洞。内存碎片是非常有害的，有可能空闲内存还很多，但都是不够大的碎片，会造成下一次分配时因没有任何一个&amp;#8220;洞&amp;#8221;可以装得下这个对象，抛出out of memory的异常（OOM）。除此之外，这些碎片还会破坏程序的空间的局部性。这样另一种算法就出现了。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;标记压缩（Mark-Compact）&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;标记压缩算法的标记阶段和标记清扫算法的标记阶段是一致的，就不再重复。使用标记压缩算法时，标记完可达对象之后，我们不再遍历所有对象清扫垃圾了，我们只需要将所有存活对象向&amp;#8220;左&amp;#8221;靠齐，让不连续的空间变成连续的，这样就没有内存碎片了。不仅如此，因为不再连续的空间变成连续的，内存分配也更快速了。&lt;/p&gt;  &lt;p&gt;对于标记清扫算法来说，因为内存中有破洞，空闲内存不再连续，为了分配内存，系统内可能要维护着一个空闲内存空间的链表。当需要分配内存时，会遍历这个链表，找到一个够大的内存块，然后将其分成两份，一份用作当前的分配，另一份放回链表（这样有造成更多的内存碎片，也有一些策略并不是按顺序查找，找到够大的就好，有可能是找到一个更好的空闲内存块为止）。&lt;/p&gt;  &lt;p&gt;而对于标记压缩算法，内存空间是连续的，我们只需要一个指针标记出下一次分配工作要从哪里开始就可以了，分配后将指针递增所分配对象的大小，这个工作是非常快速的，而且不用维护那个空间内存链表了。&lt;/p&gt;  &lt;p&gt;这样一看好像标记压缩算法绝对的优于标记清扫算法，那标记清扫还有啥存在的必要了呢？不过要记住的一点是标记压缩算法为了达到压缩的目的，是需要移动对象的，这样所有对象的引用都必须更新。看来有利必有弊啊。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;标记拷贝（Mark-Copy，也有叫节点复制）&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;这种算法的一大特点就是将堆空间分为两部分：From，To。开始的时候我们只在From里分配，当From分配满的时候出发垃圾收集，这个时候会找出From空间里所有的存活对象，然后将这些存活的对象拷贝到To空间里。这样From空间里剩下的就都全是垃圾，而且对象拷贝到To里，在To里是紧凑排列的。这个事儿做完了之后From和To的角色就转变了一下。原来的From变成了To，原来的To变成了现在的From。现在又可以在这个完全是空的From里分配了。这个算法实现起来也很简单，高效（Sun JVM的新生代的垃圾回收就使用了这种算法）。不过这个算法有一个问题，堆的利用率只有一半了，这对那些内存占用率比较低的对象还算好，如果随着应用的内存占用率的增高，问题就出现了，第一个要拷贝的对象太多了，还有可能无法回收内存了。程序失败了。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;跟踪方式的其他问题&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;使用跟踪的方式来垃圾收集还有一些其他问题：&lt;/p&gt;  &lt;p&gt;1、需要暂停当前程序的运行。 因为在垃圾收集过程中，如果当前程序还在运行，则会继续分配和使用内存，会带来更复杂的问题，为了避免这些问题大部分垃圾收集器的实现都会或多或少的暂停所有线程（只会暂停执行托管代码的线程）。这对于实时性很高的应用有可能是不可接受的。&lt;/p&gt;  &lt;p&gt;2、有些垃圾收集器在某些阶段，垃圾收集的线程可以与应用程序的线程并发执行，但是垃圾收集线程也会占用系统资源，会降低应用程序的执行性能。&lt;/p&gt;  &lt;p&gt;3、所有的线程都在同一个堆上分配，就有可能造成数据不一致的情况，这就需要锁定来做到线程的同步，这样会降低内存分配的效率，可以将内存划分为很多区域，给每个线程一个区域，做到不需要同步的情况。&lt;/p&gt;  &lt;p&gt;分代&lt;/p&gt;  &lt;p&gt;上面介绍了跟踪方式的垃圾收集算法，在这些算法中都有一个共同点：标记出可达对象。可想而知，如果需要标记的空间非常大，需要标记的对象非常多，这个过程将非常缓慢的，为了让这个过程更加快速，现代大多垃圾收集器都将内存空间分代。比如CLR的0、1和2代。JVM的新生代、旧生代和持久代。这样垃圾回收时就会在一个相对来说更小的空间里遍历标记。&lt;/p&gt;  &lt;p&gt;不过这种分代的策略也依赖一些经验假设：&lt;/p&gt;  &lt;p&gt;1、新分配的对象的生命周期相对更短&lt;/p&gt;  &lt;p&gt;2、老对象的生命周期会更长&lt;/p&gt;  &lt;p&gt;3、很少有老对象引用新对象&lt;/p&gt;  &lt;p&gt;4、小对象的生命周期更短&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;总结&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;本篇主要关注垃圾收集的算法上，这些算法应用到实际的平台还会有各种各样的问题和应用策略。&lt;/p&gt;  &lt;p&gt;可能有人会讲，我们为什么需要了解自动垃圾回收机制是如何工作的呢？这个对应用程序员来说不是一个黑盒么？在某种程度上来说是一个黑盒，你确实不能控制垃圾收集器的行为。但是知道这些细节之后我们可能会对垃圾收集器进行一些调校，让他更适合我们的应用：客户端应用？服务器端应用？实时应用？等等（在这方面JVM提供了很多可配置的选项，可以根据实际场景进行调整，而CLR只提供了很少的配置功能）。&lt;/p&gt;  &lt;p&gt;而且，了解这些特性我们还能写出对垃圾收集器更友好的代码：比如对标记压缩算法了解后，我们可能会发现，对垃圾收集器来说，占时间的不是有多少垃圾，而是现在系统中存活的对象的多少。如果有很多小的存活对象，那么就需要更长时间来标记。&lt;/p&gt;  &lt;p&gt;后面我会针对CLR以及JVM各自对垃圾收集器的实现来讨论。&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;参考资料&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;《&lt;a href="http://book.douban.com/subject/1157908/"&gt;垃圾收集&lt;/a&gt;》这是一本好书，可惜已经绝版了&lt;/p&gt;  &lt;p&gt;《CLR via C#》这本书里对.NET平台上的垃圾收集器做了很详细的讲解&lt;/p&gt;  &lt;p&gt;《memory management whiter》这个对JVM的垃圾收集器做了比较全面的介绍，不过是J2SE 5.0的版本&lt;/p&gt;&lt;img src="http://www.cnblogs.com/yuyijq/aggbug/2060733.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yuyijq/archive/2011/05/28/2060733.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/yuyijq/archive/2011/05/21/2052702.html</id><title type="text">自动垃圾回收学习笔记-我们为什么需要自动垃圾回收</title><summary type="text">现代的虚拟运行时平台基本上都提供了垃圾回收的机制，其实垃圾回收是一个非常古老的技术，可以追溯到Lisp。 那么为什么要使用垃圾回收呢？这个要和显式的内存分配和释放相比较。我们或多或少的接触过或学习过这样的代码： 1: struct Node{ 2: ElementType Element; 3: Node* Next; 4: } 5: 6: Node* node = malloc(sizeof(Node)); 7: 8: //do something 9: 10: free(node);这里的malloc和free就是程序员显式的从堆上分配内存和释放不再使用的内存，将其归还给堆。这种让程序员控</summary><published>2011-05-21T04:57:00Z</published><updated>2011-05-21T04:57:00Z</updated><author><name>横刀天笑</name><uri>http://www.cnblogs.com/yuyijq/</uri></author><link rel="alternate" href="http://www.cnblogs.com/yuyijq/archive/2011/05/21/2052702.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/yuyijq/archive/2011/05/21/2052702.html"/><content type="html">&lt;p&gt;现代的虚拟运行时平台基本上都提供了垃圾回收的机制，其实垃圾回收是一个非常古老的技术，可以追溯到Lisp。&lt;/p&gt;  &lt;p&gt;那么为什么要使用垃圾回收呢？这个要和显式的内存分配和释放相比较。我们或多或少的接触过或学习过这样的代码：&lt;/p&gt;  &lt;div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"&gt;   &lt;div style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"&gt;     &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum1"&gt;   1:&lt;/span&gt; &lt;span style="color: #0000ff"&gt;struct&lt;/span&gt; Node{&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum2"&gt;   2:&lt;/span&gt;     ElementType Element;&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum3"&gt;   3:&lt;/span&gt;     Node* Next;&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum4"&gt;   4:&lt;/span&gt; }&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum5"&gt;   5:&lt;/span&gt;&amp;nbsp; &lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum6"&gt;   6:&lt;/span&gt; Node* node = malloc(&lt;span style="color: #0000ff"&gt;sizeof&lt;/span&gt;(Node));&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum7"&gt;   7:&lt;/span&gt;&amp;nbsp; &lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum8"&gt;   8:&lt;/span&gt; &lt;span style="color: #008000"&gt;//do something&lt;/span&gt;&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: white; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum9"&gt;   9:&lt;/span&gt;&amp;nbsp; &lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&#xD;
&#xD;
    &lt;pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px"&gt;&lt;span style="color: #606060" id="lnum10"&gt;  10:&lt;/span&gt; free(node);&lt;/pre&gt;&#xD;
&lt;!--CRLF--&gt;&lt;/div&gt;&#xD;
&lt;/div&gt;&#xD;
&#xD;
&lt;p&gt;这里的malloc和free就是程序员显式的从堆上分配内存和释放不再使用的内存，将其归还给堆。这种让程序员控制内存的分配和释放有很大的灵活性，如果操作得当也非常高效。对于我上面写的这段代码我们都能很正确的写出分配和释放的代码。但是当项目越来越大，这种靠程序员自己分配和释放的机制就会带来一些问题，最典型的有下面两个：&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;1 我malloc了，但是忘记调用free。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;这就是著名的内存泄露（memory leak）。表现的形式就是程序运行一段时间后所占的内存居高不下，只升不降。因为我们总是一味的去分配，而没有释放，甚至会出现out of memory错误。特别是对于那些长时间运行的应用（比如服务器端程序），内存泄露是致命的。可能有人要问，是你malloc的，你怎么就忘记free了呢？如果代码就寥寥数行，确实不容易出现这种问题，但是试想这样的情况：你在一个函数里使用malloc分配了一段内存，然后将指针作为返回参数返回给函数调用者，这个时候就很有可能忘记free了。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;2 我已经free了，但是疏忽了，我还是使用了&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;这就是所谓的悬挂指针（hang pointer）。表现形式就是程序运行不稳定。有可能还得出错误的结果。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;实际上最大的问题是，这两种错误都很难诊断。对于一般的bug，我们可以分析源代码，调试等手段很容易的得出。而这两种类型的bug，不仅需要借助一些专用的工具，还必须有耐心，就像守株待兔一样耐心的等待bug的出现。可想而知，那样的效率是如何低下。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;为了将程序开发人员从这样的噩梦中拯救出来，现代的运行时都提供了各种各样的自动垃圾回收机制。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;在具备自动垃圾回收机制的平台里，应用开发人员只管创建对象，垃圾回收器会帮你照料这些对象。垃圾回收器通过巧妙设计的算法发现那些不再被使用的对象（垃圾），然后将它们回收。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;那么，既然垃圾回收机制如此美妙，而自己管理内存又有这么多问题，那是不是所有的编程平台都应该提供这种机制呢。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;计算机里充斥着大量类似的权衡问题。很多机制都是一把双刃剑，我们需要知道它所适用的场景，而不应该发现它的好处就把它当做神灵。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;由于内存资源并不是无限的，在程序运行过程中肯定会触发多次的垃圾回收。垃圾回收与应用程序一起（有可能是同时并行的运行）运行，肯定也要占用一定的系统资源。而且垃圾收集器线程工作时，基于某些原因还要停止当前应用程序中所有线程的执行（运行托管代码的线程），让垃圾收集器的线程干完事儿后再继续执行，这对于一些对实时性很高的应用来说是不能忍受的。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;垃圾回收器所采用的策略也多种多样，比如对于.NET的有Workstation的，有Server的。对于Workstation又有Without concurrent和Concurrent的（在.NET 4.0中，已经被Backgourd所替代）。每种策略都有自己的特点，对某一方面有利，对另一方面则弱一些，所以要根据自己应用的特点选取合适的垃圾回收策略（这方面Java的可控性更高一些，可以调整很多参数进行优化）。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/yuyijq/aggbug/2052702.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/yuyijq/archive/2011/05/21/2052702.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry></feed>
