PageRenderTime 48ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/tags/1.1.0/docs/source/en/mock_objects_tutorial.xml

https://bitbucket.org/bpanulla/simpletest
XML | 360 lines | 342 code | 17 blank | 1 comment | 0 complexity | 1e08b5bae5f0c9a9587f52b56066b92d MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?xml version="1.0"?>
  2. <!-- $Id: mock_objects_tutorial.xml 1979 2010-03-15 11:16:16Z lastcraft $ -->
  3. <page title="Mock Objects" here="Using mock objects">
  4. <long_title>PHP unit testing tutorial - Using mock objects in PHP</long_title>
  5. <content>
  6. <section name="refactor" title="Refactoring the tests again">
  7. <p>
  8. Before more functionality is added there is some refactoring
  9. to do.
  10. We are going to do some timing tests and so the
  11. <code>TimeTestCase</code> class definitely needs
  12. its own file.
  13. Let&apos;s say <em>tests/time_test_case.php</em>...
  14. <php><![CDATA[
  15. <strong><?php
  16. require_once('simpletest/unit_tester.php');
  17. abstract class TimeTestCase extends UnitTestCase {
  18. function assertSameTime($time1, $time2, $message = '') {
  19. if (! $message) {
  20. $message = "Time [$time1] should match time [$time2]";
  21. }
  22. $this->assertTrue(
  23. ($time1 == $time2) || ($time1 + 1 == $time2),
  24. $message);
  25. }
  26. }
  27. ?></strong>
  28. ]]></php>
  29. We can then <code>require()</code> this file into
  30. the <em>all_tests.php</em> script.
  31. </p>
  32. </section>
  33. <section name="timestamp" title="Adding a timestamp to the Log">
  34. <p>
  35. I don&apos;t know quite what the format of the log message should
  36. be for the test, so to check for a timestamp we could do the
  37. simplest possible thing, which is to look for a sequence of digits.
  38. <php><![CDATA[
  39. <?php
  40. require_once('simpletest/autorun.php');<strong>
  41. require_once('time_test_case.php');</strong>
  42. require_once('../classes/log.php');<strong>
  43. require_once('../classes/clock.php');
  44. class TestOfLogging extends TimeTestCase {</strong>
  45. function setUp() {
  46. @unlink('../temp/test.log');
  47. }
  48. function tearDown() {
  49. @unlink('../temp/test.log');
  50. }
  51. function getFileLine($filename, $index) {
  52. $messages = file($filename);
  53. return $messages[$index];
  54. }
  55. function testCreatingNewFile() { ... }
  56. function testAppendingToFile() { ... }
  57. <strong>
  58. function testTimestampIsEmittedInMessage() {
  59. $log = new Log('../temp/test.log');
  60. $log->message('Test line');
  61. $this->assertTrue(
  62. preg_match('/(\d+)/', $this->getFileLine('../temp/test.log', 0), $matches),
  63. 'Found timestamp');
  64. $clock = new clock();
  65. $this->assertSameTime((integer)$matches[1], $clock->now(), 'Correct time');
  66. }</strong>
  67. }
  68. ?>
  69. ]]></php>
  70. The test case creates a new <code>Log</code> object and writes a message.
  71. We look for a digit sequence and then test it against the current
  72. time using our <code>Clock</code> object.
  73. Of course it doesn&apos;t work until we write the code.
  74. <div class="demo">
  75. <h1>All tests</h1>
  76. <span class="pass">Pass</span>: log_test.php->Log class test->testAppendingToFile->Expecting [/Test line 1/] in [Test line 1]<br />
  77. <span class="pass">Pass</span>: log_test.php->Log class test->testAppendingToFile->Expecting [/Test line 2/] in [Test line 2]<br />
  78. <span class="pass">Pass</span>: log_test.php->Log class test->testCreatingNewFile->Created before message<br />
  79. <span class="pass">Pass</span>: log_test.php->Log class test->testCreatingNewFile->File created<br />
  80. <span class="fail">Fail</span>: log_test.php->Log class test->testTimestampIsEmittedInMessage->Found timestamp<br />
  81. <br />
  82. <b>Notice</b>: Undefined offset: 1 in <b>/home/marcus/projects/lastcraft/tutorial_tests/tests/log_test.php</b> on line <b>44</b><br />
  83. <span class="fail">Fail</span>: log_test.php->Log class test->testTimestampIsEmittedInMessage->Correct time<br />
  84. <span class="pass">Pass</span>: clock_test.php->Clock class test->testClockAdvance->Advancement<br />
  85. <span class="pass">Pass</span>: clock_test.php->Clock class test->testClockTellsTime->Now is the right time<br />
  86. <div style="padding: 8px; margin-top: 1em; background-color: red; color: white;">3/3 test cases complete.
  87. <strong>6</strong> passes, <strong>2</strong> fails and <strong>2</strong> exceptions.</div>
  88. </div>
  89. The test suite is still showing the passes from our earlier
  90. modification.
  91. </p>
  92. <p>
  93. We can get the tests to pass simply by adding a timestamp
  94. when writing out to the file.
  95. Yes, of course all of this is trivial and
  96. I would not normally test this fanatically, but it is going
  97. to illustrate a more general problem.
  98. The <em>log.php</em> file becomes...
  99. <php><![CDATA[
  100. <?php<strong>
  101. require_once('../classes/clock.php');</strong>
  102. class Log {
  103. private $path;
  104. function __construct($path) {
  105. $this->path = $path;
  106. }
  107. function message($message) {
  108. <strong>$clock = new Clock();</strong>
  109. $file = fopen($this->path, 'a');
  110. <strong>fwrite($file, "[" . $clock->now() . "] $message\n");</strong>
  111. fclose($file);
  112. }
  113. }
  114. ?>
  115. ]]></php>
  116. The tests should now pass.
  117. </p>
  118. <p>
  119. Our new test is full of problems, though.
  120. What if our time format changes to something else?
  121. Things are going to be a lot more complicated to test if this
  122. happens.
  123. It also means that any changes to the clock class time
  124. format will cause our logging tests to fail also.
  125. This means that our log tests are tangled up with the clock tests
  126. and extremely fragile.
  127. It lacks cohesion, which is the same as saying it is not
  128. tightly focused, testing facets of the clock as well as the log.
  129. </p>
  130. <p>
  131. Our problems are caused in part because the clock output
  132. is unpredictable, yet all we really want to test is that the
  133. logging message contains the output of <code>Clock::now()</code>.
  134. We don&apos;t really care about the contents of that method call.
  135. </p>
  136. <p>
  137. From the testing point of view, everything we've just done is
  138. wrong.
  139. We've failed to control the inputs and the outputs of the object
  140. we are testing.
  141. </p>
  142. <p>
  143. Can we make that call predictable?
  144. We could if we could get the log to use a dummy version
  145. of the clock for the duration of the test.
  146. The dummy clock class would have to behave the same way
  147. as the <code>Clock</code> class
  148. except for the fixed output from the
  149. <code>now()</code> method.
  150. Hey, that would even free us from using the
  151. <code>TimeTestCase</code> class!
  152. </p>
  153. <p>
  154. We could write such a class pretty easily although it is
  155. rather tedious work.
  156. We just create another clock class with same interface
  157. except that the <code>now()</code> method
  158. returns a value that we can change with some other setter method.
  159. That is quite a lot of work for a pretty minor test.
  160. </p>
  161. <p>
  162. Except that it is hardly any work at all.
  163. </p>
  164. </section>
  165. <section name="mock" title="A mock clock">
  166. <p>
  167. To reach instant testing clock nirvana we need
  168. only three extra lines of code...
  169. <php><![CDATA[
  170. require_once('simpletest/mock_objects.php');
  171. ]]></php>
  172. This includes the mock generator code.
  173. It is simplest to place this in the <em>all_tests.php</em>
  174. script as it gets used rather a lot.
  175. <php><![CDATA[
  176. Mock::generate('Clock');
  177. ]]></php>
  178. This is the line that does the work.
  179. The code generator scans the class for all of its
  180. methods, creates code to generate an identically
  181. interfaced or inherited class, but with the name
  182. mangled to have &quot;Mock&quot; added.
  183. It then <code>eval()</code>s the new code to
  184. create the new class.
  185. <php><![CDATA[
  186. $clock = new MockClock();
  187. ]]></php>
  188. This line can be added to any test method we are interested in.
  189. It creates the dummy clock ready to receive our instructions.
  190. </p>
  191. <p>
  192. Our test case is on the first steps of a radical clean up.
  193. First is to actually use the <code>MockClock</code> in our test...
  194. <php><![CDATA[
  195. <?php
  196. require_once('simpletest/autorun.php');<strong>
  197. require_once('simpletest/mock_objects.php');</strong>
  198. require_once('../classes/log.php');
  199. require_once('../classes/clock.php');<strong>
  200. Mock::generate('Clock');</strong>
  201. class TestOfLogging extends <strong>UnitTestCase</strong> {
  202. function setUp() {
  203. @unlink('../temp/test.log');
  204. }
  205. function tearDown() {
  206. @unlink('../temp/test.log');
  207. }
  208. function getFileLine($filename, $index) {
  209. $messages = file($filename);
  210. return $messages[$index];
  211. }
  212. function testCreatingNewFile() { ... }
  213. function testAppendingToFile() { ... }
  214. function testTimestamps() {
  215. <strong>$clock = new MockClock();</strong>
  216. <strong>$clock->returns('now', 'Timestamp');</strong>
  217. $log = new Log('../temp/test.log');
  218. $log->message('Test line', $clock);
  219. $this->assertPattern(
  220. <strong>'/Timestamp/'</strong>,
  221. $this->getFileLine('../temp/test.log', 0));
  222. }
  223. }
  224. ?>
  225. ]]></php>
  226. Note that we don't need our <code>TimeTestCase</code> anymore.
  227. It will still be useful for testing the <code>Clock</code>, but we
  228. don't need it for testing the <code>Loc</code> anymore.
  229. </p>
  230. <p>
  231. This test method creates a <code>MockClock</code>
  232. object and then sets the return value of the
  233. <code>now()</code> method to be the string
  234. &quot;Timestamp&quot;.
  235. Every time we call <code>$clock->now()</code>
  236. it will return this string.
  237. This should be easy to spot.
  238. </p>
  239. <p>
  240. Next we create our log and send a message.
  241. We pass into the <code>message()</code>
  242. call the clock we would like to use.
  243. This means that we will have to add an optional parameter to
  244. the logging class to make testing possible...
  245. <php><![CDATA[
  246. class Log {
  247. private $path;
  248. function Log($path) {
  249. $this->path = $path;
  250. }
  251. function message($message, <strong>$clock = false</strong>) {<strong>
  252. $clock = $clock? $clock : new Clock();</strong>
  253. $file = fopen($this->path, 'a');
  254. fwrite($file, "[" . $clock->now() . "] $message\n");
  255. fclose($file);
  256. }
  257. }
  258. ]]></php>
  259. All of the tests now pass and they test only the logging code.
  260. We can breathe easy again.
  261. </p>
  262. <p>
  263. Does that extra parameter in the <code>Log</code>
  264. class bother you?
  265. We have changed the interface just to facilitate testing after
  266. all.
  267. Are not interfaces the most important thing?
  268. Have we sullied our class with test code?
  269. </p>
  270. <p>
  271. It's not ideal, but consider this.
  272. Next chance you get, look at a circuit board, perhaps the motherboard
  273. of the computer you are looking at right now.
  274. On most boards you will find the odd empty hole, or solder
  275. joint with nothing attached or perhaps a pin or socket
  276. that has no obvious function.
  277. Chances are that some of these are for expansion and
  278. variations, but most of the remainder will be for testing.
  279. </p>
  280. <p>
  281. The factories making the boards many times over wasting material
  282. on parts that do not add to the final function.
  283. If hardware engineers can make this sacrifice of elegance I am
  284. sure we can too.
  285. Our sacrifice wastes no materials after all.
  286. </p>
  287. <p>
  288. Still bother you?
  289. Actually it bothers me too.
  290. If it really bothers you, then move the creation of the clock
  291. into another protected factory method.
  292. Then subclass the clock for testing and override the
  293. factory method with one that returns the mock.
  294. Your tests are clumsier, but your interface is intact.
  295. </p>
  296. <p>
  297. I leave the decision to you, but note that we have some
  298. automation in the next section that helps make the subclassing
  299. easier.
  300. </p>
  301. </section>
  302. </content>
  303. <internal>
  304. <link>
  305. <a href="#refactor">Refactoring the tests</a> so we can reuse
  306. our new time test.
  307. </link>
  308. <link>Adding <a href="#timestamp">Log timestamps</a>.</link>
  309. <link><a href="#mock">Mocking the clock</a> to make the test cohesive.</link>
  310. </internal>
  311. <external>
  312. <link>
  313. This follows the <a href="first_test_tutorial.php">unit test tutorial</a>.
  314. </link>
  315. <link>
  316. Next is distilling <a href="boundary_classes_tutorial.php">boundary classes</a>.
  317. </link>
  318. <link>
  319. You will need the <a href="simple_test.php">SimpleTest</a>
  320. tool to run the examples.
  321. </link>
  322. <link>
  323. <a href="http://www.mockobjects.com/">Mock objects</a> papers.
  324. </link>
  325. </external>
  326. <meta>
  327. <keywords>
  328. software development,
  329. php programming,
  330. programming php,
  331. software development tools,
  332. php tutorial,
  333. free php scripts,
  334. architecture,
  335. php resources,
  336. mock objects,
  337. junit,
  338. php testing,
  339. unit test,
  340. php testing
  341. </keywords>
  342. </meta>
  343. </page>