﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-eXile 的专栏</title><link>http://www.cppblog.com/eXile/</link><description /><language>zh-cn</language><lastBuildDate>Fri, 05 Sep 2008 06:56:23 GMT</lastBuildDate><pubDate>Fri, 05 Sep 2008 06:56:23 GMT</pubDate><ttl>60</ttl><item><title>Google C++ Testing Framework Primer(zt)</title><link>http://www.cppblog.com/eXile/archive/2008/07/21/56738.html</link><dc:creator>eXile</dc:creator><author>eXile</author><pubDate>Mon, 21 Jul 2008 03:35:00 GMT</pubDate><guid>http://www.cppblog.com/eXile/archive/2008/07/21/56738.html</guid><wfw:comment>http://www.cppblog.com/eXile/comments/56738.html</wfw:comment><comments>http://www.cppblog.com/eXile/archive/2008/07/21/56738.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/eXile/comments/commentRss/56738.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/eXile/services/trackbacks/56738.html</trackback:ping><description><![CDATA[<div class="postbody clearfix">
<p><strong><span class="hilite1"><span class="hilite1">Google</span></span> C++ <span class="hilite2"><span class="hilite2">Test</span></span>ing Framework Primer</strong></p>
<p><br></p>
<p><br></p>
<p>翻译：<a  href="http://rayleex.spaces.live.com/blog/cns%21C32DFA3924AF2128%21218.entry" target="_blank">Ray Li </a>(<a  href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#114;&#97;&#121;&#46;&#108;&#101;&#101;&#120;&#64;&#103;&#109;&#97;&#105;&#108;&#46;&#99;&#111;&#109;">ray.leex@gmail.com</a>)
<br>修改日期：2008年7月6日<br>原文参见：<a  href="http://code.google.com/p/googletest/wiki/GoogleTestPrimer">http://code.<span class="hilite1"><span class="hilite1">google</span></span>.com/p/<span class="hilite1"><span class="hilite1">google</span></span><span class="hilite2"><span class="hilite2">test</span></span>/wiki/<span class="hilite1"><span class="hilite1">Google</span></span><span class="hilite2"><span class="hilite2">Test</span></span>Primer</a></p>
<p>译文地址：http://www.javaeye.com/topic/212024
</p>
<p>&nbsp;</p>
<p><strong>Introduction</strong><strong>：为什么需要</strong><strong><span class="hilite1"><span class="hilite1">Google</span></span> C++ </strong><strong>测试框架？</strong>
</p>
<p>&nbsp;</p>
<p><span class="hilite1"><span class="hilite1">Google</span></span> C++ 测试框架帮助你更好地编写C++测试。
</p>
<p>&nbsp;</p>
<p>无论你是在Linux，Windows，还是Mac环境下工作，只要你编写C++代码，<span class="hilite1"><span class="hilite1">Google</span></span> 测试框架都可以帮上忙。
</p>
<p>&nbsp;</p>
<p>那么，哪些因素才能构成一个好的测试？以及，<span class="hilite1"><span class="hilite1">Google</span></span> C++ 测试框架怎样满足这些因素？我们相信：
</p>
<ol>
    <li>测试应该是<em>独立</em>、<em>可重复</em>的。因为其他测试成功或失败而导致我们要对自己的测试进行debug是非常痛苦的。<span class="hilite1"><span class="hilite1">Google</span></span> C++
    测试框架通过将每个测试在不同的对象中运行，使得测试分离开来。当一个测试失败时，<span class="hilite1"><span class="hilite1">Google</span></span> C++ 测试框架允许你独立运行它以进行快速除错。
    </li>
    <li>测试应该能够被很好地<em>组织</em>，并反映被测代码的结构。<span class="hilite1"><span class="hilite1">Google</span></span> C++
    测试框架将测试组织成测试案例，案例中的测试可以共享数据和程序分支。这样一种通用模式能够很容易辨识，使得我们的测试容易维护。当开发人员在项目之间转换，开始在一个新的代码基上开始工作时，这种一致性格外有用。
    </li>
    <li>测试应该是<em>可移植</em>、<em>可重用</em>的。开源社区有很多平台独立的代码，它们的测试也应该是平台独立的。除开一些特殊情况，<span class="hilite1"><span class="hilite1">Google</span></span>
    C++ 测试框架运行在不同的操作系统上、与不同的编译器（gcc、icc、MSVC）搭配，<span class="hilite1"><span class="hilite1">Google</span></span> C++ 测试框架的测试很容易与不同的配置一起工作。
    </li>
    <li>当测试失败时，应该提供尽可能多的、关于问题的<em>信息</em>。<span class="hilite1"><span class="hilite1">Google</span></span> C++
    测试框架在第一个测试失败时不会停下来。相反，它只是将当前测试停止，然后继续接下来的测试。你也可以设置对一些非致命的错误进行报告，并接着进行当前的测试。这样，你就可以在一次&#8220;运行-编辑-编译&#8221;循环中检查到并修复多个bug。
    </li>
    <li>测试框架应该能将测试编写人员从一些环境维护的工作中解放出来，使他们能够集中精力于测试的<em>内容</em>。<span class="hilite1"><span class="hilite1">Google</span></span> C++
    测试框架自动记录下所有定义好的测试，不需要用户通过列举来指明哪些测试需要运行。
    </li>
    <li>测试应该<em>快速</em>。使用<span class="hilite1"><span class="hilite1">Google</span></span> C++
    测试框架，你可以重用多个测试的共享资源，一次性完成设置/解除设置，而不用使一个测试去依赖另一测试。</li>
</ol>
<p>因为<span class="hilite1"><span class="hilite1">Google</span></span> C++
测试框架基于著名的xUnit架构，如果你之前使用过JUnit或PyUnit的话，你将会感觉非常熟悉。如果你没有接触过这些测试框架，它也只会占用你大约10分钟的时间来学习基本概念和上手。所以，让我们开始吧！
</p>
<p>&nbsp;</p>
<p>Note：本文偶尔会用&#8220;<span class="hilite1"><span class="hilite1">Google</span></span> <span class="hilite2"><span class="hilite2">Test</span></span>&#8221;来代指&#8220;<span class="hilite1"><span class="hilite1">Google</span></span> C++ 测试框架&#8221;。
</p>
<p>&nbsp;</p>
<p><strong>基本概念</strong></p>
<p>&nbsp;</p>
<p>使用<span class="hilite1"><span class="hilite1">Google</span></span>
<span class="hilite2"><span class="hilite2">Test</span></span>时，你是从编写<em>断言</em>开始的，而断言是一些检查条件是否为真的语句。一个断言的结果可能是成功、非致命失败，或者致命失败。如果一个致命失败出现，他会结束当前的函数；否则，程序继续正常运行。
</p>
<p>&nbsp;</p>
<p><em>测试</em>使用断言来验证被测代码的行为。如果一个测试崩溃或是出现一个失败的断言，那么，该测试<em>失败</em>；否则该测试<em>成功</em>。
</p>
<p>&nbsp;</p>
<p>一个测试案例（<span class="hilite2"><span class="hilite2">test</span></span>
case）包含了一个或多个测试。你应该将自己的测试分别归类到测试案例中，以反映被测代码的结构。当测试案例中的多个测试需要共享通用对象和子程序时，你可以把他们放到一个测试固件（<em><span class="hilite2"><span class="hilite2">test</span></span>
fixture</em>）类中。
</p>
<p>&nbsp;</p>
<p>一个<em>测试程序</em>可以包含多个测试案例。
</p>
<p>&nbsp;</p>
<p>从编写单个的断言开始，到创建测试和测试案例，我们将会介绍怎样编写一个测试程序。
</p>
<p>&nbsp;</p>
<p><strong>断言</strong>
</p>
<p>&nbsp;</p>
<p><span class="hilite1"><span class="hilite1">Google</span></span> <span class="hilite2"><span class="hilite2">Test</span></span>中的断言是一些与函数调用相似的宏。要测试一个类或函数，我们需要对其行为做出断言。当一个断言失败时，<span class="hilite1"><span class="hilite1">Google</span></span>
<span class="hilite2"><span class="hilite2">Test</span></span>会在屏幕上输出该代码所在的源文件及其所在的位置行号，以及错误信息。也可以在编写断言时，提供一个自定义的错误信息，这个信息在失败时会被附加在<span class="hilite1"><span class="hilite1">Google</span></span>
<span class="hilite2"><span class="hilite2">Test</span></span>的错误信息之后。
</p>
<p>&nbsp;</p>
<p>断言常常成对出现，它们都测试同一个类或者函数，但对当前功能有着不同的效果。ASSERT_*版本的断言失败时会产生致命失败，并<strong>结束当前函数</strong>。EXPECT_*版本的断言产生非致命失败，而不会中止当前函数。通常更推荐使用EXPECT_*断言，因为它们运行一个测试中可以有不止一个的错误被报告出来。但如果在编写断言如果失败，就没有必要继续往下执行的测试时，你应该使用ASSERT_*断言。
</p>
<p>&nbsp;</p>
<p>因为失败的ASSERT_*断言会立刻从当前的函数返回，可能会跳过其后的一些的清洁代码，这样也许会导致空间泄漏。根据泄漏本身的特质，这种情况
也许值得修复，也可能不值得我们关心——所以，如果你得到断言错误的同时，还得到了一个堆检查的错误，记住上面我们所说的这一点。 </p>
<p>&nbsp;</p>
<p>要提供一个自定义的错误消息，只需要使用&lt;&lt;操作符，或一个&lt;&lt;操作符的序列，将其输入到框架定义的宏中。下面是一个例子：
</p>
<p>&nbsp;</p>
&nbsp;
<div class="dp-highlighter">
<ol class="dp-cpp" start="1">
    <li><span><span>ASSERT_EQ(x.size(),&nbsp;y.size())&nbsp;&lt;&lt;&nbsp;</span><span class="string">"Vectors&nbsp;x&nbsp;and&nbsp;y&nbsp;are&nbsp;of&nbsp;unequal&nbsp;length"</span><span>;&nbsp;&nbsp;</span></span></li>
    <li><span><span class="keyword">for</span><span>&nbsp;(</span><span class="datatypes">int</span><span>&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;x.size();&nbsp;++i)&nbsp;{&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;EXPECT_EQ(x[i],&nbsp;y[i])&nbsp;&lt;&lt;&nbsp;<span class="string">"Vectors&nbsp;x&nbsp;and&nbsp;y&nbsp;differ&nbsp;at&nbsp;index&nbsp;"</span><span>&nbsp;&lt;&lt;&nbsp;i;&nbsp;&nbsp;</span></span></li>
    <li><span>}&nbsp;&nbsp;</span></li>
</ol>
</div>
<pre style="display: none;" name="code" class="cpp">ASSERT_EQ(x.size(), y.size()) &lt;&lt; "Vectors x and y are of unequal length";
for (int i = 0; i &lt; x.size(); ++i) {
EXPECT_EQ(x[i], y[i]) &lt;&lt; "Vectors x and y differ at index " &lt;&lt; i;
}</pre>
&nbsp;
<p>任何能够被输出到ostream中的信息都可以被输出到一个断言宏中——特别是C字符串和string对象。如果一个宽字符串
（wchar_t*，windows上UNICODE模式TCHAR*或std::wstring）被输出到一个断言中，在打印时它会被转换成UTF-8
编码。 </p>
<p>&nbsp;</p>
<p><strong>基本断言</strong>
</p>
<p>&nbsp;</p>
<p>下面这些断言实现了基本的true/false条件测试。
</p>
<p>&nbsp;</p>
<table border="1" cellpadding="0" cellspacing="0" width="500">
    <tbody>
        <tr>
            <td valign="top" width="167"><strong>致命断言</strong></td>
            <td valign="top" width="166"><strong>非致命断言</strong></td>
            <td valign="top" width="165"><strong>验证条件</strong></td>
        </tr>
        <tr>
            <td valign="top" width="167">ASSERT_TRUE(<em>condition</em>);</td>
            <td valign="top" width="166">EXPECT_TRUE(<em>condition</em>); </td>
            <td valign="top" width="165"><em>condition</em>为真</td>
        </tr>
        <tr>
            <td valign="top" width="167">ASSERT_FALSE(<em>condition</em>); </td>
            <td valign="top" width="166">EXPECT_FALSE(<em>condition</em>); </td>
            <td valign="top" width="165"><em>condition</em> 为假</td>
        </tr>
    </tbody>
</table>
<p>&nbsp;</p>
<p>记住，当它们失败时，ASSERT_*产生一个致命失败并从当前函数返回，而EXCEPT_*产生一个非致命失败，允许函数继续运行。在两种情况下，一个断言失败都意味着它所包含的测试失败。
</p>
<p>&nbsp;</p>
<p>有效平台：Linux、Windows、Mac。
</p>
<p>&nbsp;</p>
<p><strong>二进制比较</strong>
</p>
<p>&nbsp;</p>
<p>本节描述了比较两个值的一些断言。
</p>
<p>&nbsp;</p>
<table border="1" cellpadding="2" cellspacing="0" width="500">
    <tbody>
        <tr>
            <td valign="top" width="166"><strong>致命断言</strong></td>
            <td valign="top" width="166"><strong>非致命断言</strong></td>
            <td valign="top" width="166"><strong>验证条件</strong></td>
        </tr>
        <tr>
            <td valign="top" width="166">ASSERT_EQ(<em>expected</em>, <em>actual</em>);</td>
            <td valign="top" width="166">EXPECT_EQ(<em>expected</em>, <em>actual</em>);</td>
            <td valign="top" width="166"><em>expected</em> == <em>actual</em></td>
        </tr>
        <tr>
            <td valign="top" width="166">ASSERT_NE(<em>val1</em>, <em>val2</em>);</td>
            <td valign="top" width="166">EXPECT_NE(<em>val1</em>, <em>val2</em>);</td>
            <td valign="top" width="166"><em>val1</em> != <em>val2</em></td>
        </tr>
        <tr>
            <td valign="top" width="166">ASSERT_LT(<em>val1</em>, <em>val2</em>);</td>
            <td valign="top" width="166">EXPECT_LT(<em>val1</em>, <em>val2</em>);</td>
            <td valign="top" width="166"><em>val1</em> &lt; <em>val2</em></td>
        </tr>
        <tr>
            <td valign="top" width="166">ASSERT_LE(<em>val1</em>, <em>val2</em>);</td>
            <td valign="top" width="166">EXPECT_LE(<em>val1</em>, <em>val2</em>);</td>
            <td valign="top" width="166"><em>val1</em> &lt;= <em>val2</em></td>
        </tr>
        <tr>
            <td valign="top" width="166">ASSERT_GT(<em>val1</em>, <em>val2</em>);</td>
            <td valign="top" width="166">EXPECT_GT(<em>val1</em>, <em>val2</em>);</td>
            <td valign="top" width="166"><em>val1</em> &gt; <em>val2</em></td>
        </tr>
        <tr>
            <td valign="top" width="166">ASSERT_GE(<em>val1</em>, <em>val2</em>);</td>
            <td valign="top" width="166">EXPECT_GE(<em>val1</em>, <em>val2</em>);</td>
            <td valign="top" width="166"><em>val1</em> &gt;= <em>val2</em></td>
        </tr>
    </tbody>
</table>
<p>&nbsp;</p>
<p>在出现失败事件时，<span class="hilite1"><span class="hilite1">Google</span></span>
<span class="hilite2"><span class="hilite2">Test</span></span>会将两个值（<em>Val1</em>和<em>Val2</em>）都打印出来。在ASSERT_EQ*和EXCEPT_EQ*断言（以及我们随后介绍类似的断言）中，你应该把你希望测试的表达式放在<em>actual</em>（实际值）的位置上，将其期望值放在<em>expected</em>（期望值）的位置上，因为<span class="hilite1"><span class="hilite1">Google</span></span>
<span class="hilite2"><span class="hilite2">Test</span></span>的测试消息为这种惯例做了一些优化。
</p>
<p>&nbsp;</p>
<p>参数值必须是可通过断言的比较操作符进行比较的，否则你会得到一个编译错误。参数值还必须支持&lt;&lt;操作符来将值输入到ostream中。所有的C++内置类型都支持这一点。
</p>
<p>&nbsp;</p>
<p>这些断言可以用于用户自定义的型别，但你必须重载相应的比较操作符（如==、&lt;等）。如果定义有相应的操作符，推荐使用ASSERT_*()宏，因为它们不仅会输出比较的结果，还会输出两个比较对象。
</p>
<p>&nbsp;</p>
<p>参数表达式总是只被解析一次。因此，参数表达式有一定的副作用（side
effect，这里应该是指编译器不同，操作符解析顺序的不确定性）也是可以接受的。但是，同其他普通C/C++函数一样，参数表达式的解析顺序是不确定的（如，一种编译器可以自由选择一种顺序来进行解析），而你的代码不应该依赖于某种特定的参数解析顺序。
</p>
<p>&nbsp;</p>
<p>ASSERT_EQ()对指针进行的是指针比较。即，如果被用在两个C字符串上，它会比较它们是否指向同样的内存地址，而不是它们所指向的字符串是否有相同值。所以，如果你想对两个C字符串（例如，const
char*）进行值比较，请使用ASSERT_STREQ()宏，该宏会在后面介绍到。特别需要一提的是，要验证一个C字符串是否为空（NULL），使用ASSERT_STREQ(NULL,
c_string)。但是要比较两个string对象时，你应该使用ASSERT_EQ。
</p>
<p>&nbsp;</p>
<p>本节中介绍的宏都可以处理窄字符串对象和宽字符串对象（string和wstring）。
</p>
<p>&nbsp;</p>
<p>有效平台：Linux、Windows、Mac。
</p>
<p>&nbsp;</p>
<p><strong>字符串比较</strong></p>
<p>&nbsp;</p>
<p>该组断言用于比较两个C字符串。如果你想要比较两个string对象，相应地使用EXPECT_EQ、EXPECT_NE等断言。
</p>
<p>&nbsp;</p>
<table border="1" cellpadding="2" cellspacing="0" width="700">
    <tbody>
        <tr>
            <td valign="top" width="235"><strong>致命断言</strong></td>
            <td valign="top" width="234"><strong>非致命断言</strong></td>
            <td valign="top" width="229"><strong>验证条件</strong></td>
        </tr>
        <tr>
            <td valign="top" width="235">ASSERT_STREQ(<em>expected_str</em>,
            <em>actual_str</em>);</td>
            <td valign="top" width="234">EXPECT_STREQ(<em>expected_str</em>,
            <em>actual_str</em>);</td>
            <td valign="top" width="229">两个C字符串有相同的内容</td>
        </tr>
        <tr>
            <td valign="top" width="235">ASSERT_STRNE(<em>str1</em>, <em>str2</em>);</td>
            <td valign="top" width="234">EXPECT_STRNE(<em>str1</em>, <em>str2</em>);</td>
            <td valign="top" width="229">两个C字符串有不同的内容</td>
        </tr>
        <tr>
            <td valign="top" width="235">ASSERT_STRCASEEQ(<em>expected_str</em>,
            <em>actual_str</em>);</td>
            <td valign="top" width="234">EXPECT_STRCASEEQ(<em>expected_str</em>,
            <em>actual_str</em>);</td>
            <td valign="top" width="229">两个C字符串有相同的内容，忽略大小写</td>
        </tr>
        <tr>
            <td valign="top" width="235">ASSERT_STRCASENE(<em>str1</em>, <em>str2</em>);</td>
            <td valign="top" width="234">EXPECT_STRCASENE(<em>str1</em>, <em>str2</em>);</td>
            <td valign="top" width="229">两个C字符串有不同的内容，忽略大小写</td>
        </tr>
    </tbody>
</table>
<p>&nbsp;</p>
<p>注意断言名称中出现的&#8220;CASE&#8221;意味着大小写被忽略了。
</p>
<p>&nbsp;</p>
<p>*STREQ*和*STRNE*也接受宽字符串（wchar_t*）。如果两个宽字符串比较失败，它们的值会做为UTF-8窄字符串被输出。
</p>
<p>&nbsp;</p>
<p>一个NULL空指针和一个空字符串会被认为是<em>不一样</em>的。
</p>
<p>&nbsp;</p>
<p>有效平台：Linux、Windows、Mac。
</p>
<p>&nbsp;</p>
<p>参见：更多的字符串比较的技巧（如子字符串、前缀和正则表达式匹配），请参见[Advanced Guide Advanced <span class="hilite1"><span class="hilite1">Google</span></span> <span class="hilite2"><span class="hilite2">Test</span></span>
Guide]。
</p>
<p>&nbsp;</p>
<p><strong>简单的测试</strong>
</p>
<p>&nbsp;</p>
<p>要创建一个测试：
</p>
<ol>
    <li>使用<span class="hilite2"><span class="hilite2">TEST</span></span>（）宏来定义和命名一个测试函数，它们是一些没有返回值的普通C++函数。
    </li>
    <li>在这个函数中，与你想要包含的其它任何有效C++代码一起，使用<span class="hilite1"><span class="hilite1">Google</span></span> <span class="hilite2"><span class="hilite2">Test</span></span>提供的各种断言来进行检查。
    </li>
    <li>测试的结果由其中的断言决定；如果测试中的任意断言失败（无论是致命还是非致命），或者测试崩溃，那么整个测试就失败了。否则，测试通过。&nbsp;
    </li>
</ol>
<div class="dp-highlighter">
<ol class="dp-cpp" start="1">
    <li><span><span><span class="hilite2"><span class="hilite2">TEST</span></span>(<span class="hilite2"><span class="hilite2">test</span></span>_case_name,&nbsp;<span class="hilite2"><span class="hilite2">test</span></span>_name)&nbsp;{&nbsp;&nbsp;</span></span></li>
    <li><span>...&nbsp;<span class="hilite2"><span class="hilite2">test</span></span>&nbsp;body&nbsp;...&nbsp;&nbsp;</span></li>
    <li><span>}&nbsp;&nbsp;</span></li>
</ol>
</div>
<pre style="display: none;" name="code" class="cpp"><span class="hilite2"><span class="hilite2">TEST</span></span>(<span class="hilite2"><span class="hilite2">test</span></span>_case_name, <span class="hilite2"><span class="hilite2">test</span></span>_name) {
... <span class="hilite2"><span class="hilite2">test</span></span> body ...
}</pre>
&nbsp;
<p><span class="hilite2"><span class="hilite2">TEST</span></span>（）的参数是从概括到特殊的。<em>第一个</em>参数是测试案例的名称，<em>第二个</em>参数是测试案例中的测试的名称。记住，一个测试案例可以包含任意数量的独立测试。一个测试的<em>全称</em>包括了包含它的测试案例名称，及其独立的名称。不同测试案例中的独立测试可以有相同的名称。
</p>
<p>&nbsp;</p>
<p>举例来说，让我们看一个简单的整数函数：
</p>
<p>&nbsp;</p>
<div class="dp-highlighter">
<ol class="dp-cpp" start="1">
    <li><span><span class="datatypes">int</span><span>&nbsp;Factorial(</span><span class="datatypes">int</span><span>&nbsp;n);&nbsp;</span><span class="comment">//&nbsp;返回n的阶乘</span><span>&nbsp;&nbsp;</span></span></li>
</ol>
</div>
<pre style="display: none;" name="code" class="cpp">int Factorial(int n); // 返回n的阶乘</pre>
&nbsp;
<p>&nbsp;</p>
<p><span style="font-family: courier new,courier;">这个函数的测试案例应该看起来像是：
</span></p>
<p>&nbsp;</p>
<div class="dp-highlighter">
<ol class="dp-cpp" start="1">
    <li><span><span class="comment">//&nbsp;测试0的阶乘</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span><span class="hilite2"><span class="hilite2">TEST</span></span>(Factorial<span class="hilite2"><span class="hilite2">Test</span></span>,&nbsp;HandlesZeroInput)&nbsp;{&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;EXPECT_EQ(1,&nbsp;Factorial(0));&nbsp;&nbsp;</span></li>
    <li><span>}&nbsp;&nbsp;</span></li>
    <li><span><span class="comment">//&nbsp;测试正数的阶乘</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span><span class="hilite2"><span class="hilite2">TEST</span></span>(Factorial<span class="hilite2"><span class="hilite2">Test</span></span>,&nbsp;HandlesPositiveInput)&nbsp;{&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;EXPECT_EQ(1,&nbsp;Factorial(1));&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;EXPECT_EQ(2,&nbsp;Factorial(2));&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;EXPECT_EQ(6,&nbsp;Factorial(3));&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;EXPECT_EQ(40320,&nbsp;Factorial(8));&nbsp;&nbsp;</span></li>
    <li><span>}&nbsp;&nbsp;</span></li>
