Ricerca…


Sintassi

  • Elenco completo di asserzioni . Esempi:
  • assertTrue(bool $condition[, string $messageIfFalse = '']);
  • assertEquals(mixed $expected, mixed $actual[, string $messageIfNotEqual = '']);

Osservazioni

Unit test Unit sono usati per testare il codice sorgente per vedere se contiene accordi con gli input come ci aspettiamo. Unit test Unit sono supportati dalla maggior parte dei framework. Esistono diversi test PHPUnit diversi e potrebbero differire nella sintassi. In questo esempio usiamo PHPUnit .

Test delle regole di classe

Diciamo che abbiamo una semplice classe LoginForm con il metodo rules () (usato nella pagina di login come modello di framework):

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;
    }
}

Per eseguire test su questa classe, utilizziamo i test di unità (controllando il codice sorgente per vedere se soddisfa le nostre aspettative):

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;
    }
}

In che modo esattamente i test Unit possono aiutare (esclusi gli esempi generali) qui? Ad esempio, si adatta molto bene quando otteniamo risultati inaspettati. Ad esempio, prendiamo questa regola da prima:

['password', 'match', 'pattern' => '/^[a-z0-9]+$/i'],

Invece, se ci siamo persi una cosa importante e abbiamo scritto questo:

['password', 'match', 'pattern' => '/^[a-z0-9]$/i'],

Con dozzine di regole diverse (supponendo che stiamo usando non solo e-mail e password), è difficile individuare gli errori. Questo test unitario:

// 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");

Passerà il nostro primo esempio ma non secondo . Perché? Perché nel 2 ° esempio abbiamo scritto un modello con un errore di battitura (segno + mancato), il che significa che accetta solo una lettera / numero.

I test unitari possono essere eseguiti in console con il comando: phpunit [path_to_file] . Se tutto è OK, dovremmo essere in grado di vedere che tutti i test sono in stato OK , altrimenti vedremo Error (errori di sintassi) o Fail (almeno una riga in quel metodo non ha superato).

Con parametri aggiuntivi come --coverage possiamo anche vedere visivamente quante righe nel codice backend sono state testate e quali sono state superate / fallite. Questo vale per qualsiasi framework che abbia installato PHPUnit .

Esempio di come appare il test PHPUnit in console (aspetto generale, non secondo questo esempio):

inserisci la descrizione dell'immagine qui

Provider di dati PHPUnit

I metodi di test spesso richiedono dati da testare. Per testare completamente alcuni metodi è necessario fornire diversi set di dati per ogni possibile condizione di test. Certo, puoi farlo manualmente usando i loop, come questo:

...
public function testSomething()
{
    $data = [...];
    foreach($data as $dataSet) {
       $this->assertSomething($dataSet);
    }
}
... 

E qualcuno può trovarlo conveniente. Ma ci sono alcuni inconvenienti di questo approccio. Innanzitutto, dovrai eseguire ulteriori azioni per estrarre i dati se la tua funzione di test accetta diversi parametri. In secondo luogo, in caso di fallimento sarebbe difficile distinguere il set di dati in errore senza messaggi aggiuntivi e debug. In terzo luogo, PHPUnit fornisce un modo automatico per gestire i set di dati di test utilizzando i fornitori di dati .

Il fornitore di dati è una funzione che dovrebbe restituire i dati per il tuo caso di test specifico.

Un metodo di fornitore di dati deve essere pubblico e restituire una matrice di matrici o un oggetto che implementa l'interfaccia di Iterator e produce una matrice per ogni fase di iterazione. Per ogni array che fa parte della collezione verrà chiamato il metodo di test con i contenuti dell'array come argomenti.

Per utilizzare un fornitore di dati con il test, utilizzare @dataProvider annotazione @dataProvider con il nome della funzione del fornitore di dati specificata:

/**
* @dataProvider dataProviderForTest
*/
public function testEquals($a, $b)
{
    $this->assertEquals($a, $b);
}

public function dataProviderForTest()
{
    return [
        [1,1],
        [2,2],
        [3,2] //this will fail
    ];
}

Matrice di array

Si noti che dataProviderForTest() restituisce array di matrici. Ogni array annidato ha due elementi e riempiranno i parametri necessari per testEquals() uno per uno. Errore come questo verrà generato Missing argument 2 for Test::testEquals() se non ci sono abbastanza elementi. PHPUnit eseguirà automaticamente il loop dei dati ed eseguirà test:

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)
    ];
}

Ogni set di dati può essere nominato per comodità. Sarà più facile rilevare i dati in errore:

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)
    ];
}

iteratori

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]
        ]);
    }
}

Come puoi vedere, anche il semplice iteratore funziona.

Si noti che anche per un singolo parametro, il fornitore di dati deve restituire un array [$parameter]

Perché se cambiamo il nostro metodo current() (che in realtà restituisce i dati su ogni iterazione) a questo:

function current() {
    return current($this->array)[0];
}

O modificare i dati effettivi:

return new MyIterator([
            'Test 1' => 0,
            'Test 2' => false,
            'Test 3' => null
        ]);

Riceveremo un errore:

There was 1 warning:

1) Warning
The data provider specified for Test::testEquals is invalid.

Ovviamente, non è utile usare l'oggetto Iterator su un semplice array. Dovrebbe implementare una logica specifica per il tuo caso.

generatori

Non è esplicitamente indicato e mostrato nel manuale, ma è anche possibile utilizzare un generatore come fornitore di dati. Nota che la classe Generator implementa effettivamente l'interfaccia Iterator .

Ecco un esempio di utilizzo di DirectoryIterator combinato con 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.
        }
    }
}

Il fornitore di note yield un array. Riceverà invece un avviso di fornitore di dati non validi.

Verificare le eccezioni

Diciamo che vuoi testare un metodo che genera un'eccezione

class Car
{
    /**
     * @throws \Exception
     */
    public function drive()
    {
        throw new \Exception('Useful message', 1);
    }
}

È possibile farlo racchiudendo la chiamata di metodo in un blocco try / catch e facendo asserzioni sulle proprietà dell'oggetto execption, ma in modo più conveniente è possibile utilizzare metodi di asserzione di eccezioni. A partire da PHPUnit 5.2 sono disponibili i metodi expectX () per l'affermazione di tipo di eccezione, messaggio e codice

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();
    }
}

Se si utilizza la versione precedente di PHPUnit, il metodo setExpectedException può essere utilizzato al posto dei metodi expectX (), ma tenere presente che è deprecato e verrà rimosso nella versione 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();
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow