web-dev-qa-db-fra.com

Fonction fictive de PHPUnit?

J'ai un scénario intéressant dans le sens où j'ai besoin d'une fonction à définir pour pouvoir tester une autre fonction. La fonction que je veux tester ressemble à ceci:

if (function_exists('foo') && ! function_exists('baz')) {
    /**
     * Baz function
     * 
     * @param integer $n
     * @return integer
     */
    function baz($n)
    {
        return foo() + $n;
    }
}

La raison pour laquelle je vérifie l'existence de foo est qu'il peut être défini ou non dans le projet d'un développeur et que la fonction baz s'appuie sur foo. Pour cette raison, je veux seulement que baz soit défini s'il peut appeler foo

Le seul problème est que jusqu'à présent, il était impossible d'écrire des tests. J'ai essayé de créer un script d'amorçage dans la configuration de PHPUnit qui définirait une fausse fonction foo et exigerait ensuite l'autochargeur Composer, mais mon script principal pense toujours que foo n'est pas défini. foo n'est pas un package Composer et ne peut par ailleurs être requis par mon projet. Manifestement, Mockery ne fonctionnera pas pour cela non plus. Ma question est de savoir si quelqu'un de plus expérimenté avec PHPUnit a rencontré ce problème et trouvé une solution.

Merci!

16
samrap

Commencez par un léger remaniement du code pour le rendre plus testable.

function conditionalDefine($baseFunctionName, $defineFunctionName)
{
    if(function_exists($baseFunctionName) && ! function_exists($defineFunctionName))
    {
        eval("function $defineFunctionName(\$n) { return $baseFunctionName() + \$n; }");
    }
}

Alors appelez ça comme ça:

conditionalDefine('foo', 'bar');

Votre classe de test PHPUnit contiendra les tests suivants:

public function testFunctionIsDefined()
{
    $baseName = $this->mockBaseFunction(3);
    $expectedName = uniqid('testDefinedFunc');
    conditionalDefine($baseName, $expectedName);
    $this->assertTrue(function_exists($expectedName));
    $this->assertEquals(5, $expectedName(2)); 
}
public function testFunctionIsNotDefinedBecauseItExists()
{
    $baseName = $this->mockBaseFunction(3);
    $expectedName = $this->mockBaseFunction($value = 'predefined');
    conditionalDefine($base, $expectedName);
    // these are optional, you can't override a func in PHP
    // so all that is necessary is a call to conditionalDefine and if it doesn't
    // error, you're in the clear
    $this->assertTrue(function_exists($expectedName));
    $this->assertEquals($value, $expectedName()); 
}
public function testFunctionIsNotDefinedBecauseBaseFunctionDoesNotExists()
{
    $baseName = uniqid('testBaseFunc');
    $expectedName = uniqid('testDefinedFunc');
    conditionalDefine($base, $expectedName);
    $this->assertFalse(function_exists($expectedName));     
}

protected function mockBaseFunction($returnValue)
{
    $name = uniqid('testBaseFunc');
    eval("function $name() { return '$value'; }");
    return $name;
}

Cela suffit pour ce que vous demandez. Vous pouvez cependant, plus loin refactoriser ce code en extrayant la génération de fonction dans un générateur de code. C'est ce que vous pouvez écrire des tests unitaires sur le générateur pour vous assurer qu'il crée le type de fonction que vous attendez. 

1
Jeremy Giberson

Cela devrait marcher!

use MyProject\baz;

class YourTestCase
{
    /** @var callable **/
    protected $mockFoo;

    /** @var callable **/
    protected $fakeFoo;

    public function setUp()
    {
        if (function_exists('foo')) {
            $this->mockFoo = function($foosParams) {
                foo($foosParams);
                // Extra Stuff, as needed to make the test function right.
            };
        }

        $this->fakeFoo = function($foosParams) {
            // Completely mock out foo.
        };
    }

    public function testBazWithRealFoo()
    {
        if (!$this->mockFoo) {
            $this->markTestIncomplete('This system does not have the "\Foo" function.');
        }

        $actualResults = baz($n, $this->mockFoo);
        $this->assertEquals('...', $actualResults);
    }

    public function testBazWithMyFoo()
    {
        $actualResults = baz($n, $this->fakeFoo);
        $this->assertEquals('...', $actualResults);
    }
}

Puis modifiez votre code existant:

if (function_exists('foo') && ! function_exists('baz')) {
    /**
     * Baz function
     * 
     * @param integer $n
     * @return integer
     */
    function baz($n)
    {
        return foo() + $n;
    }
}

namespace MyProject
{
    function baz($bazParams, callable $foo = '\foo')
    {
        return $foo() + $bazParams;
    }
}

Ensuite, au lieu d’appeler baz($n), vous devez appeler:

use MyProject\baz;
baz($bazParams);

C'est comme l'injection de dépendance pour les fonctions, yo ;-)

1
Theodore R. Smith