</ol>
</div>
<pre style="display: none;" name="code" class="cpp">// 测试0的阶乘
<span class="hilite2"><span class="hilite2">TEST</span></span>(Factorial<span class="hilite2"><span class="hilite2">Test</span></span>, HandlesZeroInput) {
EXPECT_EQ(1, Factorial(0));
}
// 测试正数的阶乘
<span class="hilite2"><span class="hilite2">TEST</span></span>(Factorial<span class="hilite2"><span class="hilite2">Test</span></span>, HandlesPositiveInput) {
EXPECT_EQ(1, Factorial(1));
EXPECT_EQ(2, Factorial(2));
EXPECT_EQ(6, Factorial(3));
EXPECT_EQ(40320, Factorial(8));
}</pre>
&nbsp;
<p>&nbsp;</p>
<p><span class="hilite1"><span class="hilite1">Google</span></span>
<span class="hilite2"><span class="hilite2">Test</span></span>根据测试案例来分组收集测试结果，因此，逻辑相关的测试应该在同一测试案例中；换句话说，它们的<span class="hilite2"><span class="hilite2">TEST</span></span>（）的第一个参数应该是一样的。在上面的例子中，我们有两个测试，HandlesZeroInput和HandlesPostiveInput，它们都属于同一个测试案例Factorial<span class="hilite2"><span class="hilite2">Test</span></span>。
</p>
<p>&nbsp;</p>
<p>有效平台：Linux、Windows、Mac。
</p>
<p>&nbsp;</p>
<p><strong>测试固件（</strong><strong><span class="hilite2"><span class="hilite2">Test</span></span> Fixtures</strong><strong>，又做测试夹具、测试套件）：在多个测试中使用同样的数据配置</strong>
</p>
<p>&nbsp;</p>
<p>当你发现自己编写了两个或多个测试来操作同样的数据，你可以采用一个<em>测试固件</em>。它让你可以在多个不同的测试中重用同样的对象配置。
</p>
<p>&nbsp;</p>
<p>要创建测试固件，只需：
</p>
<ol>
    <li>创建一个类继承自<span class="hilite2"><span class="hilite2">test</span></span>ing::<span class="hilite2"><span class="hilite2">Test</span></span>。将其中的成员声明为protected:或是public:，因为我们想要从子类中存取固件成员。
    </li>
    <li>在该类中声明你计划使用的任何对象。
    </li>
    <li>如果需要，编写一个默认构造函数或者SetUp()函数来为每个测试准备对象。常见错误包括将SetUp()拼写为Setup()（小写了u）——不要让它发生在你身上。
    </li>
    <li>如果需要，编写一个析构函数或者TearDown()函数来释放你在SetUp()函数中申请的资源。要知道什么时候应该使用构造函数/析构函数，什么时候又应该使用SetUp()/TearDown()函数，阅读我们的FAQ。
    </li>
    <li>如果需要，定义你的测试所需要共享的子程序。</li>
