Como faço para escrever testes de unidade em PHP? [fechadas]

97

Eu li em todos os lugares sobre como eles são ótimos, mas por algum motivo não consigo descobrir como exatamente devo testar algo. Alguém poderia postar um pedaço de código de exemplo e como eles o testariam? Se não for muito problema :)

solte
fonte
5
Para equilibrar, não há 2 ou 3 estruturas de teste de unidade para PHP - há uma lista aqui: en.wikipedia.org/wiki/List_of_unit_testing_frameworks#PHP
Fenton

Respostas:

36

Existe um terceiro "framework", que é muito mais fácil de aprender - ainda mais fácil do que o Simple Test, é chamado phpt.

Uma cartilha pode ser encontrada aqui: http://qa.php.net/write-test.php

Edit: Acabei de ver sua solicitação de código de amostra.

Vamos supor que você tenha a seguinte função em um arquivo chamado lib.php :

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

Realmente simples e direto, o parâmetro que você passa é retornado. Então, vamos dar uma olhada em um teste para esta função, vamos chamar o arquivo de teste 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"

Em suma, fornecemos o parâmetro $barcom valor "Hello World"e var_dump()a resposta da chamada de função para foo().

Para executar este teste, use: pear run-test path/to/foo.phpt

Isso requer uma instalação funcional do PEAR em seu sistema, o que é bastante comum na maioria das circunstâncias. Se você precisar instalá-lo, recomendo instalar a versão mais recente disponível. Caso precise de ajuda para configurá-lo, fique à vontade para perguntar (mas forneça o sistema operacional, etc.).

Até
fonte
Não deveria ser run-tests?
Dharman
30

Existem duas estruturas que você pode usar para testes de unidade. Simpletest e PHPUnit , que eu prefiro. Leia os tutoriais sobre como escrever e executar testes na página inicial do PHPUnit. É bastante fácil e bem descrito.

okoman
fonte
21

Você pode tornar o teste de unidade mais eficaz alterando seu estilo de codificação para acomodá-lo.

Eu recomendo navegar no Google Testing Blog , em particular o post sobre Writing Testable Code .

Preston
fonte
7
Eu acho que você mencionou uma ótima postagem. Começar sua resposta com 'O teste de unidade não é muito eficaz' quase me fez votar negativamente, porém, sendo um adepto do teste ... Possivelmente, reformular de maneira positiva encorajaria as pessoas a ler o artigo.
xtofl
2
@xtofl editou para aumentar ligeiramente a 'positividade' :)
icc97
13

Enrolei o meu porque não tive tempo de aprender a maneira de alguém fazer as coisas, demorou cerca de 20 minutos para escrever, 10 para adaptá-lo para postagem aqui.

Unitesting é muito útil para mim.

isso é meio longo, mas se explica e há um exemplo na 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();

Isso resulta em:

Teste: TestOne foi um fracasso 
/ **
* Este teste foi projetado para falhar
** / (linhas: 149-152; arquivo: /Users/kris/Desktop/Testable.php)
Teste: TestTwo foi um sucesso 
Kris
fonte
7

Obtenha o PHPUnit. É muito fácil de usar.

Em seguida, comece com afirmações muito simples. Você pode fazer muito com AssertEquals antes de entrar em qualquer outra coisa. É uma boa maneira de molhar os pés.

Você também pode tentar escrever seu teste primeiro (já que você deu à sua pergunta a tag TDD) e depois escrever seu código. Se você não fez isso antes, é um abrir de olhos.

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);
}
PartialOrder
fonte
5

Para testes e documentação simples, php-doctest é muito bom e é uma maneira muito fácil de começar, já que você não precisa abrir um arquivo separado. Imagine a função abaixo:

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

Se você agora executar este arquivo através do phpdt (executor de linha de comando do php-doctest) 1 teste será executado. O doctest está contido dentro do bloco <code>. Doctest se originou em python e é bom para fornecer exemplos úteis e executáveis ​​sobre como o código deve funcionar. Você não pode usá-lo exclusivamente porque o próprio código iria se encher de casos de teste, mas descobri que é útil junto com uma biblioteca tdd mais formal - eu uso o phpunit.

Esta primeira resposta aqui resume bem (não é unit vs doctest).

Sofia
fonte
1
não torna a fonte um pouco desordenada?
Ali Ghanavatian
pode. só deve ser usado para testes simples simples. também funciona como documentação. se precisar de mais, use o teste de unidade.
Sofia
2

phpunit é basicamente o framework de teste de unidade de fato para php. há também DocTest (disponível como um pacote PEAR) e alguns outros. O próprio php é testado para regressões e similares por meio de testes de phpt que também podem ser executados via Pear.

kguest
fonte
2

Os testes de codecepção são muito parecidos com os testes de unidade comuns, mas são muito poderosos em coisas onde você precisa de simulação e stub.

Aqui está o teste do controlador de amostra. Observe como os stubs são facilmente criados. A facilidade com que você verifica se o método foi invocado.

<?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');
}
}

Também existem outras coisas legais. Você pode testar o estado do banco de dados, sistema de arquivos, etc.

Davert
fonte
1

Além das excelentes sugestões sobre frameworks de teste já fornecidas, você está construindo seu aplicativo com um dos frameworks PHP da web que possui teste automatizado embutido, como Symfony ou CakePHP ? Às vezes, ter um lugar para simplesmente colocar seus métodos de teste reduz o atrito inicial que algumas pessoas associam aos testes automatizados e TDD.

Bradheintz
fonte
1

Muito para postar novamente aqui, mas aqui está um ótimo artigo sobre como usar phpt . Ele cobre uma série de aspectos em torno do phpt que geralmente são esquecidos, então pode valer a pena uma leitura para expandir seu conhecimento sobre php além de apenas escrever um teste. Felizmente, o artigo também discute como escrever testes!

Os principais pontos de discussão

  1. Descubra como os aspectos do PHP marginalmente documentados funcionam (ou praticamente qualquer outra parte)
  2. Escreva testes de unidade simples para seu próprio código PHP
  3. Escreva testes como parte de uma extensão ou para transmitir um bug em potencial para os internos ou grupos de controle de qualidade
troca rápida
fonte
1

Sei que já há muitas informações aqui, mas como elas ainda aparecem nas pesquisas do Google, é melhor adicionar o Chinook Test Suite à lista. É uma estrutura de teste simples e pequena.

Você pode facilmente testar suas classes com ele e também criar objetos fictícios. Você executa os testes por meio de um navegador da web e (ainda não) por meio de um console. No navegador, você pode especificar qual classe de teste ou mesmo que método de teste executar. Ou você pode simplesmente executar todos os testes.

Uma captura de tela da página do github:

Estrutura de teste de unidade Chinook

O que eu gosto é a maneira como você afirma os testes. Isso é feito com as chamadas "afirmações fluentes". Exemplo:

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

E criar objetos fictícios também é uma brisa (com uma sintaxe fluente):

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

De qualquer forma, mais informações podem ser encontradas na página do github com um exemplo de código também:

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

Vivendi
fonte