Il semble que ce soit une situation dans laquelle vous devez vous moquer de la visibilité, ce qui est clairement en dehors du domaine de PHPUnit, une bibliothèque de classes. Vous pouvez donc toujours utiliser PHPUnit, mais vous devrez peut-être vous en sortir pour atteindre votre objectif. 

Le seul moyen auquel je puisse penser pour créer une maquette d'une fonction est directement dans le scénario de test, au-dessus de l'objet qui étend\PHPUnit_Framework_TestCase. 

function foo() {
    return 1;
}

if (function_exists('foo') && ! function_exists('baz')) {
    /**
     * Baz function
     *
     * @param integer $n
     * @return integer
     */
    function baz($n)
    {
        return foo() + $n;
    }
}


class TestBaz extends \PHPUnit_Framework_TestCase
{

    public function test_it() {

        $this->assertSame(4,baz(3));
    }


}

Vous devrez peut-être créer deux fichiers, un avec foo et un sans, ou quatre si vous voulez tester foo/not foo et baz/not baz. C’est définitivement une option hors du commun, que je ne recommanderais pas normalement, mais dans votre cas, c’est peut-être votre meilleur choix.

1
Katie

Est-ce suffisant?

to-test.php:

<?php
if (function_exists('foo') && ! function_exists('baz')) {
    /**
     * Baz function
     *
     * @param integer $n
     * @return integer
     */
    function baz($n)
    {
        return foo() + $n;
    }
}

BazTest.php:

<?php

class BazTest extends PHPUnit_Framework_TestCase {
    public function setUp()
    {
        function foo()
        {
            // Appropriate mock goes here
            return 1;
        }

        include __DIR__ . '/to-test.php';
    }

    public function testBaz()
    {
        $this->assertEquals(2, baz(1));
        $this->assertEquals(3, baz(2));
    }
}

Lancer le test donne:

PHPUnit 5.4.8 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 54 ms, Memory: 8.00MB

OK (1 test, 2 assertions)
0
Justin McAleer

L'ajouter dans le fichier d'amorçage ne fonctionne pas - pourquoi?

Est-ce parce que parfois la fonction baz est (A) correctement créée par le système et parfois (B) vous devez vous en moquer? Ou (C) avez-vous toujours besoin de vous moquer de cela?

  • Cas A: Pourquoi le code crée-t-il sporadiquement une fonction vitale à la volée?
  • Cas B: Une fonction ne peut être enregistrée qu'une seule fois et jamais annulée ou écrasée. Par conséquent, soit vous allez avec la maquette, soit vous ne le faites pas. Aucun mélange n'est autorisé.
  • Cas C: Si vous devez toujours vous moquer de lui et que vous l'ajoutez au fichier d'amorçage, il sera défini. En ce qui concerne ce que vous avez essayé, soit votre fichier d'amorçage pour phpunit n'est pas chargé correctement, soit vous avez mal orthographié le nom de la fonction.

Je suis sûr que vous avez correctement configuré votre amorçage phpunit, mais pour faire bonne mesure, est-ce que cela ressemble à quelque chose comme:

/tests/phpunit.xml:

<phpunit
    bootstrap="phpunit.bootstrap.php"
</phpunit>

/tests/phpunit.bootstrap.php:

<?php
require(__DIR__ . "/../bootstrap.php"); // Application startup logic; this is where the function "baz" gets defined, if it exists

if (function_exists('foo') && ! function_exists('baz')) {
    /**
     * Baz function
     * 
     * @param integer $n
     * @return integer
     */
    function baz($n)
    {
        return foo() + $n;
    }
}

Ne créez pas la fonction baz à la volée dans vos tests, par exemple. dans une fonction setUp.

Les suites de tests dans phpunit utilisent le même programme d'amorçage. Par conséquent, si vous devez tester des cas dans lesquels la fonction baz est définie et d'autres cas dans lesquels elle n'est pas définie (et que vous devez la simuler), vous devez fractionner le dossier tests, par exemple. dans deux dossiers différents, chacun avec leurs fichiers phpunit.xml et phpunit.bootstrap.php. Par exemple. /tests/with-baz et /tests/mock-baz. À partir de ces deux dossiers, exécutez les tests séparément. Créez simplement des liens symboliques vers la variable phpunit dans chaque sous-dossier (par exemple, à partir de /test/with-baz, créez ln -s ../../vendor/bin/phpunit si compositeur est à la racine) pour vous assurer d’exécuter la même version de phpunit dans les deux scénarios.

La solution ultime consiste bien entendu à déterminer où la fonction baz est définie et à inclure manuellement le fichier de script incriminé, dans la mesure du possible, pour garantir l'application de la logique correcte.

Alternative

Utilisez l'annotation @runInSeparateProcess de phpunit et définissez la fonction selon vos besoins.

<?php
class SomeTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @runInSeparateProcess
     */
    public function testOne()
    {
        if (false === function_exists('baz')) {
            function baz() {
                return 42;
            }
        }
        $this->assertSame(42, baz());
    }

    public function testTwo()
    {
        $this->assertFalse(function_exists('baz'));
    }
}
0
Kafoso