</ol>
<p>当我们要使用固件时，使用<span class="hilite2"><span class="hilite2">TEST</span></span>_F()替换掉<span class="hilite2"><span class="hilite2">TEST</span></span>()，它允许我们存取测试固件中的对象和子程序：
</p>
<p>&nbsp;</p>
<div class="dp-highlighter">
<ol class="dp-cpp" start="1">
    <li><span><span><span class="hilite2"><span class="hilite2">TEST</span></span>_F(<span class="hilite2"><span class="hilite2">test</span></span>_case_name,&nbsp;<span class="hilite2"><span class="hilite2">test</span></span>_name)&nbsp;{&nbsp;&nbsp;</span></span></li>
    <li><span>...&nbsp;<span class="hilite2"><span class="hilite2">test</span></span>&nbsp;body&nbsp;...&nbsp;&nbsp;</span></li>
    <li><span>}&nbsp;&nbsp;</span></li>
</ol>
</div>
<pre style="display: none;" name="code" class="cpp"><span class="hilite2"><span class="hilite2">TEST</span></span>_F(<span class="hilite2"><span class="hilite2">test</span></span>_case_name, <span class="hilite2"><span class="hilite2">test</span></span>_name) {
... <span class="hilite2"><span class="hilite2">test</span></span> body ...
}</pre>
<p>&nbsp;</p>
<p>与<span class="hilite2"><span class="hilite2">TEST</span></span>()一样，第一个参数是测试案例的名称，但对<span class="hilite2"><span class="hilite2">TEST</span></span>_F()来说，这个名称必须与测试固件类的名称一些。你可能已经猜到了：_F正是指固件。
</p>
<p>&nbsp;</p>
<p>不幸地是，C++宏系统并不允许我们创建一个单独的宏来处理两种类型的测试。使用错误的宏会导致编译期的错误。
</p>
<p>&nbsp;</p>
<p>而且，你必须在<span class="hilite2"><span class="hilite2">TEST</span></span>_F()中使用它之前，定义好这个测试固件类。否则，你会得到编译器的报错：&#8220;virtual outside class
declaration&#8221;。
</p>
<p>对于<span class="hilite2"><span class="hilite2">TEST</span></span>_F()中定义的每个测试，<span class="hilite1"><span class="hilite1">Google</span></span> <span class="hilite2"><span class="hilite2">Test</span></span>将会：
</p>
<ol>
    <li>在运行时创建一个<em>全新</em>的测试固件
    </li>
    <li>马上通过SetUp()初始化它，
    </li>
    <li>运行测试
    </li>
    <li>调用TearDown()来进行清理工作
    </li>
    <li>删除测试固件。注意，同一测试案例中，不同的测试拥有不同的测试固件。<span class="hilite1"><span class="hilite1">Google</span></span> <span class="hilite2"><span class="hilite2">Test</span></span>在创建下一个测试固件前总是会对现有固件进行删除。<span class="hilite1"><span class="hilite1">Google</span></span>
    <span class="hilite2"><span class="hilite2">Test</span></span>不会对多个测试重用一个测试固件。测试对测试固件的改动并不会影响到其他测试。</li>
</ol>
<p>例如，让我们为一个名为Queue的FIFO队列类编写测试，该类的接口如下：
</p>
&nbsp;
<div class="dp-highlighter">
<ol class="dp-cpp" start="1">
    <li><span><span class="keyword">template</span><span>&nbsp;&lt;</span><span class="keyword">typename</span><span>&nbsp;E&gt;&nbsp;</span><span class="comment">//&nbsp;E为元素类型</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span><span class="keyword">class</span><span>&nbsp;Queue&nbsp;{&nbsp;&nbsp;</span></span></li>
    <li><span><span class="keyword">public</span><span>:&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;Queue();&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;<span class="keyword">void</span><span>&nbsp;Enqueue(</span><span class="keyword">const</span><span>&nbsp;E&amp;&nbsp;element);&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;E*&nbsp;Dequeue();&nbsp;<span class="comment">//&nbsp;返回&nbsp;NULL&nbsp;如果队列为空.</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;<span class="datatypes">size_t</span><span>&nbsp;size()&nbsp;</span><span class="keyword">const</span><span>;&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;...&nbsp;&nbsp;</span></li>
    <li><span>};&nbsp;&nbsp;</span></li>
