¿Cómo escribo pruebas unitarias en PHP? [cerrado]


97

He leído en todas partes sobre lo geniales que son, pero por alguna razón parece que no puedo entender exactamente cómo se supone que debo probar algo. ¿Podría alguien publicar un fragmento de código de ejemplo y cómo lo probaría? Si no es mucho problema :)


5
Para equilibrar, no hay 2 o 3 marcos de prueba unitarios para PHP; hay una lista aquí: en.wikipedia.org/wiki/List_of_unit_testing_frameworks#PHP
Fenton

Respuestas:


36

Hay un tercer "marco", que es mucho más fácil de aprender, incluso más fácil que Simple Test, se llama phpt.

Puede encontrar una cartilla aquí: http://qa.php.net/write-test.php

Editar: Acabo de ver su solicitud de código de muestra.

Supongamos que tiene la siguiente función en un archivo llamado lib.php :

<?php
function foo($bar)
{
  return $bar;
}
?>

Realmente simple y directo, se devuelve el parámetro que ingresa. Así que veamos una prueba para esta función, llamaremos al archivo de prueba foo.phpt :

--TEST--
foo() function - A basic test to see if it works. :)
--FILE--
<?php
include 'lib.php'; // might need to adjust path if not in the same dir
$bar = 'Hello World';
var_dump(foo($bar));
?>
--EXPECT--
string(11) "Hello World"

En pocas palabras, proporcionamos el parámetro $barcon valor "Hello World"y var_dump()la respuesta de la función llamada a foo().

Para ejecutar esta prueba, use: pear run-test path/to/foo.phpt

Esto requiere una instalación funcional de PEAR en su sistema, lo cual es bastante común en la mayoría de las circunstancias. Si necesita instalarlo, le recomiendo instalar la última versión disponible. En caso de que necesite ayuda para configurarlo, no dude en preguntar (pero proporcione el sistema operativo, etc.).


¿No debería serlo run-tests?
Dharman

30

Hay dos marcos que puede utilizar para las pruebas unitarias. Simpletest y PHPUnit , que prefiero. Lea los tutoriales sobre cómo escribir y ejecutar pruebas en la página de inicio de PHPUnit. Es bastante fácil y está bien descrito.


21

Puede hacer que las pruebas unitarias sean más efectivas cambiando su estilo de codificación para adaptarlo.

Recomiendo navegar por el Blog de pruebas de Google , en particular la publicación sobre cómo escribir código comprobable .


7
Creo que mencionaste una gran publicación. Empezar su respuesta con 'Las pruebas unitarias no son muy efectivas' casi me hizo rechazar, sin embargo, siendo un experto en pruebas ... Posiblemente, reformular de una manera positiva alentaría a las personas a leer el artículo.
xtofl

2
@xtofl lo editó para aumentar ligeramente la 'positividad' :)
icc97

13

Hice la mía porque no tuve tiempo de aprender la forma de hacer las cosas de otra persona, esto me tomó unos 20 minutos para escribir, 10 para adaptarlo para publicarlo aquí.

Unittesting es muy útil para mí.

esto es un poco largo pero se explica solo y hay un ejemplo en la parte inferior.

/**
 * Provides Assertions
 **/
class Assert
{
    public static function AreEqual( $a, $b )
    {
        if ( $a != $b )
        {
            throw new Exception( 'Subjects are not equal.' );
        }
    }
}

/**
 * Provides a loggable entity with information on a test and how it executed
 **/
class TestResult
{
    protected $_testableInstance = null;

    protected $_isSuccess = false;
    public function getSuccess()
    {
        return $this->_isSuccess;
    }

    protected $_output = '';
    public function getOutput()
    {
        return $_output;
    }
    public function setOutput( $value )
    {
        $_output = $value;
    }

    protected $_test = null;
    public function getTest()
    {
        return $this->_test;
    }

    public function getName()
    {
        return $this->_test->getName();
    }
    public function getComment()
    {
        return $this->ParseComment( $this->_test->getDocComment() );
    }

    private function ParseComment( $comment )
    {
        $lines = explode( "\n", $comment );
        for( $i = 0; $i < count( $lines ); $i ++ )
        {
            $lines[$i] = trim( $lines[ $i ] );
        }
        return implode( "\n", $lines );
    }

    protected $_exception = null;
    public function getException()
    {
        return $this->_exception;
    }

