À ces réponses: qu'en est-il des multi-assertions dans une fonction de test, et je m'attends juste à avoir une exception de levée? Dois-je les séparer et mettre celui-ci dans une fonction de test indépendante?
Panwen Wang
Réponses:
551
<?php
require_once 'PHPUnit/Framework.php';classExceptionTestextendsPHPUnit_Framework_TestCase{publicfunction testException(){
$this->expectException(InvalidArgumentException::class);// or for PHPUnit < 5.2// $this->setExpectedException(InvalidArgumentException::class);//...and then add your test code that generates the exception
exampleMethod($anInvalidArgument);}}
Si vous utilisez des espaces de noms, vous devez bien saisir l'espace de noms complet:$this->setExpectedException('\My\Name\Space\MyCustomException');
Alcalyn
15
Le fait que vous ne pouvez pas désigner la ligne de code précise qui doit être lancée est une erreur OMI. Et l'incapacité de tester plus d'une exception dans le même test, rend le test de nombreuses exceptions attendues une affaire vraiment maladroite. J'ai écrit une affirmation réelle pour essayer de résoudre ces problèmes.
mindplay.dk
18
Pour info: à partir de phpunit 5.2.0, lasetExpectedException méthode est déconseillée, remplacée par celle- expectExceptionlà. :)
hejdav
41
Ce qui n'est pas mentionné dans la documentation ou ici, mais le code censé lever une exception doit être appelé aprèsexpectException() . Bien que cela ait pu être évident pour certains, c'était un piège pour moi.
Jason McCreary
7
Ce n'est pas évident d'après la doc, mais aucun code après votre fonction qui lève une exception ne sera exécuté. Donc, si vous souhaitez tester plusieurs exceptions dans le même scénario de test, vous ne pouvez pas.
laurent
122
Vous pouvez également utiliser une annotation docblock jusqu'à la sortie de PHPUnit 9:
@LeviMorrison - À mon humble avis, le message d' exception ne doit pas être testé, de la même manière que les messages de journal. Les deux sont considérés comme des informations inutiles et utiles lors de la criminalistique manuelle . Le point clé à tester est le type d'exception. Tout ce qui va au-delà lie trop étroitement la mise en œuvre. IncorrectPasswordExceptiondevrait être suffisant - que le message égal "Wrong password for [email protected]"est accessoire. Ajoutez à cela que vous voulez passer le moins de temps possible à écrire des tests, et vous commencez à voir à quel point les tests simples deviennent importants.
David Harkness
5
@DavidHarkness J'ai pensé que quelqu'un pourrait en parler. De même, je conviens que le test des messages en général est trop strict et serré. Cependant, c'est cette rigueur et cette liaison stricte qui peuvent (soulignées à dessein) être ce que l'on souhaite dans certaines situations, telles que l'application d'une spécification.
Levi Morrison
1
Je ne regarderais pas dans un doc-block pour comprendre ce qu'il attendait, mais je regarderais le code de test réel (quel que soit le type de test). C'est la norme pour tous les autres tests; Je ne vois pas de raisons valables pour que les exceptions soient (oh mon dieu) une exception à cette convention.
Kamafeather
3
La règle "ne pas tester le message" semble valide, sauf si vous testez une méthode qui lève le même type d'exception dans plusieurs parties du code, à la seule différence que l'ID d'erreur, qui est transmis dans le message. Votre système peut afficher un message à l'utilisateur en fonction du message d'exception (et non du type d'exception). Dans ce cas, le message que l'utilisateur voit importe, vous devez donc tester le message d'erreur.
Le code ci-dessous testera le message d'exception et le code d'exception.
Important: il échouera si l'exception attendue n'est pas levée également.
try{
$test->methodWhichWillThrowException();//if this method not throw exception it must be fail too.
$this->fail("Expected exception 1162011 not thrown");}catch(MySpecificException $e){//Not catching a generic Exception or the fail function is also catched
$this->assertEquals(1162011, $e->getCode());
$this->assertEquals("Exception Message", $e->getMessage());}
$this->fail()n'est pas censé être utilisé de cette façon, je ne pense pas, du moins pas actuellement (PHPUnit 3.6.11); elle fait elle-même exception. En utilisant votre exemple, si $this->fail("Expected exception not thrown")est appelé, le catchbloc est déclenché et $e->getMessage()est "Exception attendue non levée" .
ken
1
@ken vous avez probablement raison. L'appel à failappartient probablement après le bloc catch, pas à l'intérieur de l'essai.
Frank Farmer
1
Je dois voter contre parce que l'appel à failne devrait pas être dans le trybloc. Il déclenche en soi le catchbloc produisant de faux résultats.
Twifty
6
Je crois que la raison pour laquelle cela ne fonctionne pas bien est qu'une situation est due à la capture de toutes les exceptions catch(Exception $e). Cette méthode fonctionne très bien pour moi lorsque j'essaie d'attraper des exceptions spécifiques:try { throw new MySpecificException; $this->fail('MySpecificException not thrown'); } catch(MySpecificException $e){}
spyle
23
Vous pouvez utiliser l' extension assertException pour affirmer plusieurs exceptions au cours d'une exécution de test.
Insérez la méthode dans votre TestCase et utilisez:
publicfunction testSomething(){
$test =function(){// some code that has to throw an exception};
$this->assertException( $test,'InvalidArgumentException',100,'expected message');}
J'ai aussi fait un trait pour les amateurs de joli code ..
Quelle PHPUnit utilisez-vous? J'utilise PHPUnit 4.7.5, et il assertExceptionn'est pas défini. Je ne le trouve pas non plus dans le manuel PHPUnit.
physique du
2
La asertExceptionméthode ne fait pas partie de PHPUnit d'origine. Vous devez hériter la PHPUnit_Framework_TestCaseclasse et ajouter manuellement la méthode liée dans le post ci-dessus . Vos cas de test hériteront alors de cette classe héritée.
PHPUnit expectException méthode est très gênante car elle permet de tester une seule exception par méthode de test.
J'ai créé cette fonction d'aide pour affirmer que certaines fonctions lèvent une exception:
/**
* Asserts that the given callback throws the given exception.
*
* @param string $expectClass The name of the expected exception class
* @param callable $callback A callback which should throw the exception
*/protectedfunction assertException(string $expectClass, callable $callback){try{
$callback();}catch(\Throwable $exception){
$this->assertInstanceOf($expectClass, $exception,'An invalid exception was thrown');return;}
$this->fail('No exception was thrown');}
Ajoutez-le à votre classe de test et appelez ainsi:
Certainement la meilleure solution parmi toutes les réponses! Jetez-le dans un trait et emballez-le!
domdambrogia
11
Solution complète
Les " meilleures pratiques " actuelles de PHPUnit pour les tests d'exception semblent .. terne ( docs ).
Comme je voulais plus que l' expectExceptionimplémentation actuelle , j'ai fait un trait à utiliser sur mes cas de test. Ce ne sont que ~ 50 lignes de code .
Prend en charge plusieurs exceptions par test
Prend en charge les assertions appelées après la levée de l'exception
Exemples d'utilisation robustes et clairs
assertSyntaxe standard
Prend en charge les assertions pour plus qu'un simple message, code et classe
Prend en charge l'assertion inverse, assertNotThrows
Prend en charge les Throwableerreurs PHP 7
Bibliothèque
J'ai publié le AssertThrowstrait sur Github et packagist pour qu'il puisse être installé avec composer.
Exemple simple
Juste pour illustrer l'esprit derrière la syntaxe:
<?php
// Using simple callback
$this->assertThrows(MyException::class,[$obj,'doSomethingBad']);// Using anonymous function
$this->assertThrows(MyException::class,function()use($obj){
$obj->doSomethingBad();});
Génial?
Exemple d'utilisation complète
Veuillez voir ci-dessous pour un exemple d'utilisation plus complet:
<?php
declare(strict_types=1);useJchook\AssertThrows\AssertThrows;usePHPUnit\Framework\TestCase;// These are just for illustrationuseMyNamespace\MyException;useMyNamespace\MyObject;finalclassMyTestextendsTestCase{useAssertThrows;// <--- adds the assertThrows methodpublicfunction testMyObject(){
$obj =newMyObject();// Test a basic exception is thrown
$this->assertThrows(MyException::class,function()use($obj){
$obj->doSomethingBad();});// Test custom aspects of a custom extension class
$this->assertThrows(MyException::class,function()use($obj){
$obj->doSomethingBad();},function($exception){
$this->assertEquals('Expected value', $exception->getCustomThing());
$this->assertEquals(123, $exception->getCode());});// Test that a specific exception is NOT thrown
$this->assertNotThrows(MyException::class,function()use($obj){
$obj->doSomethingGood();});}}?>
Un peu ironique que votre package pour les tests unitaires n'inclue pas de tests unitaires dans le référentiel.
domdambrogia
2
@domdambrogia grâce à @ jean-beguin il a désormais des tests unitaires.
jchook
8
publicfunction testException(){try{
$this->methodThatThrowsException();
$this->fail("Expected Exception has not been raised.");}catch(Exception $ex){
$this->assertEquals($ex->getMessage(),"Exception message");}}
La signature de assertEquals()est assertEquals(mixed $expected, mixed $actual...), inversée comme dans votre exemple, il devrait donc être$this->assertEquals("Exception message", $ex->getMessage());
Roger Campanera
7
Voici toutes les affirmations d'exception que vous pouvez faire. Notez que tous sont facultatifs .
classExceptionTestextendsPHPUnit_Framework_TestCase{publicfunction testException(){// make your exception assertions
$this->expectException(InvalidArgumentException::class);// if you use namespaces:// $this->expectException('\Namespace\MyException');
$this->expectExceptionMessage('message');
$this->expectExceptionMessageRegExp('/essage$/');
$this->expectExceptionCode(123);// code that throws an exceptionthrownewInvalidArgumentException('message',123);}publicfunction testAnotherException(){// repeat as needed
$this->expectException(Exception::class);thrownewException('Oh no!');}}
C'est incorrect car PHP s'arrête à la première exception levée. PHPUnit vérifie que l'exception levée a le bon type et dit «le test est OK», il ne connaît même pas la seconde exception.
Finesse
3
/**
* @expectedException Exception
* @expectedExceptionMessage Amount has to be bigger then 0!
*/publicfunction testDepositNegative(){
$this->account->deposit(-7);}
Faites très attention "/**", notez le double "*". Écrire seulement "**" (astérisque) échouera votre code. Assurez-vous également que vous utilisez la dernière version de phpUnit. Dans certaines versions antérieures de phpunit, l'exception @expectedException n'est pas prise en charge. J'avais 4.0 et cela ne fonctionnait pas pour moi, j'ai dû mettre à jour vers 5.5 https://coderwall.com/p/mklvdw/install-phpunit-with-composer pour mettre à jour avec composer.
Pour PHPUnit 5.7.27 et PHP 5.6 et pour tester plusieurs exceptions dans un seul test, il était important de forcer le test d'exception. L'utilisation de la gestion des exceptions seule pour affirmer l'instance d'Exception sautera le test de la situation si aucune exception ne se produit.
function yourfunction($a,$z){if($a<$z){thrownew<YOUR_EXCEPTION>;}}
voici le test
classFunctionTestextends \PHPUnit_Framework_TestCase{publicfunction testException(){
$this->setExpectedException(<YOUR_EXCEPTION>::class);
yourfunction(1,2);//add vars that cause the exception }}
PhpUnit est une bibliothèque incroyable, mais ce point spécifique est un peu frustrant. C'est pourquoi nous pouvons utiliser la bibliothèque open source turbotesting-php qui a une méthode d'assertion très pratique pour nous aider à tester les exceptions. On le trouve ici:
Et pour l'utiliser, nous ferions simplement ce qui suit:
AssertUtils::throwsException(function(){// Some code that must throw an exception here},'/expected error message/');
Si le code que nous tapons à l'intérieur de la fonction anonyme ne lève pas d'exception, une exception sera levée.
Si le code que nous tapons à l'intérieur de la fonction anonyme lève une exception, mais que son message ne correspond pas à l'expression rationnelle attendue, une exception sera également levée.
Réponses:
expectException () documentation PHPUnit
L'article de l'auteur PHPUnit fournit des explications détaillées sur les meilleures pratiques de test des exceptions.
la source
$this->setExpectedException('\My\Name\Space\MyCustomException');
setExpectedException
méthode est déconseillée, remplacée par celle-expectException
là. :)expectException()
. Bien que cela ait pu être évident pour certains, c'était un piège pour moi.Vous pouvez également utiliser une annotation docblock jusqu'à la sortie de PHPUnit 9:
Pour PHP 5.5+ (en particulier avec le code avec espace de noms), je préfère maintenant utiliser
::class
la source
IncorrectPasswordException
devrait être suffisant - que le message égal"Wrong password for [email protected]"
est accessoire. Ajoutez à cela que vous voulez passer le moins de temps possible à écrire des tests, et vous commencez à voir à quel point les tests simples deviennent importants.Si vous utilisez PHP 5.5+, vous pouvez utiliser la
::class
résolution pour obtenir le nom de la classe avecexpectException
/setExpectedException
. Cela offre plusieurs avantages:string
donc il fonctionnera avec n'importe quelle version de PHPUnit.Exemple:
PHP compile
dans
sans PHPUnit étant le plus sage.
la source
Le code ci-dessous testera le message d'exception et le code d'exception.
Important: il échouera si l'exception attendue n'est pas levée également.
la source
$this->fail()
n'est pas censé être utilisé de cette façon, je ne pense pas, du moins pas actuellement (PHPUnit 3.6.11); elle fait elle-même exception. En utilisant votre exemple, si$this->fail("Expected exception not thrown")
est appelé, lecatch
bloc est déclenché et$e->getMessage()
est "Exception attendue non levée" .fail
appartient probablement après le bloc catch, pas à l'intérieur de l'essai.fail
ne devrait pas être dans letry
bloc. Il déclenche en soi lecatch
bloc produisant de faux résultats.catch(Exception $e)
. Cette méthode fonctionne très bien pour moi lorsque j'essaie d'attraper des exceptions spécifiques:try { throw new MySpecificException; $this->fail('MySpecificException not thrown'); } catch(MySpecificException $e){}
Vous pouvez utiliser l' extension assertException pour affirmer plusieurs exceptions au cours d'une exécution de test.
Insérez la méthode dans votre TestCase et utilisez:
J'ai aussi fait un trait pour les amateurs de joli code ..
la source
assertException
n'est pas défini. Je ne le trouve pas non plus dans le manuel PHPUnit.asertException
méthode ne fait pas partie de PHPUnit d'origine. Vous devez hériter laPHPUnit_Framework_TestCase
classe et ajouter manuellement la méthode liée dans le post ci-dessus . Vos cas de test hériteront alors de cette classe héritée.Une autre manière peut être la suivante:
Veuillez vous assurer que votre classe de test s'étend
\PHPUnit_Framework_TestCase
.la source
PHPUnit
expectException
méthode est très gênante car elle permet de tester une seule exception par méthode de test.J'ai créé cette fonction d'aide pour affirmer que certaines fonctions lèvent une exception:
Ajoutez-le à votre classe de test et appelez ainsi:
la source
Solution complète
Les " meilleures pratiques " actuelles de PHPUnit pour les tests d'exception semblent .. terne ( docs ).
Comme je voulais plus que l'
expectException
implémentation actuelle , j'ai fait un trait à utiliser sur mes cas de test. Ce ne sont que ~ 50 lignes de code .assert
Syntaxe standardassertNotThrows
Throwable
erreurs PHP 7Bibliothèque
J'ai publié le
AssertThrows
trait sur Github et packagist pour qu'il puisse être installé avec composer.Exemple simple
Juste pour illustrer l'esprit derrière la syntaxe:
Génial?
Exemple d'utilisation complète
Veuillez voir ci-dessous pour un exemple d'utilisation plus complet:
la source
la source
assertEquals()
estassertEquals(mixed $expected, mixed $actual...)
, inversée comme dans votre exemple, il devrait donc être$this->assertEquals("Exception message", $ex->getMessage());
Voici toutes les affirmations d'exception que vous pouvez faire. Notez que tous sont facultatifs .
La documentation peut être trouvée ici .
la source
Faites très attention
"/**"
, notez le double "*". Écrire seulement "**" (astérisque) échouera votre code. Assurez-vous également que vous utilisez la dernière version de phpUnit. Dans certaines versions antérieures de phpunit, l'exception @expectedException n'est pas prise en charge. J'avais 4.0 et cela ne fonctionnait pas pour moi, j'ai dû mettre à jour vers 5.5 https://coderwall.com/p/mklvdw/install-phpunit-with-composer pour mettre à jour avec composer.la source
Pour PHPUnit 5.7.27 et PHP 5.6 et pour tester plusieurs exceptions dans un seul test, il était important de forcer le test d'exception. L'utilisation de la gestion des exceptions seule pour affirmer l'instance d'Exception sautera le test de la situation si aucune exception ne se produit.
la source
voici le test
la source
PhpUnit est une bibliothèque incroyable, mais ce point spécifique est un peu frustrant. C'est pourquoi nous pouvons utiliser la bibliothèque open source turbotesting-php qui a une méthode d'assertion très pratique pour nous aider à tester les exceptions. On le trouve ici:
https://github.com/edertone/TurboTesting/blob/master/TurboTesting-Php/src/main/php/utils/AssertUtils.php
Et pour l'utiliser, nous ferions simplement ce qui suit:
Si le code que nous tapons à l'intérieur de la fonction anonyme ne lève pas d'exception, une exception sera levée.
Si le code que nous tapons à l'intérieur de la fonction anonyme lève une exception, mais que son message ne correspond pas à l'expression rationnelle attendue, une exception sera également levée.
la source