</ol>
</div>
<pre style="display: none;" name="code" class="cpp">template &lt;typename E&gt; // E为元素类型
class Queue {
public:
Queue();
void Enqueue(const E&amp; element);
E* Dequeue(); // 返回 NULL 如果队列为空.
size_t size() const;
...
};</pre>
<p>&nbsp;</p>
<p>首先，定义一个固件类。习惯上，你应该把它的名字定义为Foo<span class="hilite2"><span class="hilite2">Test</span></span>，这里的Foo是被测试的类。
</p>
&nbsp;
<div class="dp-highlighter">
<ol class="dp-cpp" start="1">
    <li><span><span class="keyword">class</span><span>&nbsp;Queue<span class="hilite2"><span class="hilite2">Test</span></span>&nbsp;:&nbsp;</span><span class="keyword">public</span><span>&nbsp;<span class="hilite2"><span class="hilite2">test</span></span>ing::<span class="hilite2"><span class="hilite2">Test</span></span>&nbsp;{&nbsp;&nbsp;</span></span></li>
    <li><span><span class="keyword">protected</span><span>:&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;<span class="keyword">virtual</span><span>&nbsp;</span><span class="keyword">void</span><span>&nbsp;SetUp()&nbsp;{&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;q1_.Enqueue(1);&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;q2_.Enqueue(2);&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;q2_.Enqueue(3);&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;<span class="comment">//&nbsp;virtual&nbsp;void&nbsp;TearDown()&nbsp;{}</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;Queue&lt;<span class="datatypes">int</span><span>&gt;&nbsp;q0_;&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;Queue&lt;<span class="datatypes">int</span><span>&gt;&nbsp;q1_;&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;Queue&lt;<span class="datatypes">int</span><span>&gt;&nbsp;q2_;&nbsp;&nbsp;</span></span></li>
    <li><span>};&nbsp;&nbsp;</span></li>
</ol>
</div>
<pre style="display: none;" name="code" class="cpp">class Queue<span class="hilite2"><span class="hilite2">Test</span></span> : public <span class="hilite2"><span class="hilite2">test</span></span>ing::<span class="hilite2"><span class="hilite2">Test</span></span> {
protected:
virtual void SetUp() {
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}
// virtual void TearDown() {}
Queue&lt;int&gt; q0_;
Queue&lt;int&gt; q1_;
Queue&lt;int&gt; q2_;
};</pre>
&nbsp;
<p>在这个案例中，我们不需要TearDown()，因为每个测试后除了析构函数外不需要进行其它的清理工作了。
</p>
<p>&nbsp;</p>
<p>接下来我们使用<span class="hilite2"><span class="hilite2">TEST</span></span>_F()和这个固件来编写测试。
</p>
<p>&nbsp;</p>
<div class="dp-highlighter">
<ol class="dp-cpp" start="1">
    <li><span><span><span class="hilite2"><span class="hilite2">TEST</span></span>_F(Queue<span class="hilite2"><span class="hilite2">Test</span></span>,&nbsp;IsEmptyInitially)&nbsp;{&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;EXPECT_EQ(0,&nbsp;q0_.size());&nbsp;&nbsp;</span></li>
    <li><span>}&nbsp;&nbsp;</span></li>
    <li><span><span class="hilite2"><span class="hilite2">TEST</span></span>_F(Queue<span class="hilite2"><span class="hilite2">Test</span></span>,&nbsp;DequeueWorks)&nbsp;{&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;<span class="datatypes">int</span><span>*&nbsp;n&nbsp;=&nbsp;q0_.Dequeue();&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;EXPECT_EQ(NULL,&nbsp;n);&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;n&nbsp;=&nbsp;q1_.Dequeue();&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;ASSERT_TRUE(n&nbsp;!=&nbsp;NULL);&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;EXPECT_EQ(1,&nbsp;*n);&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;EXPECT_EQ(0,&nbsp;q1_.size());&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;<span class="keyword">delete</span><span>&nbsp;n;&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;n&nbsp;=&nbsp;q2_.Dequeue();&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;ASSERT_TRUE(n&nbsp;!=&nbsp;NULL);&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;EXPECT_EQ(2,&nbsp;*n);&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;EXPECT_EQ(1,&nbsp;q2_.size());&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;<span class="keyword">delete</span><span>&nbsp;n;&nbsp;&nbsp;</span></span></li>
    <li><span>}&nbsp;&nbsp;</span></li>
</ol>
</div>
<pre style="display: none;" name="code" class="cpp"><span class="hilite2"><span class="hilite2">TEST</span></span>_F(Queue<span class="hilite2"><span class="hilite2">Test</span></span>, IsEmptyInitially) {
EXPECT_EQ(0, q0_.size());
}
<span class="hilite2"><span class="hilite2">TEST</span></span>_F(Queue<span class="hilite2"><span class="hilite2">Test</span></span>, DequeueWorks) {
int* n = q0_.Dequeue();
EXPECT_EQ(NULL, n);
n = q1_.Dequeue();
ASSERT_TRUE(n != NULL);
EXPECT_EQ(1, *n);
EXPECT_EQ(0, q1_.size());
delete n;
n = q2_.Dequeue();
ASSERT_TRUE(n != NULL);
EXPECT_EQ(2, *n);
EXPECT_EQ(1, q2_.size());
delete n;
}</pre>
&nbsp;
<p>上面这段代码既使用了ASSERT_*断言，又使用了EXPECT_*断言。经验上讲，如果你想要断言失败后，测试能够继续进行以显示更多的错误
时，你应该使用EXPECT_*断言；使用ASSERT_*如果该断言失败后继续往下执行毫无意义。例如，Dequeue测试中的第二个断言是
ASSERT_TURE(n!= NULL)，因为我们随后会n指针解引用，如果n指针为空的话，会导致一个段错误。 </p>
<p>&nbsp;</p>
<p>当这些测试开始时，会发生如下情况：
</p>
<ol>
    <li><span class="hilite1"><span class="hilite1">Google</span></span> <span class="hilite2"><span class="hilite2">Test</span></span>创建一个Queue<span class="hilite2"><span class="hilite2">Test</span></span>对象（我们把它叫做t1）。
    </li>
    <li>t1.SetUp()初始化t1。
    </li>
    <li>第一个测试（IsEmptyInitiallly）在t1上运行。
    </li>
    <li>测试完成后，t1.TearDown()进行一些清理工作。
    </li>
    <li>t1被析构。
    </li>
    <li>以上步骤在另一个Queue<span class="hilite2"><span class="hilite2">Test</span></span>对象上重复进行，这回会运行DequeueWorks测试。</li>
</ol>
<p>有效平台：Linux、Windows、Mac。
</p>
<p>&nbsp;</p>
<p>注意：当一个测试对象被构造时，<span class="hilite1"><span class="hilite1">Google</span></span> <span class="hilite2"><span class="hilite2">Test</span></span>会自动地保存所有的<span class="hilite1"><span class="hilite1">Google</span></span> <span class="hilite2"><span class="hilite2">Test</span></span>变量标识，对象析构后进行恢复。
</p>
<p>&nbsp;</p>
<p><strong>调用测试</strong>
</p>
<p>&nbsp;</p>
<p><span class="hilite2"><span class="hilite2">TEST</span></span>()和<span class="hilite2"><span class="hilite2">TEST</span></span>_F()向<span class="hilite1"><span class="hilite1">Google</span></span>
<span class="hilite2"><span class="hilite2">Test</span></span>隐式注册它们的测试。因此，与很多其他的C++测试框架不同，你不需要为了运行你定义的测试而将它们全部再列出来一次。
</p>
<p>&nbsp;</p>
<p>在定义好测试后，你可以通过RUN_ALL_<span class="hilite2"><span class="hilite2">TEST</span></span>S()来运行它们，如果所有测试成功，该函数返回0，否则会返回1.注意RUN_ALL_<span class="hilite2"><span class="hilite2">TEST</span></span>S()会运行你链接到的所有测试——它们可以来自不同的测试案例，甚至是来自不同的文件。
</p>
<p>&nbsp;</p>
<p>当被调用时，RUN_ALL_<span class="hilite2"><span class="hilite2">TEST</span></span>S()宏会：
</p>
<ol>
    <li>保存所有的<span class="hilite1"><span class="hilite1">Google</span></span> <span class="hilite2"><span class="hilite2">Test</span></span>标志。
    </li>
    <li>为一个侧测试创建测试固件对象。
    </li>
    <li>调用SetUp()初始化它。
    </li>
    <li>在固件对象上运行测试。
    </li>
    <li>调用TearDown()清理固件。
    </li>
    <li>删除固件。
    </li>
    <li>恢复所有<span class="hilite1"><span class="hilite1">Google</span></span> <span class="hilite2"><span class="hilite2">Test</span></span>标志的状态。
    </li>
    <li>重复上诉步骤，直到所有测试完成。</li>
</ol>
<p>此外，如果第二步时，测试固件的构造函数产生一个致命错误，继续执行3至5部显然没有必要，所以它们会被跳过。与之相似，如果第3部产生致命错误，第4部也会被跳过。
</p>
<p>&nbsp;</p>
<p>重要：你不能忽略掉RUN_ALL_<span class="hilite2"><span class="hilite2">TEST</span></span>S()的返回值，否则gcc会报一个编译错误。这样设计的理由是自动化测试服务会根据测试退出返回码来决定一个测试是否通过，而不是根据其stdout/stderr输出；因此你的main()函数必须返回RUN_ALL_<span class="hilite2"><span class="hilite2">TEST</span></span>S()的值。
</p>
<p>&nbsp;</p>
<p>而且，你应该只调用RUN_ALL_<span class="hilite2"><span class="hilite2">TEST</span></span>S()一次。多次调用该函数会与<span class="hilite1"><span class="hilite1">Google</span></span> <span class="hilite2"><span class="hilite2">Test</span></span>的一些高阶特性（如线程安全死亡测试thread-safe
death <span class="hilite2"><span class="hilite2">test</span></span>s）冲突，因而是不被支持的。
</p>
<p>&nbsp;</p>
<p>有效平台：Linux、Windows、Mac。
</p>
<p>&nbsp;</p>
<p><strong>编写</strong><strong>main()</strong><strong>函数</strong>
</p>
<p>&nbsp;</p>
<p>你可以从下面这个样板开始:
</p>
<p>&nbsp;</p>
<div class="dp-highlighter">
<ol class="dp-cpp" start="1">
    <li><span><span class="preprocessor">#include&nbsp;"this/package/foo.h"</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span><span class="preprocessor">#include&nbsp;&lt;g<span class="hilite2"><span class="hilite2">test</span></span>/g<span class="hilite2"><span class="hilite2">test</span></span>.h&gt;</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span><span class="keyword">namespace</span><span>&nbsp;{&nbsp;&nbsp;</span></span></li>
    <li><span><span class="comment">//&nbsp;测试Foo类的测试固件</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span><span class="keyword">class</span><span>&nbsp;Foo<span class="hilite2"><span class="hilite2">Test</span></span>&nbsp;:&nbsp;</span><span class="keyword">public</span><span>&nbsp;<span class="hilite2"><span class="hilite2">test</span></span>ing::<span class="hilite2"><span class="hilite2">Test</span></span>&nbsp;{&nbsp;&nbsp;</span></span></li>
    <li><span><span class="keyword">protected</span><span>:&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;<span class="comment">//&nbsp;You&nbsp;can&nbsp;remove&nbsp;any&nbsp;or&nbsp;all&nbsp;of&nbsp;the&nbsp;following&nbsp;functions&nbsp;if&nbsp;its&nbsp;body</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;<span class="comment">//&nbsp;is&nbsp;empty.</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;Foo<span class="hilite2"><span class="hilite2">Test</span></span>()&nbsp;{&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//&nbsp;You&nbsp;can&nbsp;do&nbsp;set-up&nbsp;work&nbsp;for&nbsp;each&nbsp;<span class="hilite2"><span class="hilite2">test</span></span>&nbsp;here.</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;<span class="keyword">virtual</span><span>&nbsp;~Foo<span class="hilite2"><span class="hilite2">Test</span></span>()&nbsp;{&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//&nbsp;You&nbsp;can&nbsp;do&nbsp;clean-up&nbsp;work&nbsp;that&nbsp;doesn't&nbsp;throw&nbsp;exceptions&nbsp;here.</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;<span class="comment">//&nbsp;If&nbsp;the&nbsp;constructor&nbsp;and&nbsp;destructor&nbsp;are&nbsp;not&nbsp;enough&nbsp;for&nbsp;setting&nbsp;up</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;<span class="comment">//&nbsp;and&nbsp;cleaning&nbsp;up&nbsp;each&nbsp;<span class="hilite2"><span class="hilite2">test</span></span>,&nbsp;you&nbsp;can&nbsp;define&nbsp;the&nbsp;following&nbsp;methods:</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;<span class="keyword">virtual</span><span>&nbsp;</span><span class="keyword">void</span><span>&nbsp;SetUp()&nbsp;{&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//&nbsp;Code&nbsp;here&nbsp;will&nbsp;be&nbsp;called&nbsp;immediately&nbsp;after&nbsp;the&nbsp;constructor&nbsp;(right</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//&nbsp;before&nbsp;each&nbsp;<span class="hilite2"><span class="hilite2">test</span></span>).</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;<span class="keyword">virtual</span><span>&nbsp;</span><span class="keyword">void</span><span>&nbsp;TearDown()&nbsp;{&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//&nbsp;Code&nbsp;here&nbsp;will&nbsp;be&nbsp;called&nbsp;immediately&nbsp;after&nbsp;each&nbsp;<span class="hilite2"><span class="hilite2">test</span></span>&nbsp;(right</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">//&nbsp;before&nbsp;the&nbsp;destructor).</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;}&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;<span class="comment">//&nbsp;Objects&nbsp;declared&nbsp;here&nbsp;can&nbsp;be&nbsp;used&nbsp;by&nbsp;all&nbsp;<span class="hilite2"><span class="hilite2">test</span></span>s&nbsp;in&nbsp;the&nbsp;<span class="hilite2"><span class="hilite2">test</span></span>&nbsp;case&nbsp;for&nbsp;Foo.</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>};&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;</span></li>
    <li><span><span class="comment">//&nbsp;<span class="hilite2"><span class="hilite2">Test</span></span>s&nbsp;that&nbsp;the&nbsp;Foo::Bar()&nbsp;method&nbsp;does&nbsp;Abc.</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span><span class="hilite2"><span class="hilite2">TEST</span></span>_F(Foo<span class="hilite2"><span class="hilite2">Test</span></span>,&nbsp;MethodBarDoesAbc)&nbsp;{&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;<span class="keyword">const</span><span>&nbsp;string&nbsp;input_filepath&nbsp;=&nbsp;</span><span class="string">"this/package/<span class="hilite2"><span class="hilite2">test</span></span>data/myinputfile.dat"</span><span>;&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;<span class="keyword">const</span><span>&nbsp;string&nbsp;output_filepath&nbsp;=&nbsp;</span><span class="string">"this/package/<span class="hilite2"><span class="hilite2">test</span></span>data/myoutputfile.dat"</span><span>;&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;Foo&nbsp;f;&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;EXPECT_EQ(0,&nbsp;f.Bar(input_filepath,&nbsp;output_filepath));&nbsp;&nbsp;</span></li>
    <li><span>}&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;</span></li>
    <li><span><span class="comment">//&nbsp;<span class="hilite2"><span class="hilite2">Test</span></span>s&nbsp;that&nbsp;Foo&nbsp;does&nbsp;Xyz.</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span><span class="hilite2"><span class="hilite2">TEST</span></span>_F(Foo<span class="hilite2"><span class="hilite2">Test</span></span>,&nbsp;DoesXyz)&nbsp;{&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;<span class="comment">//&nbsp;Exercises&nbsp;the&nbsp;Xyz&nbsp;feature&nbsp;of&nbsp;Foo.</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>}&nbsp;&nbsp;</span></li>
    <li><span>}&nbsp;&nbsp;<span class="comment">//&nbsp;namespace</span><span>&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;</span></li>
    <li><span><span class="datatypes">int</span><span>&nbsp;main(</span><span class="datatypes">int</span><span>&nbsp;argc,&nbsp;</span><span class="datatypes">char</span><span>&nbsp;**argv)&nbsp;{&nbsp;&nbsp;</span></span></li>
    <li><span>&nbsp;&nbsp;<span class="hilite2"><span class="hilite2">test</span></span>ing::Init<span class="hilite1"><span class="hilite1">Google</span></span><span class="hilite2"><span class="hilite2">Test</span></span>(&amp;argc,&nbsp;argv);&nbsp;&nbsp;</span></li>
    <li><span>&nbsp;&nbsp;<span class="keyword">return</span><span>&nbsp;RUN_ALL_<span class="hilite2"><span class="hilite2">TEST</span></span>S();&nbsp;&nbsp;</span></span></li>
    <li><span>}&nbsp;&nbsp;</span></li>