    static public function CreateFailure( Testable $object, ReflectionMethod $test, Exception $exception )
    {
        $result = new self();
        $result->_isSuccess = false;
        $result->testableInstance = $object;
        $result->_test = $test;
        $result->_exception = $exception;

        return $result;
    }
    static public function CreateSuccess( Testable $object, ReflectionMethod $test )
    {
        $result = new self();
        $result->_isSuccess = true;
        $result->testableInstance = $object;
        $result->_test = $test;

        return $result;
    }
}

/**
 * Provides a base class to derive tests from
 **/
abstract class Testable
{
    protected $test_log = array();

    /**
     * Logs the result of a test. keeps track of results for later inspection, Overridable to log elsewhere.
     **/
    protected function Log( TestResult $result )
    {
        $this->test_log[] = $result;

        printf( "Test: %s was a %s %s\n"
            ,$result->getName()
            ,$result->getSuccess() ? 'success' : 'failure'
            ,$result->getSuccess() ? '' : sprintf( "\n%s (lines:%d-%d; file:%s)"
                ,$result->getComment()
                ,$result->getTest()->getStartLine()
                ,$result->getTest()->getEndLine()
                ,$result->getTest()->getFileName()
                )
            );

    }
    final public function RunTests()
    {
        $class = new ReflectionClass( $this );
        foreach( $class->GetMethods() as $method )
        {
            $methodname = $method->getName();
            if ( strlen( $methodname ) > 4 && substr( $methodname, 0, 4 ) == 'Test' )
            {
                ob_start();
                try
                {
                    $this->$methodname();
                    $result = TestResult::CreateSuccess( $this, $method );
                }
                catch( Exception $ex )
                {
                    $result = TestResult::CreateFailure( $this, $method, $ex );
                }
                $output = ob_get_clean();
                $result->setOutput( $output );
                $this->Log( $result );
            }
        }
    }
}

/**
 * a simple Test suite with two tests
 **/
class MyTest extends Testable
{
    /**
     * This test is designed to fail
     **/
    public function TestOne()
    {
        Assert::AreEqual( 1, 2 );
    }

    /**
     * This test is designed to succeed
     **/
    public function TestTwo()
    {
        Assert::AreEqual( 1, 1 );
    }
}

// this is how to use it.
$test = new MyTest();
$test->RunTests();

Esto produce:

Prueba: TestOne fue un fracaso 
/ **
* Esta prueba está diseñada para fallar
** / (líneas: 149-152; archivo: /Users/kris/Desktop/Testable.php)
Prueba: TestTwo fue un éxito 

7

Obtenga PHPUnit. Es muy fácil de usar.

Luego comience con afirmaciones muy simples. Puede hacer mucho con AssertEquals antes de comenzar con cualquier otra cosa. Esa es una buena forma de mojarse los pies.

También puede intentar escribir su prueba primero (ya que le dio a su pregunta la etiqueta TDD) y luego escribir su código. Si no lo ha hecho antes, es una revelación.

require_once 'ClassYouWantToTest';
require_once 'PHPUnit...blah,blah,whatever';

class ClassYouWantToTest extends PHPUnit...blah,blah,whatever
{
    private $ClassYouWantToTest;

   protected function setUp ()
    {
        parent::setUp();
        $this->ClassYouWantToTest = new ClassYouWantToTest(/* parameters */);
    }

    protected function tearDown ()
    {
        $this->ClassYouWantToTest = null;
        parent::tearDown();
    }

    public function __construct ()
    {   
        // not really needed
    }

