PHP
Examen de la unidad
Buscar..
Sintaxis
- Lista completa de afirmaciones . Ejemplos:
-
assertTrue(bool $condition[, string $messageIfFalse = '']);
-
assertEquals(mixed $expected, mixed $actual[, string $messageIfNotEqual = '']);
Observaciones
Unit
pruebas Unit
se utilizan para probar el código fuente para ver si contiene acuerdos con entradas como esperamos. Unit
pruebas Unit
son soportadas por la mayoría de los marcos. Hay varias pruebas diferentes de PHPUnit y pueden diferir en la sintaxis. En este ejemplo estamos usando PHPUnit
.
Pruebas de reglas de clase
Digamos, tenemos un LoginForm
simple de clase LoginForm
con reglas () (utilizado en la página de inicio de sesión como plantilla de marco):
class LoginForm {
public $email;
public $rememberMe;
public $password;
/* rules() method returns an array with what each field has as a requirement.
* Login form uses email and password to authenticate user.
*/
public function rules() {
return [
// Email and Password are both required
[['email', 'password'], 'required'],
// Email must be in email format
['email', 'email'],
// rememberMe must be a boolean value
['rememberMe', 'boolean'],
// Password must match this pattern (must contain only letters and numbers)
['password', 'match', 'pattern' => '/^[a-z0-9]+$/i'],
];
}
/** the validate function checks for correctness of the passed rules */
public function validate($rule) {
$success = true;
list($var, $type) = $rule;
foreach ((array) $var as $var) {
switch ($type) {
case "required":
$success = $success && $this->$var != "";
break;
case "email":
$success = $success && filter_var($this->$var, FILTER_VALIDATE_EMAIL);
break;
case "boolean":
$success = $success && filter_var($this->$var, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) !== null;
break;
case "match":
$success = $success && preg_match($rule["pattern"], $this->$var);
break;
default:
throw new \InvalidArgumentException("Invalid filter type passed")
}
}
return $success;
}
}
Para realizar pruebas en esta clase, usamos pruebas de unidad (verificando el código fuente para ver si se ajusta a nuestras expectativas):
class LoginFormTest extends TestCase {
protected $loginForm;
// Executing code on the start of the test
public function setUp() {
$this->loginForm = new LoginForm;
}
// To validate our rules, we should use the validate() method
/**
* This method belongs to Unit test class LoginFormTest and
* it's testing rules that are described above.
*/
public function testRuleValidation() {
$rules = $this->loginForm->rules();
// Initialize to valid and test this
$this->loginForm->email = "[email protected]";
$this->loginForm->password = "password";
$this->loginForm->rememberMe = true;
$this->assertTrue($this->loginForm->validate($rules), "Should be valid as nothing is invalid");
// Test email validation
// Since we made email to be in email format, it cannot be empty
$this->loginForm->email = '';
$this->assertFalse($this->loginForm->validate($rules), "Email should not be valid (empty)");
// It does not contain "@" in string so it's invalid
$this->loginForm->email = 'invalid.email.com';
$this->assertFalse($this->loginForm->validate($rules), "Email should not be valid (invalid format)");
// Revert email to valid for next test
$this->loginForm->email = '[email protected]';
// Test password validation
// Password cannot be empty (since it's required)
$this->loginForm->password = '';
$this->assertFalse($this->loginForm->validate($rules), "Password should not be valid (empty)");
// Revert password to valid for next test
$this->loginForm->password = 'ThisIsMyPassword';
// Test rememberMe validation
$this->loginForm->rememberMe = 999;
$this->assertFalse($this->loginForm->validate($rules), "RememberMe should not be valid (integer type)");
// Revert remeberMe to valid for next test
$this->loginForm->rememberMe = true;
}
}
¿Cómo pueden ayudar exactamente las pruebas Unit
(excluyendo ejemplos generales) aquí? Por ejemplo, encaja muy bien cuando obtenemos resultados inesperados. Por ejemplo, tomemos esta regla de antes:
['password', 'match', 'pattern' => '/^[a-z0-9]+$/i'],
En cambio, si nos perdimos una cosa importante y escribimos esto:
['password', 'match', 'pattern' => '/^[a-z0-9]$/i'],
Con docenas de reglas diferentes (asumiendo que estamos usando no solo correo electrónico y contraseña), es difícil detectar errores. Esta prueba unitaria:
// Initialize to valid and test this
$this->loginForm->email = "[email protected]";
$this->loginForm->password = "password";
$this->loginForm->rememberMe = true;
$this->assertTrue($this->loginForm->validate($rules), "Should be valid as nothing is invalid");
Pasará nuestro primer ejemplo pero no el segundo . ¿Por qué? Porque en el segundo ejemplo escribimos un patrón con un error tipográfico (signo +
perdido), lo que significa que solo acepta una letra / número.
Las pruebas unitarias se pueden ejecutar en la consola con el comando: phpunit [path_to_file]
. Si todo está bien, deberíamos poder ver que todas las pruebas están en estado OK
, de lo contrario, veremos Error
(errores de sintaxis) o Fail
(al menos una línea en ese método no se aprobó).
Con parámetros adicionales como --coverage
también podemos ver visualmente cuántas líneas en el código de back-end se probaron y cuáles pasaron / fallaron. Esto se aplica a cualquier marco que haya instalado PHPUnit .
Ejemplo de cómo se ve la prueba PHPUnit
en la consola (apariencia general, no de acuerdo con este ejemplo):
PHPUnit Data Providers
Los métodos de prueba a menudo necesitan datos para ser probados. Para probar algunos métodos completamente, debe proporcionar diferentes conjuntos de datos para cada posible condición de prueba. Por supuesto, puedes hacerlo manualmente usando bucles, como este:
...
public function testSomething()
{
$data = [...];
foreach($data as $dataSet) {
$this->assertSomething($dataSet);
}
}
...
Y alguien puede encontrarlo conveniente. Pero hay algunos inconvenientes de este enfoque. Primero, tendrá que realizar acciones adicionales para extraer datos si su función de prueba acepta varios parámetros. En segundo lugar, en caso de error, sería difícil distinguir el conjunto de datos con errores sin mensajes adicionales y depuración. En tercer lugar, PHPUnit proporciona una forma automática de manejar los conjuntos de datos de prueba utilizando proveedores de datos .
El proveedor de datos es una función que debe devolver datos para su caso de prueba particular.
Un método de proveedor de datos debe ser público y devolver una matriz de matrices o un objeto que implementa la interfaz de iterador y produce una matriz para cada paso de iteración. Para cada matriz que forma parte de la colección, se llamará al método de prueba con el contenido de la matriz como sus argumentos.
Para usar un proveedor de datos con su prueba, use la anotación @dataProvider
con el nombre de la función del proveedor de datos especificada:
/**
* @dataProvider dataProviderForTest
*/
public function testEquals($a, $b)
{
$this->assertEquals($a, $b);
}
public function dataProviderForTest()
{
return [
[1,1],
[2,2],
[3,2] //this will fail
];
}
Array de matrices
Tenga en cuenta que
dataProviderForTest()
devuelve una matriz de matrices. Cada matriz anidada tiene dos elementos y llenarán los parámetros necesarios paratestEquals()
uno por uno. Se lanzará un error como este.Missing argument 2 for Test::testEquals()
si no hay suficientes elementos. PHPUnit pasará automáticamente por los datos y ejecutará pruebas:
public function dataProviderForTest()
{
return [
[1,1], // [0] testEquals($a = 1, $b = 1)
[2,2], // [1] testEquals($a = 2, $b = 2)
[3,2] // [2] There was 1 failure: 1) Test::testEquals with data set #2 (3, 4)
];
}
Cada conjunto de datos puede ser nombrado por conveniencia. Será más fácil detectar los datos que fallan:
public function dataProviderForTest()
{
return [
'Test 1' => [1,1], // [0] testEquals($a = 1, $b = 1)
'Test 2' => [2,2], // [1] testEquals($a = 2, $b = 2)
'Test 3' => [3,2] // [2] There was 1 failure:
// 1) Test::testEquals with data set "Test 3" (3, 4)
];
}
Iteradores
class MyIterator implements Iterator {
protected $array = [];
public function __construct($array) {
$this->array = $array;
}
function rewind() {
return reset($this->array);
}
function current() {
return current($this->array);
}
function key() {
return key($this->array);
}
function next() {
return next($this->array);
}
function valid() {
return key($this->array) !== null;
}
}
...
class Test extends TestCase
{
/**
* @dataProvider dataProviderForTest
*/
public function testEquals($a)
{
$toCompare = 0;
$this->assertEquals($a, $toCompare);
}
public function dataProviderForTest()
{
return new MyIterator([
'Test 1' => [0],
'Test 2' => [false],
'Test 3' => [null]
]);
}
}
Como puedes ver, el iterador simple también funciona.
Tenga en cuenta que incluso para un solo parámetro, el proveedor de datos debe devolver una matriz
[$parameter]
Porque si cambiamos nuestro método current()
(que en realidad devuelve datos en cada iteración) a esto:
function current() {
return current($this->array)[0];
}
O cambiar los datos reales:
return new MyIterator([
'Test 1' => 0,
'Test 2' => false,
'Test 3' => null
]);
Obtendremos un error:
There was 1 warning:
1) Warning
The data provider specified for Test::testEquals is invalid.
Por supuesto, no es útil usar el objeto
Iterator
sobre una matriz simple. Debería implementar alguna lógica específica para su caso.
Generadores
No se indica ni se muestra explícitamente en el manual, pero también puede usar un generador como proveedor de datos. Tenga en cuenta que la clase Generator
realmente implementa la interfaz Iterator
.
Este es un ejemplo del uso de DirectoryIterator
combinado con el generator
:
/**
* @param string $file
*
* @dataProvider fileDataProvider
*/
public function testSomethingWithFiles($fileName)
{
//$fileName is available here
//do test here
}
public function fileDataProvider()
{
$directory = new DirectoryIterator('path-to-the-directory');
foreach ($directory as $file) {
if ($file->isFile() && $file->isReadable()) {
yield [$file->getPathname()]; // invoke generator here.
}
}
}
Tenga en cuenta el
yield
proveedor es una matriz. En su lugar, recibirá una advertencia de proveedor de datos no válido.
Excepciones de prueba
Digamos que quieres probar el método que lanza una excepción
class Car
{
/**
* @throws \Exception
*/
public function drive()
{
throw new \Exception('Useful message', 1);
}
}
Puede hacerlo encerrando la llamada al método en un bloque try / catch y haciendo afirmaciones sobre las propiedades del objeto de exección, pero más convenientemente puede usar métodos de afirmación de excepción. A partir de PHPUnit 5.2 , tiene métodos expectX () disponibles para confirmar el tipo de excepción, el mensaje y el código
class DriveTest extends PHPUnit_Framework_TestCase
{
public function testDrive()
{
// prepare
$car = new \Car();
$expectedClass = \Exception::class;
$expectedMessage = 'Useful message';
$expectedCode = 1;
// test
$this->expectException($expectedClass);
$this->expectMessage($expectedMessage);
$this->expectCode($expectedCode);
// invoke
$car->drive();
}
}
Si está utilizando una versión anterior de PHPUnit, el método setExpectedException puede usarse en lugar de los métodos expectX (), pero tenga en cuenta que está en desuso y se eliminará en la versión 6.
class DriveTest extends PHPUnit_Framework_TestCase
{
public function testDrive()
{
// prepare
$car = new \Car();
$expectedClass = \Exception::class;
$expectedMessage = 'Useful message';
$expectedCode = 1;
// test
$this->setExpectedException($expectedClass, $expectedMessage, $expectedCode);
// invoke
$car->drive();
}
}