</ol>
</div>
<pre style="display: none;" name="code" class="cpp">#include "this/package/foo.h"
#include &lt;g<span class="hilite2"><span class="hilite2">test</span></span>/g<span class="hilite2"><span class="hilite2">test</span></span>.h&gt;
namespace {
// 测试Foo类的测试固件
class Foo<span class="hilite2"><span class="hilite2">Test</span></span> : public <span class="hilite2"><span class="hilite2">test</span></span>ing::<span class="hilite2"><span class="hilite2">Test</span></span> {
protected:
// You can remove any or all of the following functions if its body
// is empty.
Foo<span class="hilite2"><span class="hilite2">Test</span></span>() {
// You can do set-up work for each <span class="hilite2"><span class="hilite2">test</span></span> here.
}
virtual ~Foo<span class="hilite2"><span class="hilite2">Test</span></span>() {
// You can do clean-up work that doesn't throw exceptions here.
}
// If the constructor and destructor are not enough for setting up
// and cleaning up each <span class="hilite2"><span class="hilite2">test</span></span>, you can define the following methods:
virtual void SetUp() {
// Code here will be called immediately after the constructor (right
// before each <span class="hilite2"><span class="hilite2">test</span></span>).
}
virtual void TearDown() {
// Code here will be called immediately after each <span class="hilite2"><span class="hilite2">test</span></span> (right
// before the destructor).
}
// Objects declared here can be used by all <span class="hilite2"><span class="hilite2">test</span></span>s in the <span class="hilite2"><span class="hilite2">test</span></span> case for Foo.
};
// <span class="hilite2"><span class="hilite2">Test</span></span>s that the Foo::Bar() method does Abc.
<span class="hilite2"><span class="hilite2">TEST</span></span>_F(Foo<span class="hilite2"><span class="hilite2">Test</span></span>, MethodBarDoesAbc) {
const string input_filepath = "this/package/<span class="hilite2"><span class="hilite2">test</span></span>data/myinputfile.dat";
const string output_filepath = "this/package/<span class="hilite2"><span class="hilite2">test</span></span>data/myoutputfile.dat";
Foo f;
EXPECT_EQ(0, f.Bar(input_filepath, output_filepath));
}
// <span class="hilite2"><span class="hilite2">Test</span></span>s that Foo does Xyz.
<span class="hilite2"><span class="hilite2">TEST</span></span>_F(Foo<span class="hilite2"><span class="hilite2">Test</span></span>, DoesXyz) {
// Exercises the Xyz feature of Foo.
}
}  // namespace
int main(int argc, char **argv) {
<span class="hilite2"><span class="hilite2">test</span></span>ing::Init<span class="hilite1"><span class="hilite1">Google</span></span><span class="hilite2"><span class="hilite2">Test</span></span>(&amp;argc, argv);
return RUN_ALL_<span class="hilite2"><span class="hilite2">TEST</span></span>S();
}</pre>
<p>&nbsp;</p>
<p><span class="hilite2"><span class="hilite2">test</span></span>ing::Init<span class="hilite1"><span class="hilite1">Google</span></span><span class="hilite2"><span class="hilite2">Test</span></span>()函数负责解析命令行传入的<span class="hilite1"><span class="hilite1">Google</span></span>
<span class="hilite2"><span class="hilite2">Test</span></span>标志，并删除所有它可以处理的标志。这使得用户可以通过各种不同的标志控制一个测试程序的行为。关于这一点我们会在G<span class="hilite2"><span class="hilite2">Test</span></span>Advanced中讲到。你必须在调用RUN_ALL_<span class="hilite2"><span class="hilite2">TEST</span></span>S()之前调用该函数，否则就无法正确地初始化标示。
</p>
<p>&nbsp;</p>
<p>在Windows上Init<span class="hilite1"><span class="hilite1">Google</span></span><span class="hilite2"><span class="hilite2">Test</span></span>()可以支持宽字符串，所以它也可以被用在以UNICODE模式编译的程序中。
</p>
<p>&nbsp;</p>
<p><strong>进阶阅读</strong>
</p>
<p>&nbsp;</p>
<p>恭喜你！你已经学到了一些<span class="hilite1"><span class="hilite1">Google</span></span> <span class="hilite2"><span class="hilite2">Test</span></span>基础。你可以从编写和运行几个<span class="hilite1"><span class="hilite1">Google</span></span> <span class="hilite2"><span class="hilite2">Test</span></span>测试开始，再阅读一下<a  href="http://code.google.com/p/googletest/wiki/GoogleTestSamples"><span class="hilite1"><span class="hilite1">Google</span></span><span class="hilite2"><span class="hilite2">Test</span></span>Samples</a>，或是继续研究<a  href="http://code.google.com/p/googletest/wiki/GoogleTestAdvancedGuide"><span class="hilite1"><span class="hilite1">Google</span></span><span class="hilite2"><span class="hilite2">Test</span></span>AdvancedGuide</a>，其中描述了很多更有用的<span class="hilite1"><span class="hilite1">Google</span></span>
<span class="hilite2"><span class="hilite2">Test</span></span>特性。
</p>
<p>&nbsp;</p>
<p><strong>已知局限</strong>
</p>
<p>&nbsp;</p>
<p><span class="hilite1"><span class="hilite1">Google</span></span> <span class="hilite2"><span class="hilite2">Test</span></span>被设计为线程安全的。但是，我们还没有时间在各种平台上实现同步原语（synchronization
primitives）。因此，目前从两个线程同时使用<span class="hilite1"><span class="hilite1">Google</span></span>
<span class="hilite2"><span class="hilite2">Test</span></span>断言是不安全的。由于通常断言是在主线程中完成的，因此在大多数测试中这都不算问题。如果你愿意帮忙，你可以试着在g<span class="hilite2"><span class="hilite2">test</span></span>-port.h中实现必要的同步原语。</p>
</div><img src ="http://www.cppblog.com/eXile/aggbug/56738.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eXile/" target="_blank">eXile</a> 2008-07-21 11:35 <a href="http://www.cppblog.com/eXile/archive/2008/07/21/56738.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>纯真IP数据库格式详解(zt)</title><link>http://www.cppblog.com/eXile/archive/2008/07/20/56679.html</link><dc:creator>eXile</dc:creator><author>eXile</author><pubDate>Sun, 20 Jul 2008 05:46:00 GMT</pubDate><guid>http://www.cppblog.com/eXile/archive/2008/07/20/56679.html</guid><wfw:comment>http://www.cppblog.com/eXile/comments/56679.html</wfw:comment><comments>http://www.cppblog.com/eXile/archive/2008/07/20/56679.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/eXile/comments/commentRss/56679.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/eXile/services/trackbacks/56679.html</trackback:ping><description><![CDATA[转自<a  href="http://lumaqq.linuxsir.org/article/qqwry_format_detail.html">http://lumaqq.linuxsir.org/article/qqwry_format_detail.html</a> <br><br>
<center>
<h1>纯真IP数据库格式详解</h1>
</center>
<center></center>
<blockquote><strong>摘要</strong> <br>网络上的IP数据库以纯真版的最为流行，LumaQQ也采用了纯真版IP数据库做为IP查询功能的
基础。不过关于其格式的文档却非常之少，后来终于在网上找到了一份文档，得以了解其内幕，不过那份文档寥寥数语，也是颇为耐心才读明白。在这里我重写一
份，以此做为LumaQQ开发者文档的一部分，我想还是必要的。本文详细介绍了纯真IP数据库的格式，并且给出了一些Demo以供参考。
<p><strong>Luma, 清华大学 <br></strong>修改日期： 2005/01/14 </p>
<p>Note: 在此感谢纯真IP数据库作者金狐和那唯一一份文档的作者。</p>
<strong>修改历史:</strong> <br>2005-01-14 修改了原来一些表达不清和错误的地方 </blockquote>
<hr width="100%">
<p>自从有了IP数据库这种东西，QQ外挂的显示IP功能也随之而生，本人见识颇窄，是否还有其他应用不得而知，不过，IP数据库确实是个不错的东西。
如今网络上最流行的IP数据库我想应该是纯真版的（说错了也不要扁我），迄今为止其IP记录条数已经接近30000，对于有些IP甚至能精确到楼层，不亦
快哉。2004年4、5月间，正逢LumaQQ破土动工，为了加上这个人人都喜欢，但是好像人人都不知道为什么喜欢的显IP功能，我也采用了纯真版IP数
据库，它的优点是记录多，查询速度快，它只用一个文件QQWry.dat就包含了所有记录，方便嵌入到其他程序中，也方便升级。</p>
<h3>基本结构</h3>
<p>QQWry.dat文件在结构上分为3块：文件头，记录区，索引区。一般我们要查找IP时，先在索引区查找记录偏移，然后再到记录区读出信息。由于
记录区的记录是不定长的，所以直接在记录区中搜索是不可能的。由于记录数比较多，如果我们遍历索引区也会是有点慢的，一般来说，我们可以用二分查找法搜索
索引区，其速度比遍历索引区快若干数量级。图1是QQWry.dat的文件结构图。</p>
<center><img  src="http://lumaqq.linuxsir.org/article/images/1/qqwry_dat_overview.gif" twffan="done" border="0"> <br><br>图1. QQWry.dat文件结构</center>
<p>要注意的是，QQWry.dat里面全部采用了little-endian字节序</p>
<h3>一. 了解文件头</h3>
<p>QQWry.dat的文件头只有8个字节，其结构非常简单，首四个字节是第一条索引的绝对偏移，后四个字节是最后一条索引的绝对偏移。</p>
<h3>二. 了解记录区</h3>
<p>每条IP记录都由国家和地区名组成，国家地区在这里并不是太确切，因为可能会查出来&#8220;清华大学计算机系&#8221;之类的，这里清华大学就成了国家名了，所以
这个国家地区名和IP数据库制作的时候有关系。所以记录的格式有点像QName，有一个全局部分和局部部分组成，我们这里还是沿用国家名和地区名的说法。</p>
<p>于是我们想象着一条记录的格式应该是:
[IP地址][国家名][地区名]，当然，这个没有什么问题，但是这只是最简单的情况。很显然，国家名和地区名可能会有很多的重复，如果每条记录都保存一
个完整的名称拷贝是非常不理想的，所以我们就需要重定向以节省空间。所以为了得到一个国家名或者地区名，我们就有了两个可能：第一就是直接的字符串表示的
国家名，第二就是一个4字节的结构，第一个字节表明了重定向的模式，后面3个字节是国家名或者地区名的实际偏移位置。对于国家名来说，情况还可能更复杂
些，因为这样的重定向最多可能有两次。</p>
<p>那么什么是重定向模式？根据上面所说，一条记录的格式是[IP地址][国家记录][地区记录]，如果国家记录是重定向的话，那么地区记录是有可能没有的，于是就有了两种情况，我管他叫做模式1和模式2。我们对这些格式的情况举图说明：</p>
<center><img  src="http://lumaqq.linuxsir.org/article/images/1/ip_record_1.gif" twffan="done" border="0"> <br><br>图2. IP记录的最简单形式</center>
<p>图2表示了最简单的IP记录格式，我想没有什么可以解释的</p>
<center><img  src="http://lumaqq.linuxsir.org/article/images/1/ip_record_2.gif" twffan="done" border="0"> <br><br>图3. 重定向模式1</center>
<p>图3演示了重定向模式1的情况。我们看到在模式1的情况下，地区记录也跟着国家记录走了，在IP地址之后只剩下了国家记录的4字节，后面3个字节构成了一个指针，指向了实际的国家名，然后又跟着地址名。模式1的标识字节是0x01。</p>
<center><img  src="http://lumaqq.linuxsir.org/article/images/1/ip_record_3.gif" twffan="done" border="0"> <br><br>图4. 重定向模式2</center>
<p>图4演示了重定向模式2的情况。我们看到了在模式2的情况下（其标识字节是0x02），地区记录没有跟着国家记录走，因此在国家记录之后4个字节之
后还是有地区记录。我想你已经明白了模式1和模式2的区别，即：模式1的国家记录后面不会再有地区记录，模式2的国家记录后会有地区记录。下面我们来看一
下更复杂的情况。</p>
<center><img  src="http://lumaqq.linuxsir.org/article/images/1/ip_record_5.gif" twffan="done" border="0"> <br><br>图5. 混和情况1</center>
<p>图5演示了当国家记录为模式1的时候可能出现的更复杂情况，在这种情况下，重定向指向的位置仍然是个重定向，不过第二次重定向为模式2。大家不用担
心，没有模式3了，这个重定向也最多只有两次，并且如果发生了第二次重定向，则其一定为模式2，而且这种情况只会发生在国家记录上，对于地区记录，模式1
和模式2是一样的，地区记录也不会发生2次重定向。不过，这个图还可以更复杂，如图7：</p>
<center><img  src="http://lumaqq.linuxsir.org/article/images/1/ip_record_6.gif" twffan="done" border="0"> <br><br>图6. 混和情况2</center>
<p>图6是模式1下最复杂的混和情况，不过我想应该也很好理解，只不过地区记录也来重定向而已，有一点我要提醒你，如果重定向的地址是0，则表示未知的地区名。</p>
<p>所以我们总结如下：一条IP记录由[IP地址][国家记录][地区记录]组成，对于国家记录，可以有三种表示方式：字符串形式，重定向模式1和重定
向模式2。对于地区记录，可以有两种表示方式：字符串形式和重定向，另外有一条规则：重定向模式1的国家记录后不能跟地区记录。按照这个总结，在这些方式
中合理组合，就构成了IP记录的所有可能情况。</p>
<h3>设计的理由</h3>
<p>在我们继续去了解索引区的结构之前，我们先来了解一下为何记录区的结构要如此设计。我想你可能想到了答案：字符串重用。没错，在这种结构下，对于一
个国家名和地区名，我只需要保存其一次就可以了。我们举例说明，为了表示方便，我们用小写字母代表IP记录，C表示国家名，A表示地区名： </p>
<ol>
    <li>有两条记录a(C1, A1), b(C2, A2)，如果C1 = C2, A1 = A2，那么我们就可以使用图3显示的结构来实现重用 </li>
    <li>有三条记录a(C1, A1), b(C2, A2), c(C3, A3)，如果C1 = C2, A2 = A3，现在我们想存储记录b，那么我们可以用图6的结构来实现重用 </li>
    <li>有两条记录a(C1, A1), b(C2, A2)，如果C1 = C2，现在我们想存储记录b，那么我们可以采用模式2表示C2，用字符串表示A2 </li>
