Pergunta

Existe uma maneira de colocar os testes dentro de um TestCase executar em uma determinada ordem?Por exemplo, quero separar o ciclo de vida de um objeto, desde a criação até o uso e a destruição, mas preciso ter certeza de que o objeto está configurado antes de executar os outros testes.

Foi útil?

Solução

Talvez haja um problema de design nos seus testes.

Normalmente cada teste não deve depender de nenhum outro teste, portanto eles podem ser executados em qualquer ordem.

Cada teste precisa instanciar e destruir tudo o que precisa para ser executado, essa seria a abordagem perfeita, você nunca deve compartilhar objetos e estados entre testes.

Você pode ser mais específico sobre por que precisa do mesmo objeto para N testes?

Outras dicas

PHPUnit suporta dependências de teste através do @depende anotação.

Aqui está um exemplo da documentação onde os testes serão executados em uma ordem que satisfaça as dependências, com cada teste dependente passando um argumento para o próximo:

class StackTest extends PHPUnit_Framework_TestCase
{
    public function testEmpty()
    {
        $stack = array();
        $this->assertEmpty($stack);

        return $stack;
    }

    /**
     * @depends testEmpty
     */
    public function testPush(array $stack)
    {
        array_push($stack, 'foo');
        $this->assertEquals('foo', $stack[count($stack)-1]);
        $this->assertNotEmpty($stack);

        return $stack;
    }

    /**
     * @depends testPush
     */
    public function testPop(array $stack)
    {
        $this->assertEquals('foo', array_pop($stack));
        $this->assertEmpty($stack);
    }
}

No entanto, é importante observar que testes com dependências não resolvidas não ser executado (desejável, pois isso chama a atenção rapidamente para o teste que falhou).Portanto, é importante prestar muita atenção ao usar dependências.

A resposta correta para isso é um arquivo de configuração adequado para testes.Eu tive o mesmo problema e resolvi criando testsuite com a ordem dos arquivos de teste necessários:

phpunit.xml:

<phpunit
        colors="true"
        bootstrap="./tests/bootstrap.php"
        convertErrorsToExceptions="true"
        convertNoticesToExceptions="true"
        convertWarningsToExceptions="true"
        strict="true"
        stopOnError="false"
        stopOnFailure="false"
        stopOnIncomplete="false"
        stopOnSkipped="false"
        stopOnRisky="false"
>
    <testsuites>
        <testsuite name="Your tests">
            <file>file1</file> //this will be run before file2
            <file>file2</file> //this depends on file1
        </testsuite>
    </testsuites>
</phpunit>

Se quiser que seus testes compartilhem vários objetos e configurações auxiliares, você pode usar setUp(), tearDown() para adicionar ao sharedFixture propriedade.

PHPUnit permite o uso da anotação '@depends' que especifica casos de teste dependentes e permite passar argumentos entre casos de teste dependentes.

Na minha opinião, considere o seguinte cenário em que preciso testar a criação e destruição de um recurso específico.

Inicialmente eu tinha dois métodos, a.testCreateResource e b.testDestroyResource

a.testCreateResource

<?php
$app->createResource('resource');
$this->assertTrue($app->hasResource('resource'));
?>

b.testDestroyResource

<?php
$app->destroyResource('resource');
$this->assertFalse($app->hasResource('resource'));
?>

Acho que é uma má ideia, pois testDestroyResource depende de testCreateResource.E uma prática melhor seria fazer

a.testCreateResource

<?php
$app->createResource('resource');
$this->assertTrue($app->hasResource('resource'));
$app->deleteResource('resource');
?>

b.testDestroyResource

<?php
$app->createResource('resource');
$app->destroyResource('resource');
$this->assertFalse($app->hasResource('resource'));
?>

Solução alternativa:Use funções estáticas(!) em seus testes para criar elementos reutilizáveis.Por exemplo (eu uso o Selenium IDE para registrar testes e o phpunit-selenium (github) para executar testes dentro do navegador)

class LoginTest extends SeleniumClearTestCase
{
    public function testAdminLogin()
    {
        self::adminLogin($this);
    }

    public function testLogout()
    {
        self::adminLogin($this);
        self::logout($this);
    }

    public static function adminLogin($t)
    {
        self::login($t, 'john.smith@gmail.com', 'pAs$w0rd');
        $t->assertEquals('John Smith', $t->getText('css=span.hidden-xs'));
    }

    // @source LoginTest.se
    public static function login($t, $login, $pass)
    {
        $t->open('/');
        $t->click("xpath=(//a[contains(text(),'Log In')])[2]");
        $t->waitForPageToLoad('30000');
        $t->type('name=email', $login);
        $t->type('name=password', $pass);
        $t->click("//button[@type='submit']");
        $t->waitForPageToLoad('30000');
    }

    // @source LogoutTest.se
    public static function logout($t)
    {
        $t->click('css=span.hidden-xs');
        $t->click('link=Logout');
        $t->waitForPageToLoad('30000');
        $t->assertEquals('PANEL', $t->getText("xpath=(//a[contains(text(),'Panel')])[2]"));
    }
}

Ok, e agora posso usar esses elementos reutilizáveis ​​em outro teste :) Por exemplo:

class ChangeBlogTitleTest extends SeleniumClearTestCase
{
    public function testAddBlogTitle()
    {
      self::addBlogTitle($this,'I like my boobies');
      self::cleanAddBlogTitle();
    }

    public static function addBlogTitle($t,$title) {
      LoginTest::adminLogin($t);

      $t->click('link=ChangeTitle');
      ...
      $t->type('name=blog-title', $title);
      LoginTest::logout($t);
      LoginTest::login($t, 'paris@gmail.com','hilton');
      $t->screenshot(); // take some photos :)
      $t->assertEquals($title, $t->getText('...'));
    }

    public static function cleanAddBlogTitle() {
        $lastTitle = BlogTitlesHistory::orderBy('id')->first();
        $lastTitle->delete();
    }
  • Desta forma, você pode construir uma hierarquia de seus testes.
  • Você pode manter a propriedade de que cada caso de teste é totalmente separado dos outros (se você limpar o banco de dados após cada teste).
  • E o mais importante, se por exemplo, a forma de login mudar no futuro, você apenas modifica a classe LoginTest e não precisa da parte de login correta em outros testes (eles devem funcionar após a atualização do LoginTest) :)

Quando executo o teste, meu script limpa o anúncio do banco de dados no início.Acima eu uso meu SeleniumClearTestCase class (eu faço screenshot() e outras funções legais lá) é uma extensão de MigrationToSelenium2 (do github, para portar testes gravados no firefox usando seleniumIDE + plugin ff "Selenium IDE:PHP Formatters" ) que é uma extensão da minha classe LaravelTestCase (é uma cópia de Illuminate\Foundation esting estCase mas não estende PHPUnit_Framework_TestCase) que configura o laravel para ter acesso ao eloquent quando queremos limpar o banco de dados no final do teste) que é extensão de PHPUnit_Extensions_Selenium2TestCase.Para configurar o laravel eloquent eu também tenho na função createApplication do SeleniumClearTestCase (que é chamada em setUp, e pego essa função do laral test/TestCase)

Realmente há um problema com seus testes se eles precisarem ser executados em uma determinada ordem.Cada teste deve ser totalmente independente dos demais:ajuda na localização de defeitos e permite obter resultados repetíveis (e, portanto, depuráveis).

Confira esse site para obter um monte de idéias/informações sobre como fatorar seus testes de maneira a evitar esse tipo de problema.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top