    /**
     * Tests ClassYouWantToTest->methodFoo()
     */
    public function testMethodFoo ()
    {
        $this->assertEquals(
            $this->ClassYouWantToTest->methodFoo('putValueOfParamHere), 'expectedOutputHere);

    /**
     * Tests ClassYouWantToTest->methodBar()
     */
    public function testMethodFoo ()
    {
        $this->assertEquals(
            $this->ClassYouWantToTest->methodBar('putValueOfParamHere), 'expectedOutputHere);
}

5

Para pruebas Y documentación simples, php-doctest es bastante bueno y es una manera realmente fácil de comenzar, ya que no tiene que abrir un archivo separado. Imagina la siguiente función:

/**
* Sums 2 numbers
* <code>
* //doctest: add
* echo add(5,2);
* //expects:
* 7
* </code>
*/
function add($a,$b){
    return $a + $b;   
}

Si ahora ejecuta este archivo a través de phpdt (corredor de línea de comandos de php-doctest) se ejecutará 1 prueba. El doctest está contenido dentro del bloque <código>. Doctest se originó en Python y está bien para dar ejemplos útiles y ejecutables sobre cómo se supone que funciona el código. No puede usarlo exclusivamente porque el código en sí se llenará de casos de prueba, pero descubrí que es útil junto con una biblioteca tdd más formal: uso phpunit.

Esta primera respuesta aquí lo resume muy bien (no es unit vs doctest).


1
¿No hace que la fuente sea un poco desordenada?
Ali Ghanavatian

puede. solo debe usarse para pruebas simples simples. también funciona como documentación. si necesita más prueba de unidad de uso.
Sofía

2

phpunit es prácticamente el marco de prueba unitario de facto para php. también hay DocTest (disponible como paquete PEAR) y algunos otros. php en sí mismo se prueba para regresiones y similares mediante pruebas phpt que también se pueden ejecutar a través de pear.


2

Las pruebas de codecepción son muy parecidas a las pruebas unitarias comunes, pero son mucho más poderosas en las cosas en las que necesita burlarse y tropezarse.

Aquí está la prueba del controlador de muestra. Observe la facilidad con la que se crean los apéndices. Con qué facilidad verifica que se haya invocado el método.

<?php
use Codeception\Util\Stub as Stub;

const VALID_USER_ID = 1;
const INVALID_USER_ID = 0;

class UserControllerCest {
public $class = 'UserController';


public function show(CodeGuy $I) {
    // prepare environment
    $I->haveFakeClass($controller = Stub::makeEmptyExcept($this->class, 'show'));
    $I->haveFakeClass($db = Stub::make('DbConnector', array('find' => function($id) { return $id == VALID_USER_ID ? new User() : null ))); };
    $I->setProperty($controller, 'db', $db);

    $I->executeTestedMethodOn($controller, VALID_USER_ID)
        ->seeResultEquals(true)
        ->seeMethodInvoked($controller, 'render');

    $I->expect('it will render 404 page for non existent user')
        ->executeTestedMethodOn($controller, INVALID_USER_ID)
        ->seeResultNotEquals(true)
        ->seeMethodInvoked($controller, 'render404','User not found')
        ->seeMethodNotInvoked($controller, 'render');
}
}

También hay otras cosas interesantes. Puede probar el estado de la base de datos, el sistema de archivos, etc.


1

Además de las excelentes sugerencias sobre marcos de prueba que ya se han dado, ¿está construyendo su aplicación con uno de los marcos web PHP que tiene pruebas automatizadas integradas, como Symfony o CakePHP ? A veces, tener un lugar para dejar caer sus métodos de prueba reduce la fricción inicial que algunas personas asocian con las pruebas automatizadas y TDD.


1

Demasiado para volver a publicar aquí, pero aquí hay un gran artículo sobre el uso de phpt . Cubre una serie de aspectos relacionados con phpt que a menudo se pasan por alto, por lo que podría valer la pena leerlo para ampliar su conocimiento de php más allá de simplemente escribir una prueba. Afortunadamente, el artículo también trata sobre pruebas de escritura.

Los principales puntos de discusión

  1. Descubra cómo funcionan los aspectos marginalmente documentados de PHP (o prácticamente cualquier parte)
  2. Escriba pruebas unitarias simples para su propio código PHP
  3. Escriba pruebas como parte de una extensión o para transmitir un error potencial a los internos o grupos de control de calidad

1

Sé que ya hay mucha información aquí, pero dado que esto todavía aparece en las búsquedas de Google, también podría agregar Chinook Test Suite a la lista. Es un marco de prueba simple y pequeño.

Puede probar fácilmente sus clases con él y también crear objetos simulados. Ejecuta las pruebas a través de un navegador web y (todavía no) a través de una consola. En el navegador puede especificar qué clase de prueba o incluso qué método de prueba ejecutar. O simplemente puede ejecutar todas las pruebas.

Una captura de pantalla de la página de github:

Marco de prueba de unidad Chinook

Lo que me gusta de él es la forma en que afirma las pruebas. Esto se hace con las llamadas "afirmaciones fluidas". Ejemplo:

$this->Assert($datetime)->Should()->BeAfter($someDatetime);

Y crear objetos simulados también es muy sencillo (con una sintaxis fluida como):

$mock = new CFMock::Create(new DummyClass());
$mock->ACallTo('SomeMethod')->Returns('some value');

De todos modos, también se puede encontrar más información en la página de github con un ejemplo de código:

https://github.com/w00/Chinook-TestSuite

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.