</ol>
<p>你可以举出更多的情况，你也会发现在这种结构下，不同的字符串只需要存储一次。</p>
<h3>了解索引区</h3>
<p>在"了解文件头"部分，我们说明了文件头实际上是两个指针，分别指向了第一条索引和最后一条索引的绝对偏移。如图8所示：</p>
<center><img  src="http://lumaqq.linuxsir.org/article/images/1/header_to_index.gif" twffan="done" border="0"> <br><br>图8. 文件头指向索引区图示</center>
<p>实在是很简单，不是吗？从文件头你就可以定位到索引区，然后你就可以开始搜索IP了！每条索引长度为7个字节，前4个字节是起始IP地址，后三个字
节就指向了IP记录。这里有些概念需要说明一下，什么是起始IP，那么有没有结束IP？ 假设有这么一条记录：166.111.0.0 -
166.111.255.255，那么166.111.0.0就是起始IP，166.111.255.255就是结束IP，结束IP就是IP记录中的那头
4个字节，这下你应该就清楚了吧。于是乎，每条索引配合一条记录，构成了一个IP范围，如果你要查找166.111.138.138所在的位置，你就会发
现166.111.138.138落在了166.111.0.0 - 166.111.255.255
这个范围内，那么你就可以顺着这条索引去读取国家和地区名了。那么我们给出一个最详细的图解吧：</p>
<center><img  src="http://lumaqq.linuxsir.org/article/images/1/overall_format.gif" twffan="done" border="0"> <br><br>图9. 文件详细结构</center>
<p>现在一切都清楚了是不是？也许还有一点你不清楚，QQWry.dat的版本信息存在哪里呢？
答案是：最后一条IP记录实际上就是版本信息，最后一条记录显示出来就是这样：255.255.255.0 255.255.255.255 纯真网络
2004年6月25日IP数据。OK，到现在你应该全部清楚了。</p>
<h3>Demo</h3>
<p>下一步：我给出一个读取IP记录的程序片断，此片断摘录自LumaQQ源文件edu.tsinghua.lumaqq.IPSeeker.java，如果你有兴趣，可以下载源代码详细看看。</p>
<pre> /**<br>  * 给定一个ip国家地区记录的偏移，返回一个IPLocation结构<br>  * @param offset 国家记录的起始偏移<br>  * @return IPLocation对象<br>  */<br> private IPLocation getIPLocation(long offset) {<br>  try {<br>   // 跳过4字节ip<br>   ipFile.seek(offset + 4);<br>   // 读取第一个字节判断是否标志字节<br>   byte b = ipFile.readByte();<br>   if(b == REDIRECT_MODE_1) {<br>    // 读取国家偏移<br>    long countryOffset = readLong3();<br>    // 跳转至偏移处<br>    ipFile.seek(countryOffset);<br>    // 再检查一次标志字节，因为这个时候这个地方仍然可能是个重定向<br>    b = ipFile.readByte();<br>    if(b == REDIRECT_MODE_2) {<br>     loc.country = readString(readLong3());<br>     ipFile.seek(countryOffset + 4);<br>    } else<br>     loc.country = readString(countryOffset);<br>    // 读取地区标志<br>    loc.area = readArea(ipFile.getFilePointer());<br>   } else if(b == REDIRECT_MODE_2) {<br>    loc.country = readString(readLong3());<br>    loc.area = readArea(offset + 8);<br>   } else {<br>    loc.country = readString(ipFile.getFilePointer() - 1);<br>    loc.area = readArea(ipFile.getFilePointer());<br>   }<br>   return loc;<br>  } catch (IOException e) {<br>   return null;<br>  }<br> } <br><br> /**<br>  * 从offset偏移开始解析后面的字节，读出一个地区名<br>  * @param offset 地区记录的起始偏移<br>  * @return 地区名字符串<br>  * @throws IOException 地区名字符串<br>  */<br> private String readArea(long offset) throws IOException {<br>  ipFile.seek(offset);<br>  byte b = ipFile.readByte();<br>  if(b == REDIRECT_MODE_1 || b == REDIRECT_MODE_2) {<br>   long areaOffset = readLong3(offset + 1);<br>   if(areaOffset == 0)<br>    return LumaQQ.getString("unknown.area");<br>   else<br>    return readString(areaOffset);<br>  } else<br>   return readString(offset);<br> }<br><br> /**<br>  * 从offset位置读取3个字节为一个long，因为java为big-endian格式，所以没办法<br>  * 用了这么一个函数来做转换<br>  * @param offset 整数的起始偏移<br>  * @return 读取的long值，返回-1表示读取文件失败<br>  */<br> private long readLong3(long offset) {<br>  long ret = 0;<br>  try {<br>   ipFile.seek(offset);<br>   ipFile.readFully(b3);<br>   ret |= (b3[0] &amp; 0xFF);<br>   ret |= ((b3[1] &lt;&lt; 8) &amp; 0xFF00);<br>   ret |= ((b3[2] &lt;&lt; 16) &amp; 0xFF0000);<br>   return ret;<br>  } catch (IOException e) {<br>   return -1;<br>  }<br> } <br> <br> /**<br>  * 从当前位置读取3个字节转换成long<br>  * @return 读取的long值，返回-1表示读取文件失败<br>  */<br> private long readLong3() {<br>  long ret = 0;<br>  try {<br>   ipFile.readFully(b3);<br>   ret |= (b3[0] &amp; 0xFF);<br>   ret |= ((b3[1] &lt;&lt; 8) &amp; 0xFF00);<br>   ret |= ((b3[2] &lt;&lt; 16) &amp; 0xFF0000);<br>   return ret;<br>  } catch (IOException e) {<br>   return -1;<br>  }<br> }<br><br> /**<br>  * 从offset偏移处读取一个以0结束的字符串<br>  * @param offset 字符串起始偏移<br>  * @return 读取的字符串，出错返回空字符串<br>  */<br> private String readString(long offset) {<br>  try {<br>   ipFile.seek(offset);<br>   int i;<br>   for(i = 0, buf[i] = ipFile.readByte(); buf[i] != 0; buf[++i] = ipFile.readByte());<br>   if(i != 0) <br>       return Utils.getString(buf, 0, i, "GBK");<br>  } catch (IOException e) {   <br>      log.error(e.getMessage());<br>  }<br>  return "";<br> }<br></pre>
<p>代码并不复杂，getIPLocation是主要方法，它检查国家记录格式，并针对字符串形式，模式1，模式2采用不同的代码，readArea则相对简单，因为只有字符串和重定向两种情况需要处理。</p>
<h3>总结</h3>
<p>纯真IP数据库的结构使得查找IP简单迅速，不过你想要编辑它却是比较麻烦的，我想应该需要专门的工具来生成QQWry.dat文件，由于其文件格式的限制，你要直接添加IP记录就不容易了。不过，能查到IP已经很开心了，希望纯真记录越来越多～。</p><img src ="http://www.cppblog.com/eXile/aggbug/56679.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eXile/" target="_blank">eXile</a> 2008-07-20 13:46 <a href="http://www.cppblog.com/eXile/archive/2008/07/20/56679.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++代码风格谷歌版</title><link>http://www.cppblog.com/eXile/archive/2008/07/01/55046.html</link><dc:creator>eXile</dc:creator><author>eXile</author><pubDate>Tue, 01 Jul 2008 07:24:00 GMT</pubDate><guid>http://www.cppblog.com/eXile/archive/2008/07/01/55046.html</guid><wfw:comment>http://www.cppblog.com/eXile/comments/55046.html</wfw:comment><comments>http://www.cppblog.com/eXile/archive/2008/07/01/55046.html#Feedback</comments><slash:comments>7</slash:comments><wfw:commentRss>http://www.cppblog.com/eXile/comments/commentRss/55046.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/eXile/services/trackbacks/55046.html</trackback:ping><description><![CDATA[&nbsp;&nbsp; 原文地址：&nbsp;<a title="谷歌c++代码风格原文地址" href="http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Displaying_Hidden_Details_in_this_Guide#Naming">http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Displaying_Hidden_Details_in_this_Guide#Naming</a><br><br>&nbsp;&nbsp; 谷歌的C++代码风格与网上广为流传的林锐风格有很大不同，不过正如文中所言：重要的是保持一致。它还说出了每一种风格的优缺点，这里只说几个比较有意思的。<br>&nbsp;&nbsp; <br>&nbsp;&nbsp; 1）关于空行：<br>&nbsp;&nbsp; 基本原则：使一个屏幕能容纳更多的代码（显然，这是K&amp;R风格的拥趸)<br>&nbsp; <br>&nbsp;&nbsp; 2) 关于引用参数：<br>&nbsp;&nbsp; 所有的引用前加 const, 如下列：<br>&nbsp;&nbsp; void f(const Object&amp; in, Object&amp; out);&nbsp; // bad<br>&nbsp;&nbsp; void f(const Object&amp; in, Object* out);&nbsp; // good<br><br>&nbsp;&nbsp; 以上两点倒是和Qt相似，还有一个相似的地方是：尽量不要用 unsigned。<br><br>&nbsp;&nbsp; 3）关于全局变量：<br>&nbsp;&nbsp; 禁止使用类全局变量，可以使用一些内置类型的全局变量。（为了避免初始化顺序问题）。<br><br>&nbsp;&nbsp; 4）不提倡的C++特性：<br>&nbsp;&nbsp; 尽量不要使用多重继承；<br>
&nbsp;&nbsp; 尽量不要使用操作符重载；<br>
&nbsp;&nbsp; 尽量不要使用函数重载；<br>
&nbsp;&nbsp; 不要使用函数缺省参数；<br>
&nbsp;&nbsp; 不要使用异常；<br>
&nbsp;&nbsp; 不要使用RTTI；<br>&nbsp;&nbsp; 内置的整性类型只使用int.(64位可使用int64_t, 指针兼容可使用intptr_t或ptrdiff_t).<br><br>&nbsp;&nbsp; 具体的理由和更多的风格参见原文。<br>&nbsp;&nbsp; 另外，还可以和Java风格做个对比：<br>&nbsp;&nbsp; <a href_cetemp="http://www.cppblog.com/eXile/admin/EditPosts.aspx" href="">http://java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html</a><br><br>      <img src ="http://www.cppblog.com/eXile/aggbug/55046.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eXile/" target="_blank">eXile</a> 2008-07-01 15:24 <a href="http://www.cppblog.com/eXile/archive/2008/07/01/55046.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Google App Engine！</title><link>http://www.cppblog.com/eXile/archive/2008/06/03/52031.html</link><dc:creator>eXile</dc:creator><author>eXile</author><pubDate>Tue, 03 Jun 2008 07:08:00 GMT</pubDate><guid>http://www.cppblog.com/eXile/archive/2008/06/03/52031.html</guid><wfw:comment>http://www.cppblog.com/eXile/comments/52031.html</wfw:comment><comments>http://www.cppblog.com/eXile/archive/2008/06/03/52031.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.cppblog.com/eXile/comments/commentRss/52031.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/eXile/services/trackbacks/52031.html</trackback:ping><description><![CDATA[<p>　　Google App Engine开放注册了！每个用户可以申请３个ＡＰＰ，每个ＡＰＰ５００Ｍ空间，(免费的1.5G啊），域名为 xxx.appspot.com ，它支持Python与有限的Django。你可以很容易地在上面配置自己的web应用，比如马上就有人在上面做了一个突破GFW的应用。<br>　　我在想，Google App Engine可以用来做什么？一个免费的主页空间吗？似乎又不限于，它应该有着更为广阔的应用前景，对于一个开发者而言，可以迅速的把自己的一个小想法布诸于实践。<br>　　不管怎么样，gae起码可以使更多的人来使用 Python.</p>
<img src ="http://www.cppblog.com/eXile/aggbug/52031.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eXile/" target="_blank">eXile</a> 2008-06-03 15:08 <a href="http://www.cppblog.com/eXile/archive/2008/06/03/52031.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>利用boost::asio实现一个简单的服务器框架</title><link>http://www.cppblog.com/eXile/archive/2008/05/28/51430.html</link><dc:creator>eXile</dc:creator><author>eXile</author><pubDate>Wed, 28 May 2008 13:00:00 GMT</pubDate><guid>http://www.cppblog.com/eXile/archive/2008/05/28/51430.html</guid><wfw:comment>http://www.cppblog.com/eXile/comments/51430.html</wfw:comment><comments>http://www.cppblog.com/eXile/archive/2008/05/28/51430.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.cppblog.com/eXile/comments/commentRss/51430.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/eXile/services/trackbacks/51430.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 利用boost::asio实现一个简单的服务器框架&nbsp;&nbsp;<a href='http://www.cppblog.com/eXile/archive/2008/05/28/51430.html'>阅读全文</a><img src ="http://www.cppblog.com/eXile/aggbug/51430.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eXile/" target="_blank">eXile</a> 2008-05-28 21:00 <a href="http://www.cppblog.com/eXile/archive/2008/05/28/51430.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Reactor模式中网络事件分派探讨</title><link>http://www.cppblog.com/eXile/archive/2008/05/22/50767.html</link><dc:creator>eXile</dc:creator><author>eXile</author><pubDate>Thu, 22 May 2008 07:27:00 GMT</pubDate><guid>http://www.cppblog.com/eXile/archive/2008/05/22/50767.html</guid><wfw:comment>http://www.cppblog.com/eXile/comments/50767.html</wfw:comment><comments>http://www.cppblog.com/eXile/archive/2008/05/22/50767.html#Feedback</comments><slash:comments>7</slash:comments><wfw:commentRss>http://www.cppblog.com/eXile/comments/commentRss/50767.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/eXile/services/trackbacks/50767.html</trackback:ping><description><![CDATA[<br>原文地址:　<a href="http://www.cppblog.com/eXile">http://www.cppblog.com/eXile</a><br><br>&nbsp;　　对于使用线程池的Reactor模式，针对每一个SOCKET句柄的事件处理器handler可能被分派到不同的线程当中，这就要求handler的每一个操作都是线程安全的。<br>　　可以使用一种办法使一个handler的操作只能分派到一个线程中：为每一个handler设定一个线程所有者ＩＤ，一开始ＩＤ为空，则每个线程都可以分派，第一次分派之后，则设定为该线程ＩＤ，以后只分派到该线程中。这样，可以保证handler操作的单线程性，简化以后handler的具体实现。这个ＩＤ也可以灵活设置，以适应具体事务的要求。<br>　　但这样并不能保证handler彻底无锁，因为还会有两个线程会出现竟争，除了这个事件处理线程以外，还有事件分派线程（即事件侦听线程）。对于这个问题的解决办法如下：为每个handler设定一个原子计数，事件分派线程在分派事件前，首先设定该原子计数，若设置失败，表明此时正有其它线程在处理该handler，则并不分派该事件，而是将它置于一个pending队列中，等待以后分派。<br>&nbsp;　　还有一种简单的方法，就是将该handler直接挂起，处理完后才允许进行事件分派。 
<img src ="http://www.cppblog.com/eXile/aggbug/50767.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eXile/" target="_blank">eXile</a> 2008-05-22 15:27 <a href="http://www.cppblog.com/eXile/archive/2008/05/22/50767.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>大地震: 什么是奇迹? 谁才是英雄?</title><link>http://www.cppblog.com/eXile/archive/2008/05/20/50579.html</link><dc:creator>eXile</dc:creator><author>eXile</author><pubDate>Tue, 20 May 2008 14:19:00 GMT</pubDate><guid>http://www.cppblog.com/eXile/archive/2008/05/20/50579.html</guid><wfw:comment>http://www.cppblog.com/eXile/comments/50579.html</wfw:comment><comments>http://www.cppblog.com/eXile/archive/2008/05/20/50579.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/eXile/comments/commentRss/50579.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/eXile/services/trackbacks/50579.html</trackback:ping><description><![CDATA[<a href="http://news.21cn.com/zhuanti/domestic/08dizhen/2008/05/19/4733366.shtml"><br></a><a href="http://blog.sina.com.cn/lichengpeng"><strong>http://blog.sina.com.cn/lichengpeng</strong></a><br><a id=articleTitle href="http://blog.sina.com.cn/s/blog_46e7ba41010091pk.html" target=_blank _extended="true"><font color=#000000><strong>北川邓家&#8220;刘汉小学&#8221;无一死亡奇迹背后的真相</strong></font></a><strong> <br><br></strong>在单位地域死伤最严重的北川大地震中，虽然北川一中教学楼迅速淹没二千多名学生，但邓家小学483名学生一个都没有少。而且，以肖晓川带队的9名老师携无家长认领的71名学生历经两天一夜，在无水无粮无工具的情况下，先是困守一处山坡，后来翻越水洞子、景家山、杨柳坪三座（之前媒体报道成两座）海拔最高达2000多米的大山，其中还有一名4岁多的学前班孩子，最后到达绵阳。<br><br><br><u><font color=#810081>http://news.21cn.com/zhuanti/domestic/08dizhen/2008/05/19/4733366.shtml<br></font></u><strong>最牛希望小学地震后无恙 网友向建筑商致敬</strong><br>
<p>5月18日《环球时报》在名为《专访挽救78名孩子的英雄教师》的新闻中写道：&#8220;12日下午2时28分，北川县曲山镇海光村刘汉希望小学400多名学生刚午休结束8分钟，突然，大地开始颤动，教室的门窗哗啦啦着响......站在山顶，师生们看见，他们曾经的家园已经变成一堆废墟;他们曾经工作、学习和生活的学校，只剩下那幢3层教学楼仍然倔强而孤单地挺立着。<br></p>
<a href="http://news.21cn.com/social/shixiang/2008/05/20/4738298.shtml"><strong><u><font color=#810081>http://news.21cn.com/social/shixiang/2008/05/20/4738298.shtml</font></u></strong></a><br><strong>最牛小学建筑商：所建五所希望小学均未倒<br><br><br></strong>李承鹏：&#8220;&#215;先生曾给我发来一则短信，未经他同意，我就刊发在我的博客上，目的是让有的人有的部门看看，也提醒以后有人想修希望小学的人看看：打扰您了，可以负责的告诉你，绵阳五所希望小学建设均由我经办，而此次大地震未能撼动一幢，巍然屹立!师生未损毫发!请你来绵阳做客!&nbsp; 这次邓家汉龙小学无一人死亡成为一个奇迹，让我明白一个道理：所谓奇迹——就是你修房子时能在十年前，想到十年后的事情。&#8221; 
<img src ="http://www.cppblog.com/eXile/aggbug/50579.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eXile/" target="_blank">eXile</a> 2008-05-20 22:19 <a href="http://www.cppblog.com/eXile/archive/2008/05/20/50579.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>PyQt 与 Eric 的安装</title><link>http://www.cppblog.com/eXile/archive/2008/05/19/50414.html</link><dc:creator>eXile</dc:creator><author>eXile</author><pubDate>Mon, 19 May 2008 09:57:00 GMT</pubDate><guid>http://www.cppblog.com/eXile/archive/2008/05/19/50414.html</guid><wfw:comment>http://www.cppblog.com/eXile/comments/50414.html</wfw:comment><comments>http://www.cppblog.com/eXile/archive/2008/05/19/50414.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/eXile/comments/commentRss/50414.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/eXile/services/trackbacks/50414.html</trackback:ping><description><![CDATA[<p>PyQt 与 Eric 在WINDOWS平台上的安装<br>By eXile<br><br>1.前提: 已安装 make工具(如Mingw), Qt, Python.<br>pexports C:\WINDOWS\system32\python25.dll &gt; python25.def<br>dlltool --dllname python25.dll --def python25.def --output-lib libpython25.a<br></p>
<p>2.安裝 PyQt<br><br>1)&nbsp;安裝 SIP<br>python configure.py -p win32-g++<br>make<br>make install<br>2)&nbsp;安裝 PyQt<br>python configure.py<br>make <br>make install<br><br><br>3.安装 Eric</p>
<p>1)安裝 QScintilla<br>qmake qscintilla.pro<br>make<br>make install<br>copy %QTDIR%\lib\qscintilla2.dll %QTDIR%\bin</p>
<p>2).安裝 eric<br>python install.py<br><br><a href="http://www.riverbankcomputing.co.uk/news"><u><font color=#810081>http://www.riverbankcomputing.co.uk/news<br></font></u></a><br>原文地址: <a href="http://www.cppblog.com/exile/">http://www.cppblog.com/exile/</a></p>
<img src ="http://www.cppblog.com/eXile/aggbug/50414.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eXile/" target="_blank">eXile</a> 2008-05-19 17:57 <a href="http://www.cppblog.com/eXile/archive/2008/05/19/50414.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>boost::asio示例HTTP Server类图</title><link>http://www.cppblog.com/eXile/archive/2008/05/07/49072.html</link><dc:creator>eXile</dc:creator><author>eXile</author><pubDate>Tue, 06 May 2008 16:50:00 GMT</pubDate><guid>http://www.cppblog.com/eXile/archive/2008/05/07/49072.html</guid><wfw:comment>http://www.cppblog.com/eXile/comments/49072.html</wfw:comment><comments>http://www.cppblog.com/eXile/archive/2008/05/07/49072.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/eXile/comments/commentRss/49072.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/eXile/services/trackbacks/49072.html</trackback:ping><description><![CDATA[<p><a href="http://www.cppblog.com/images/cppblog_com/exile/asio_http_1.jpg"><img height=652 alt="" src="http://www.cppblog.com/images/cppblog_com/exile/asio_http_1.jpg" width=878 align=left border=0></a><a href="http://www.cppblog.com/images/cppblog_com/exile/asio_http_1.jpg"></a></p>
<img src ="http://www.cppblog.com/eXile/aggbug/49072.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eXile/" target="_blank">eXile</a> 2008-05-07 00:50 <a href="http://www.cppblog.com/eXile/archive/2008/05/07/49072.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>单元测试[zt]</title><link>http://www.cppblog.com/eXile/archive/2008/04/29/48422.html</link><dc:creator>eXile</dc:creator><author>eXile</author><pubDate>Tue, 29 Apr 2008 05:39:00 GMT</pubDate><guid>http://www.cppblog.com/eXile/archive/2008/04/29/48422.html</guid><wfw:comment>http://www.cppblog.com/eXile/comments/48422.html</wfw:comment><comments>http://www.cppblog.com/eXile/archive/2008/04/29/48422.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/eXile/comments/commentRss/48422.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/eXile/services/trackbacks/48422.html</trackback:ping><description><![CDATA[<br>来源: WingFire On Toplanguange<br><br>1.单元测试库要尽量少地增加开发人员的负担。额外负担必须尽可能直白，傻瓜化。<br>市面上的许多讲到单元测试的书都是以XUnit为蓝本的，这导致CppUnit的接受程度颇高。CppUnit中规中矩，四平八稳，但不够犀利。个人认为boost.test最简单，只要一个BOOST_AUTO_TEST_CASE就可以开始了。CppUnit则要复杂一点，而这种复杂性是多余的，甚至是有害的。用CppUnit的时候，我看到有人为了共享测试代码，随便在test case里面加函数，然后复用，结果导致case不独立。boost.test倾向于不要建立.h文件，所以要复用不方便（或者，不习惯在Cpp中复用），反而不容易犯错误。<br>2.实施单元测试，必须能够让程序员看得到好处并尽快受益。新项目必须尽早引入单元测试，要早在正式编码之前。<br>想立刻让UT变得完美是不可能的，行政命令也不会有好结果。在推行单元测试的时候，教育很重要。必须让同事能理解单元测试为什么有效，如何工作，UT编写准则之类的问题。另外，在工作多年的程序员（对UT缺乏认识的）中推行单元测试，阻力更大。更要注意教育和反馈。最好的反馈就是帮助他们从单元测试中获益。例如，修改更轻松，思维更面向接口，bug更少，代码更容易理解等等。作为推动者，有义务去主动发现这些改善之处并积极地反馈给程序员。从而增强应用UT的信心和意愿。<br>3.必须充分自动化。<br>UT的任务之一是给代码编织一层细密的保护网。程序员应该认识到，单元测试是为自己服务的，所以，我们要的是完成任务而不是展示。能够自动地完成任务则是最好的。如果单元测试过多地干扰程序员的正常思考，就会招致更多的抵触（抵触总是存在的）或敷衍。敷衍是可怕的。我向来是把单元测试的运行作为build的一个步骤的。成功的单元测试不需要输出任何信息，最多在全部passs的时候给个OK就足够了。图形界面的测试工具在我看来也是鸡肋，新手的玩具而已。图形界面既不利于参数化运行，也不方便自动化，实在是降低开发效率的杀手。<br>4.不要追求完美的UT。<br>不是所有东西都很容易测试。UT要求被测试的东西可重现，可观测。 基本上，大部分的物理操作因为缺乏可重复性或可观察性，很难测试，例如database，GUI （注意，这不意味着在实现一个GUI库或db driver时就不能做UT了）。勉强UT全覆盖，既不现实，也不实惠。并且，这很可能让UT变得复杂，高成本，这是非常危险的和不值得的。我的主张是，很难测，那就不测，但要正确应对。我的做法是将难测的部分隔离到一些抽象层当中去。然后为这些抽象层写MockObject即可测试了。我曾经应用在数据库应用中，并很自然的得到一个良好的数据访问的抽象层，单元测试就只测了这个抽象层。而实际的数据库访问中的物理操作部分，则从单元测试中剥离出去。如果坚持分离物理操作和逻辑操作的话，这个剥离出去的部分一般很小很有限，也很容易测试。相反，如果不剥离，将导致单元测试的结果要依赖数据库的状态。这种额外的依赖性没什么好处。这里的关键是，必须让不可测的部分尽可能隔离，尽可能小，尽可能地将逻辑操作从物理操作中分离出来。被隔离部分所包含的逻辑操作仍然需要写UT。 
<img src ="http://www.cppblog.com/eXile/aggbug/48422.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/eXile/" target="_blank">eXile</a> 2008-04-29 13:39 <a href="http://www.cppblog.com/eXile/archive/2008/04/29/48422.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>