{"id":589,"date":"2011-09-14T23:14:15","date_gmt":"2011-09-14T21:14:15","guid":{"rendered":"https:\/\/d-mueller.de\/blog\/?p=589"},"modified":"2011-09-16T21:55:40","modified_gmt":"2011-09-16T19:55:40","slug":"erwartete-exceptions-richtig-testen","status":"publish","type":"post","link":"https:\/\/d-mueller.de\/blog\/erwartete-exceptions-richtig-testen\/","title":{"rendered":"Erwartete Exceptions richtig testen"},"content":{"rendered":"<p>Der klassische Ablauf beim Testen von Code, der eine Exception werfen <b>soll<\/b>, ist <a href=\"http:\/\/www.phpunit.de\/manual\/current\/en\/appendixes.annotations.html#appendixes.annotations.expectedException\">der Folgende<\/a> (PHPUnit):<\/p>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">\r\n\/**\r\n * @expectedException InvalidArgumentException\r\n *\/\r\npublic function testException()\r\n{\r\n   throw new InvalidArgumentException();\r\n}\r\n<\/pre>\n<p>Problem dabei: Wir haben nicht spezifiziert, an welcher Stelle die Exception geworfen werden soll. Au\u00dferdem k\u00f6nnen wir nicht pr\u00fcfen, ob die geworfene Exception <b>genau<\/b> die erwartete oder nur igendeine war.<\/p>\n<p>Jetzt l\u00e4sst sich das noch aufbohren:<\/p>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">\r\n\/**\r\n * @expectedException        InvalidArgumentException\r\n * @expectedExceptionMessage Right Message\r\n *\/\r\npublic function testExceptionHasRightMessage()\r\n{\r\n    throw new InvalidArgumentException(&#039;Right Message&#039;);\r\n}\r\n<\/pre>\n<p><p>Auch damit werde ich nicht gl\u00fccklich. Wenn ich jetzt z.B. mehrere Exceptions in einem Test pr\u00fcfen m\u00f6chte (guter Stil hin oder her) st\u00f6\u00dft man an die Grenzen diesen Ansatzes. <\/p>\n<p>Etwas feingranularer ist das Handling mit der nachfolgend vorstellten Methode <i>setExpectedException<\/i>.<\/p>\n<h3>Alternative Methode<\/h3>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">\r\npublic function testExceptionHasRightMessage()\r\n{\r\n    $this-&gt;setExpectedException(\r\n      &#039;InvalidArgumentException&#039;, &#039;Right Message&#039;\r\n    );\r\n\t\r\n    throw new InvalidArgumentException(&#039;Right Message&#039;);\r\n}\r\n<\/pre>\n<p>Macht letztlich einen entscheidenden Unterschied verglichen mit der Variante per Annotation: Ich kann den Zeitpunkt selbst bestimmen, ab dem ich eine Exception erwarte. Die Annotation greift direkt ab der ersten Zeile der Methode, in der ich vielleicht noch garkeine Exception haben m\u00f6chte.<\/p>\n<p>Weils so sch\u00f6n simpel ist: Das macht <i>setExpectedException<\/i> under the hood:<\/p>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">\r\npublic function setExpectedException($exceptionName, $exceptionMessage = &#039;&#039;, $exceptionCode = 0)\r\n{\r\n    if ($exceptionName == &#039;Exception&#039;) {\r\n\tthrow new InvalidArgumentException(\r\n\t  &#039;You must not expect the generic exception class.&#039;\r\n  \t);\r\n    }\r\n\r\n    $this-&gt;expectedException        = $exceptionName;\r\n    $this-&gt;expectedExceptionMessage = $exceptionMessage;\r\n    $this-&gt;expectedExceptionCode    = $exceptionCode;\r\n    $this-&gt;expectedExceptionTrace   = debug_backtrace();\r\n}\r\n<\/pre>\n<p>&#8230; und wenn dann eine Exception fliegt, pr\u00fcft das PHP Unit folgenderma\u00dfen ab:<\/p>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">\r\ntry {\r\n\t$testResult = $method-&gt;invokeArgs(\r\n\t      $this, array_merge($this-&gt;data, $this-&gt;dependencyInput)\r\n\t);\r\n}\r\n\r\ncatch (Exception $e) {\r\n\tif (!$e instanceof PHPUnit_Framework_IncompleteTest &amp;&amp;\r\n\t\t!$e instanceof PHPUnit_Framework_SkippedTest &amp;&amp;\r\n\t\tis_string($this-&gt;expectedException) &amp;&amp;\r\n\t\t$e instanceof $this-&gt;expectedException) {\r\n\t\tif (is_string($this-&gt;expectedExceptionMessage) &amp;&amp;\r\n\t\t\t!empty($this-&gt;expectedExceptionMessage)) {\r\n\t\t\t$this-&gt;assertContains(\r\n\t\t\t  $this-&gt;expectedExceptionMessage,\r\n\t\t\t  $e-&gt;getMessage()\r\n\t\t\t);\r\n\t\t}\r\n       \/\/ ...\r\n}\r\n<\/pre>\n<h3>Volle Kontrolle!<\/h3>\n<p>Auch wenn das Testen mehrerer Exceptions in einer Methode durchaus umstritten ist, gibt es nun noch ein weiteres, gern genutztes Pattern, das eben dies erm\u00f6glicht:<\/p>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">\r\ntry\r\n{\r\n    code();\r\n    $this-&gt;fail(&quot;No Exception was thrown&quot;);\r\n}\r\ncatch (InvalidArgumentException $ex)\r\n{\r\n    $this-&gt;assertEquals($ex-&gt;getMessage(), &quot;Expected Exception-Text&quot;, &quot;Wrong exceptiontext...&quot;);\r\n}\r\ncatch (Exception $ex)\r\n{\r\n    $this-&gt;fail(&quot;Wrong Exception was thrown&quot;);\r\n}\r\n<\/pre>\n<p>Muss nat\u00fcrlich f\u00fcr maximalen Komfort noch ausgebaut werden, ihr versteht worauf ich hinauswill.<\/p>\n<p>Um das etwas komfortabler und wiederholungsfreier zu gestalten, hat <a href=\"http:\/\/lars-tesmer.com\/blog\/2011\/08\/29\/phpunit-better-syntax-for-expecting-exceptions\/\">dieser Herr<\/a> eine <a href=\"https:\/\/github.com\/kiltec\/phpunit\/commit\/dd5c7bd71d6eb8d4b58ce79b5ae069fbb0734354\">Erweiterung<\/a> zu PHPUnit geschrieben, womit uns seine Methode <i>assertThrowsException<\/i> die Arbeit abnimmt und den zu testenden Code in einer anonymen Funktion kapselt. F\u00fchlt sich f\u00fcr mich am sympathischsten an.<\/p>\n<pre data-enlighter-language=\"php\" class=\"EnlighterJSRAW\">\r\n&lt;?php\r\npublic function testSomeImportantMethod() {\r\n    $someClass = new SomeClass();\r\n\r\n    $this-&gt;assertThrowsException(&#039;InvalidArgumentException&#039;, function () use($someClass) {\r\n            $someClass-&gt;someMethod();\r\n        }\r\n    );\r\n}\r\n<\/pre>\n<p><b>Update:<\/b> Danke an Thomas f\u00fcr eine Richtigstellung, habe den Artikel entsprechend angepasst.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Der klassische Ablauf beim Testen von Code, der eine Exception werfen soll, ist der Folgende (PHPUnit): \/** * @expectedException InvalidArgumentException *\/ public function testException() { throw new InvalidArgumentException(); } Problem dabei: Wir haben nicht spezifiziert, an welcher Stelle die Exception &hellip; <a href=\"https:\/\/d-mueller.de\/blog\/erwartete-exceptions-richtig-testen\/\">Weiterlesen <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,3],"tags":[],"class_list":["post-589","post","type-post","status-publish","format-standard","hentry","category-php","category-webdev"],"_links":{"self":[{"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/posts\/589","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/comments?post=589"}],"version-history":[{"count":0,"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/posts\/589\/revisions"}],"wp:attachment":[{"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/media?parent=589"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/categories?post=589"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/d-mueller.de\/blog\/wp-json\/wp\/v2\/tags?post=589"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}