<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title><![CDATA[向东博客 专注WEB应用 构架之美 --- 构架之美，在于尽态极妍 | 应用之美，在于药到病除]]></title> 
<link>http://jackxiang.com/index.php</link> 
<description><![CDATA[赢在IT，Playin' with IT,Focus on Killer Application,Marketing Meets Technology.]]></description> 
<language>zh-cn</language> 
<copyright><![CDATA[向东博客 专注WEB应用 构架之美 --- 构架之美，在于尽态极妍 | 应用之美，在于药到病除]]></copyright>
<item>
<link>http://jackxiang.com/post//</link>
<title><![CDATA[phpunit单元测试之Mock对象和数据库相关代码的测试]]></title> 
<author>jack &lt;xdy108@126.com&gt;</author>
<category><![CDATA[WEB2.0]]></category>
<pubDate>Fri, 25 Jun 2010 14:03:19 +0000</pubDate> 
<guid>http://jackxiang.com/post//</guid> 
<description>
<![CDATA[ 
	当需要测试的类依赖另外一个类的实例，而你又不想这两个类紧密关联时，Mock对象就派上用场了。Mock对象是原实例的“克隆体”，我们可以用它完成断言或替换原实例的某些功能来简化测试。<br/><br/>本文首先讲述如何在PHPUnit中创建和使用mock对象，然后再举例说明如何使用mock对象测试从数据库中取回数据的代码。<br/><br/><br/>Mock 基础<br/>在讲述PHPUnit如何实现mock对象之前，我们先看一下mock对象的工作原理。<br/><br/>创建一个mock对象非常简单，即使没有PHPUnit提供的那些精巧的方法，只需要创建一个类，继承你需要模拟的类即可：<br/><br/>class SomeClassMock extends SomeClass &#123;<br/>&#125;<br/>现在我们就可以使用这个mock对象，尽管它还没有什么实际用处。<br/><br/>假设待测的类会调用SomeClass的一个方法，现在我们需要确认该方法确实在测试中调用了。在mock类中增加一个变量和方法：<br/><br/>class SomeClassMock extends SomeClass &#123;<br/>public $methodWasCalled = false;<br/><br/>public function aMethod() &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;//This method should get called by the object we are testing<br/>&nbsp;&nbsp;&nbsp;&nbsp;$this-&gt;methodWasCalled = true;<br/>&#125;<br/>&#125;<br/>现在，就可以在测试用例中这么做：<br/><br/>//Just a simple example<br/>public function testAMethodIsCalled() &#123;<br/>//First, create a mock of SomeClass<br/>$someClass = new SomeClassMock();<br/><br/>//This is the object we are testing, let&#039;s assume<br/>//it takes a SomeClass instance as the parameter<br/>$object = new MyClass($someClass);<br/><br/>//Say the method in SomeClass needs to be called now<br/>$object-&gt;doSomething();<br/><br/>//Confirm the method was called by checking the mock&#039;s variable:<br/>$this-&gt;assertTrue($someClass-&gt;methodWasCalled);<br/>&#125;<br/>在PHPUnit中创建mock对象<br/>像上面这么创建mock对象非常复杂和浪费时间，幸运地，我们并不需要自己写这些mock类，因为PHPUnit提供了一组方法，我们可以用它们完成大部分mock所需的工作。下面我们将使用这些API，不过了解背后的原理，总是有益处的。<br/><br/>我们使用PHPUnit的mock API重写一下上面的测试：<br/><br/>public function testAMethodIsCalled() &#123;<br/>//First, create a mock of SomeClass<br/>$someClass = $this-&gt;getMock(&#039;SomeClass&#039;);<br/><br/>//Now we can tell the mock what we want to do:<br/>$someClass-&gt;expects($this-&gt;once())<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-&gt;method(&#039;aMethod&#039;);<br/><br/>//This is the object we are testing, let&#039;s assume<br/>//it takes a SomeClass instance as the parameter<br/>$object = new MyClass($someClass);<br/><br/>//Say the method in SomeClass needs to be called now<br/>$object-&gt;doSomething();<br/><br/>//We don&#039;t need to do anything else - the mock object will confirm<br/>//that the method was called for us.<br/>&#125;<br/>简单多了吧？不用再为一个简单的方法调用检查去写一个完整的类，仅用getMock方法获取原对象的一个mock版本，然后告诉它这个测试中它需要做什么。<br/><br/>我们对这个mock对象做了什么呢？<br/><br/>第一步，调用expects方法，并将$this-&gt;once()作为参数传入。 第二步，以参数&#039;aMethod‘调用method方法。上面操作意味着mock对象的aMethod方法将被调用一次。像这样用mock对象中的一个方法来替换原方法，即通常所说的桩方法——原对象中的方法被一个测试桩替代。<br/><br/>如果这个方法没有被调用，那会发生什么呢？运行测试的时候，将得到如下的一个类似错误：<br/><br/>1) testAMethodIsCalled(ExampleTest)<br/>Expectation failed for method name is equal to &lt;string:aMethod&gt; when invoked 1 time(s).<br/>Method was expected to be called 1 times, actually called 0 times.<br/><br/>使用mock对象测试数据库代码<br/>在了解了mock对象之后，现在开始看一些真实的例子。<br/><br/>一个常见的情形是测试和数据库相关的代码，比如，待测代码会从数据库中取回或插入一些数据。这可以通过创建一个测试数据库来实现，但是用mock对象的话，事情将会更加简单。<br/><br/>测试样例<br/>假设有一个EventRepository类，它记录应用中的某些事件。还有一个类Event，表示特定的事件。现在需要为EventRepository类编写单元测试，但是它需要访问数据库，使得问题变得很困难。<br/><br/>class EventRepository &#123;<br/>private $_pdo;<br/><br/>public function __construct(PDO $database) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;$this-&gt;_pdo = $database;<br/>&#125;<br/><br/>public function findById() &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;$sql = &#039;SELECT * FROM events WHERE ...&#039;;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;//Here we have some code to execute the SQL, convert<br/>&nbsp;&nbsp;&nbsp;&nbsp;//the result to an Event object and then return it<br/>&#125;<br/><br/>/* some other methods here */<br/>&#125;<br/>为了测试这个类，我们需要一个测试数据库，因为SQL查询直接在这个类内部执行，测试对此无能为力。<br/><br/>让EventRepository变得可测试<br/>为了让这个类更合理，更易于测试，需要修改类，让它使用一个数据访问对象。这个数据访问对象(DAO)可以如下所示：<br/><br/>class EventDao &#123;<br/>private $_pdo;<br/><br/>public function __construct(PDO $pdo) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;$this-&gt;_pdo = $pdo;<br/>&#125;<br/><br/>public function findById() &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;$sql = &#039;SELECT * FROM events WHERE ...&#039;;<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;//Instead of event repository, the code to execute SQL is here.<br/>&nbsp;&nbsp;&nbsp;&nbsp;//We then return the row&#039;s data as an array<br/>&#125;<br/><br/>/* some other methods here */<br/>&#125;<br/>然后，修改EventRepository类，使用EventDao：<br/><br/>class EventRepository &#123;<br/>private $_dao;<br/><br/>public function __construct(EventDao $dao) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;$this-&gt;_dao = $dao;<br/>&#125;<br/><br/>public function findById($id) &#123;<br/>&nbsp;&nbsp;&nbsp;&nbsp;$row = $this-&gt;_dao-&gt;findById($id);<br/><br/>&nbsp;&nbsp;&nbsp;&nbsp;//Now we simply process $row into an Event object. No direct DB access<br/>&#125;<br/><br/>/* some other methods here */<br/>&#125;<br/>测试<br/>现在可以测试这个类了！我们mock一个EventDao对象，这样测试代码就不需要访问数据库。<br/><br/>下面为findById写一个测试，示例如何创建DAO的mock对象：<br/><br/>public function testFindsCorrectEvent() &#123;<br/>//Set up a fake database row<br/>$eventRow = array(<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#039;id&#039; =&gt; 1,<br/>&nbsp;&nbsp;&nbsp;&nbsp;&#039;name&#039; =&gt; &#039;Awesome event&#039;<br/>);<br/><br/>$dao = $this-&gt;getMock(&#039;EventDao&#039;);<br/><br/>//Set up the mock to return the fake row when findById is called<br/>$dao-&gt;expects($this-&gt;once())<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-&gt;method(&#039;findById&#039;)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-&gt;with(1)<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-&gt;will($this-&gt;returnValue($eventRow));<br/><br/>$repo = new EventRepository($dao);<br/><br/>$event = $repo-&gt;findById(1);<br/><br/>//Confirm ID of the returned Event is correct<br/>$this-&gt;assertEquals(1, $event-&gt;getId());<br/>&#125;<br/>这里包含了mock API的更多特性——检查参数和确定返回值。<br/><br/>首先，$eventRow 是mock对象dao的假返回值。因为仓库类要正常运行，就需要dao对象返回一个值，所以这里构造了一个返回值。<br/>mock对象的expects 和method 方法和前面的一样，不过新加了with(1) 和will($this-&gt;returnValue($eventRow)) 的调用。<br/><br/>with 方法用于声明模拟方法需要哪些参数，这个例子要求传入参数事件ID：1。如果调用这个模拟方法时没有传入参数1，那么这个测试将会报告失败。<br/><br/>will 方法声明模拟方法被调用时它应该做什么，这里我们希望它返回我们构造的假返回值。<br/><br/>测试的其余部分创建我们测试的仓库实例，然后调用方法，最后断言返回值是正确的。这里没有提供findById的剩余代码，也没有提供Event的getId的实现，不过可以假设findById会用数据行创建Event实例，而Event拥有getId方法。<br/><br/>总结与进阶阅读<br/>通过调用PHPUnit的mock API，可以非常方便地使用mock对象替换测试类对其他类的依赖，从而让测试变得简单。<br/><br/>我们没有列举全部的mock调用，比如如何抛出异常等。如果需要进一步研究，推荐阅读PHPUnit manual stubs and mocks chapter。<br/><br/>原文链接：http://codeutopia.net/blog/2009/06/26/unit-testing-4-mock-objects-and-testing-code-which-uses-the-database/<br/> <br/>
]]>
</description>
</item><item>
<link>http://jackxiang.com/post//#blogcomment</link>
<title><![CDATA[[评论] phpunit单元测试之Mock对象和数据库相关代码的测试]]></title> 
<author> &lt;user@domain.com&gt;</author>
<category><![CDATA[评论]]></category>
<pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate> 
<guid>http://jackxiang.com/post//#blogcomment</guid> 
<description>
<![CDATA[ 
	
]]>
</description>
</item>
</channel>
</rss>