2009-08-07 20 views
10

Sto testando un po 'di codice PHP con SimpleTest e ho avuto dei problemi. Nei miei test di una classe di database voglio essere in grado di impostare un'aspettativa per le funzioni di PHP mysql. Nei miei test di una classe wrapper per la funzione mail voglio prendere in giro la funzione PHP mail. Questi sono solo alcuni esempi.Mocking delle funzioni PHP nei test unitari

Il punto è: non voglio (sempre) verificare se la mia classe Mail invia e-mail, voglio testare come chiama la funzione mail. Voglio essere in grado di controllare cosa restituiscono queste funzioni. Voglio essere in grado di testare la mia classe Database senza bisogno di un database, fixture e tutto questo.

Ho una certa esperienza con il test del codice Ruby e Test :: Unit e RSpec rendono molto semplice testare il codice in isolamento. Sono nuovo a testare PHP e mi sembra di testare molto più di quanto avrei dovuto, al fine di far passare i miei test.

C'è un modo in SimpleTest o PhpUnit o qualche altro framework di test che rende questo possibile o più semplice?

risposta

10

Non in modo automatico. Quello che puoi fare è scrivere il tuo codice in modo tale che le dipendenze esterne siano avvolte in oggetti che vengono passati dall'esterno. Nel tuo ambiente di produzione dovrai solo collegare i veri adattatori, ma durante i test, puoi collegarli a stub o mock.

Se si vuole davvero, è possibile utilizzare lo runkit extension che modifica il modello di programmazione di php in modo che sia possibile ridefinire classi e funzioni in fase di esecuzione. Si tratta di estensioni esterne e non standard, quindi tenetelo a mente. Lo standard defacto è un approccio manuale come ho descritto sopra.

+0

Vedo. Mi aspettavo (ma non ho sperato) una simile risposta. Non insisto davvero, dal momento che voglio semplificare e accelerare i test, non renderlo più complesso. Grazie per la risposta! – avdgaag

+0

Ora [Mockery] (http://docs.mockery.io/en/latest/cookbook/mocking_hard_dependencies.html) sembra la [strada da percorrere] (http://stackoverflow.com/a/42158443/659788). – Franco

+1

@Franco Non per i builder. – troelskn

0

In PHP 5.3+ ambiente che si può aggirare la necessità di utilizzare l'estensione runkit hacking gli spazi dei nomi. L'unico requisito in quanto le chiamate di funzione non utilizzano lo spazio dei nomi completo come \mysql_query() e di solito non lo fanno. Quindi puoi definire la stessa funzione nel tuo test, definendo il test in uno spazio dei nomi, e PHP chiamerà la tua funzione al posto di quella globale. Personalmente, utilizzo questo approccio per bloccare la chiamata alla funzione time(). Ecco uno nice example with the mockery framework

+0

È possibile utilizzare [php-mock] (https: // github.com/php-mock/php-mock) per creare tali mock. –

1

Here is an interesting article che scrive su funzioni di php globali di simulazione. L'autore propone una soluzione molto creativa per 'Mock' (tipo off) le funzioni globali di php sovrascrivendo i metodi all'interno dello spazio dei nomi del SUT (servizio in prova).

Ecco il codice da un esempio nel post del blog in cui viene preso in giro la funzione time:

<?php 

namespace My\Namespace; 

use PHPUnit_Framework_TestCase; 

/** 
* Override time() in current namespace for testing 
* 
* @return int 
*/ 
function time() 
{ 
    return SomeClassTest::$now ?: \time(); 
} 

class SomeClassTest extends PHPUnit_Framework_TestCase 
{ 
    /** 
    * @var int $now Timestamp that will be returned by time() 
    */ 
    public static $now; 

    /** 
    * @var SomeClass $someClass Test subject 
    */ 
    private $someClass; 

    /** 
    * Create test subject before test 
    */ 
    protected function setUp() 
    { 
     parent::setUp(); 
     $this->someClass = new SomeClass; 
    } 

    /** 
    * Reset custom time after test 
    */ 
    protected function tearDown() 
    { 
     self::$now = null; 
    } 

    /* 
    * Test cases 
    */ 
    public function testOneHourAgoFromNoon() 
    { 
     self::$now = strtotime('12:00'); 
     $this->assertEquals('11:00', $this->someClass->oneHourAgo()); 
    } 
    public function testOneHourAgoFromMidnight() 
    { 
     self::$now = strtotime('0:00'); 
     $this->assertEquals('23:00', $this->someClass->oneHourAgo()); 
    } 
} 

Non sono sicuro se questo è un buon modo per farlo, ma funziona sicuramente bene e penso che sia degno di nota Qui. Potrebbe essere un po 'di cibo per una discussione ...