tutorial example composer php exception phpunit

example - phpunit laravel



PHPUnit no continúa la prueba después de esperar una excepción (6)

Descubrí que la forma más fácil de continuar la prueba después de una excepción era implementar el bloque try / finally dentro de la prueba. Básicamente, esto permite que la ejecución de la prueba continúe independientemente de cualquier excepción que se haya lanzado.

Esta fue mi implementación:

$this->expectException(InvalidOperationException::class); try { $report = $service->executeReport($reportId, $jobId); } finally { $this->assertEquals($report->getStatus(), StatusMapper::STATUS_ABORTED); }

¿Por qué PHPUnit no hace la última afirmación de excepción en este código?

public function testConfigOverriding() { $this->dependencyContainer = new DependencyContainer(__DIR__ . "/../../Resources/valid_json.json"); $this->assertEquals(''overriden'', $this->dependencyContainer->getConfig(''shell_commander'')[''pygmentize_command'']); $unexisting = "unexisting_file"; $this->setExpectedException(''Exception'', "Configuration file at path /"$unexisting/" doesn''t exist."); $this->dependencyContainer = new DependencyContainer($unexisting); $invalid = __DIR . "/../../Resources/invalid_json.json"; $this->setExpectedException(''Exception'', "Configuration JSON file provided is not valid."); $this->dependencyContainer = new DependencyContainer($invalid); }

Básicamente, comprueba si se lanzó la excepción "unexsisting_file", pero ignora por completo la prueba "invalid json". ¿Necesito hacer pruebas separadas para cada excepción lanzada?


Incluso con setExpectedException , su prueba sigue siendo un código PHP normal y sigue las reglas normales de PHP. Si se lanza una excepción, el flujo del programa salta inmediatamente del contexto actual hasta que alcanza un bloque try / catch .

En PHPUnit, cuando usa setExpectedException , le dice al núcleo de PHPUnit que debe esperar una excepción del código que está a punto de ejecutarse. Por lo tanto, lo espera con un bloque try / catch y pasa la prueba si se llama a catch con el tipo de excepción que está esperando.

Sin embargo, dentro de su método de prueba, las reglas normales de PHP aún se aplican: cuando ocurre la excepción, ese es el final del bloque de código actual. No se ejecutará nada más en ese método, a menos que tenga su propio bloque try / catch dentro del método de prueba.

Por lo tanto, para probar múltiples excepciones, tiene algunas opciones:

  1. Agregue su propio try / catch al método de prueba, para que pueda continuar con otras pruebas dentro de ese método después de la primera excepción.

  2. Divida las pruebas en métodos separados, de modo que cada excepción esté en su propia prueba.

  3. Este ejemplo en particular parece un buen caso para usar el mecanismo del dataProvider de datos de PHPUnit, porque básicamente está probando la misma funcionalidad con dos conjuntos de datos. La función dataProvider permite definir una función separada que contiene una matriz de datos de entrada para cada conjunto de valores que desea probar. Estos valores se pasan un conjunto a la vez en el método de prueba. Su código se vería algo así:

    /** * @dataProvider providerConfigOverriding */ public function testConfigOverriding($filename, $expectedExceptionText) { $this->dependencyContainer = new DependencyContainer(__DIR__ . "/../../Resources/valid_json.json"); $this->assertEquals(''overriden'', $this->dependencyContainer->getConfig(''shell_commander'')[''pygmentize_command'']); $this->setExpectedException(''Exception'', $expectedExceptionText); $this->dependencyContainer = new DependencyContainer($filename); } public function providerConfigOverriding() { return array( array(''unexisting_file'', ''Configuration file at path "unexisting_file" doesn/'t exist.''), array(__DIR__ . "/../../Resources/invalid_json.json", "Configuration JSON file provided is not valid."), ); }

Espero que ayude.


Para cualquiera que busque hacer lo que está en el título de la pregunta, esto es lo más limpio que se me ha ocurrido.

$exception_thrown = false try { ... stuff that should throw exception ... } catch (SomeTypeOfException $e) { $exception_thrown = true; } $this->assertSame(true, $exception_thrown);


Primero, hay un error tipográfico. Reemplazar

__DIR

con

__DIR__

:)

Gracias al comentario de @ SDC, me di cuenta de que, de hecho, necesitará métodos de prueba avanzados para cada excepción (si está utilizando la función expectedException de PHPUnit). La tercera afirmación de su código simplemente no se está ejecutando. Si necesita probar la Excepción múltiple en un método de prueba, le recomendaría que escriba sus propias declaraciones try en el método de prueba.

Gracias de nuevo @SDC


Si necesita realizar aserciones adicionales después de que se haya lanzado una excepción, simplemente use esta plantilla:

//You can use annotations instead of this method $this->expectException(FooException::class); try { $testable->setFoo($bar); } catch (FooException $exception) { //Asserting that $testable->foo stays unchanged $this->assertEquals($foo, $testable->getFoo()); //re-throwing exception throw $exception; }


Sobre la base de la respuesta de @ SDC, recomiendo lo siguiente

  • dividir las pruebas aún más
  • Evite utilizar propiedades de instancia para hacer referencia al sistema bajo prueba.

División de las pruebas más lejos

Hay un problema con varias aserciones en una sola prueba si las aseveraciones no están relacionadas con el mismo comportamiento: no puede nombrar la prueba correctamente, incluso podría terminar usando and dentro del nombre del método de prueba. Si eso sucede, divida las pruebas en pruebas separadas.

Evite usar propiedades de instancia para el SUT

Cuando comencé a escribir pruebas, sentí que había una oportunidad de reducir la duplicación de código al organizar el sistema bajo prueba (SUT) en setUp , y luego hacer referencia al SUT a través de las propiedades de la instancia correspondiente en las pruebas individuales.

Esto es tentador, pero después de un tiempo, cuando comience a extraer colaboradores del SUT, tendrá la necesidad de configurar pruebas dobles. Al principio, esto podría seguir funcionando para usted, pero luego comenzará a configurar las pruebas de duplicación de manera diferente en las diferentes pruebas, y toda la duplicación que tenía como objetivo evitar antes le regresará a usted: terminará configurando ambas pruebas de duplicación, y arreglando el SUT en tu prueba otra vez.

Cuando encuentro esto en revisiones de código, me gusta hacer referencia

y recomiendo leerlo.

El punto importante es que desea facilitar la escritura y el mantenimiento de las pruebas. Mantener las pruebas (o cualquier código, si lo desea) significa en su mayoría hacer que el código sea fácil de leer. Si lees un poco de código, digamos, un método de clase, quieres entender fácilmente de qué se trata, e idealmente, el método debería hacer lo que esperas que haga desde su nombre de clase. Si está probando diferentes comportamientos, hágalo obvio creando diferentes métodos de prueba.

Esto también tiene la ventaja de que si ejecuta sus pruebas con

$ phpunit --testdox

terminas con una buena lista de comportamientos esperados, ver

Ejemplo basado en su pregunta

Nota Los comentarios que proporciono en este ejemplo son solo para ilustrar la idea de dividir más las pruebas, en el código real que no las tendría.

/** * The name of this method suggests a behaviour we expect from the * constructor of DependencyContainer */ public function testCanOverrideShellCommanderConfiguration() { $container = new DependencyContainer(__DIR__ . ''/../../Resources/valid_json.json''); $this->assertEquals( ''overriden'', $container->getConfig(''shell_commander'')[''pygmentize_command''] ); } /** * While the idea of using a data provider is good, splitting the test * further makes sense for the following reasons * * - running tests with --testdox option as lined out above * - modifying the behaviour independently * Currently, a generic Exception is thrown, but you might * consider using a more specific exception from the SPL library, * (see http://php.net/manual/en/spl.exceptions.php), * or creating your own NonExistentConfigurationException class, * and then a data provider might not make sense anymore) */ public function testConstructorRejectsNonExistentConfigurationFile() { $path = ''unexisting_file''; $this->setExpectedException(/Exception::class, sprintf( ''Configuration file at path "%s" doesn/'t exist.'', $path )); new DependencyContainer($path); } public function testConstructorRejectsInvalidConfigurationFile() { $path = __DIR__ . ''/../../Resources/invalid_json.json''; $this->setExpectedException( /Exception::class, ''Configuration JSON file provided is not valid.'' ); new DependencyContainer($path); }

Nota también recomendaría echar un vistazo a