Phpunit, wie kann man testen, ob die Methode “nichts” tut?

Lesezeit: 10 Minuten

Benutzer-Avatar
John Smith

class Testme()
{
    public function testMe ($a)
    {
        if ($a == 1)
        {
            throw new Exception ('YAY');
        }
    }
}

es ist also einfach zu testen, ob es eine Ausnahme ausgelöst hat

/**
 * @expectedException Exception
 */
public function test()
{
    new Testme(1);
}

Aber was wenn es hat nichts gebracht?

public function test()
{
    new Testme(2);
 ?? ? ? ? ?
}

  • Bitte nicht still abstimmen! Ich denke, das ist eine vollkommen berechtigte Frage, also erklären Sie bitte, wenn Sie nicht einverstanden sind. @OP: Dies ist eine verwandte Frage: stackoverflow.com/questions/27511593/…

    – Quasdunk

    22. Februar 2015 um 18:16 Uhr


  • Es ist in Ordnung, mit einem Wert zurückzukehren – aber wenn meine ursprüngliche Methode darin besteht, etwas zu “tun”, etwas nicht zu “geben”, möchte ich den ursprünglichen Code nicht ändern

    – John Smith

    22. Februar 2015 um 18:18 Uhr

  • Nicht sicher, was genau Sie damit meinen … Die Lösung in der verwandten Frage besteht darin, den Funktionsaufruf in Ihrem Test in einen Try-Catch-Block einzuschließen. Wenn eine Ausnahme ausgelöst wird, fangen Sie sie ab und schlagen den Test manuell fehl ($this->fail()), andernfalls machen Sie einfach eine Dummy-Assertion, die den Test besteht ($this->assertTrue(true)). Sie testen nicht, ob etwas zurückgegeben wird oder nicht, Sie konzentrieren sich nur auf die Ausnahme.

    – Quasdunk

    22. Februar 2015 um 18:24 Uhr


  • Und nur um es ausdrücklich klarzustellen: Dafür gibt es (noch) keine eingebaute Lösung. Es gibt einen GitHub-Issue-Thread, in dem die Details besprochen werden: github.com/sebastianbergmann/phpunit-documentation/issues/171

    – Quasdunk

    22. Februar 2015 um 18:31 Uhr


Benutzer-Avatar
Xavi Montero

Szenarien

Sie haben zwei mögliche Szenarien, in denen eine Funktion nichts tut:

Szenario 1: Keine return-Anweisung

Ihre Funktion tut nichts, weil Sie in ihr und Ihnen keine Aktionen ausführen unterlassen Sie umfassen die return Stichwort darin:

public function doNothing()
{
    // Do nothing.
}

Szenario 2: Mit return-Anweisung

Ihre Funktion tut nichts, weil Sie in ihr und Ihnen keine Aktionen ausführen gehören dazu das return Stichwort darin ohne einen beliebigen Rückgabewert ausdrücken:

public function doNothing()
{
    // Do nothing.
    return;
}

Andere Szenarien

Ich werde die Fälle auslassen, um die folgenden Szenarien zu behandeln:

  1. Fall, in dem Sie nichts zurückgeben, aber wichtige Aktionen ausführen, die an anderen Objekten getestet werden können. In diesem Fall müssen Sie die resultierenden Zustände der geänderten Objekte einem Komponententest unterziehen.

  2. Falls Sie nichts anderes tun, als etwas zurückzugeben, sollten Sie den Rückgabewert einem Komponententest unterziehen.

Durchsuchen der Dokumentation im PHP-Handbuch

Für den ersten Fall dokumentiert das PHP-Handbuch, dass der ausgewertete Ausdruck der Funktion sein wird null. Es sagt hier: http://php.net/manual/en/functions.returning-values.php in einer Anmerkung:

Wird die Rückgabe weggelassen, wird der Wert NULL zurückgegeben.

Für den zweiten Fall dokumentiert das PHP-Handbuch, dass der ausgewertete Ausdruck der Funktion ebenfalls sein wird null. Es sagt hier: http://php.net/manual/en/function.return.php in einer Anmerkung:

Wenn kein Parameter angegeben wird, müssen die Klammern weggelassen werden und es wird NULL zurückgegeben. […]

Fazit

Es ist daher eindeutig dokumentiert, dass eine Funktion, die “nichts tut”, zwangsläufig zu evaluiert null.

Wie man eine Funktion testet, die nichts tut

Behaupten Sie einfach Ihre Erwartungen:

$this->assertNull( $sut->doNothing() );

Auf diese Weise “üben” Sie Ihre Funktion, Sie führen sie durch, sodass die Codeabdeckung alle Zeilen vervollständigt, und Sie “erwarten”, dass “nichts passiert”, indem Sie die testen null Wert seiner Bewertung als Ausdruck, wie dokumentiert.

Wie man einen Konstruktor testet, der nichts tut

Trotzdem einen Konstruktor zu testen… naja… gesunder Menschenverstand: Was ist der Zweck eines Konstruktors? Erstellen Sie ein Objekt (Instanz) eines bestimmten Typs (Klasse), richtig?

Also … Ich ziehe es vor, die 100% meiner Komponententests damit zu beginnen, dass ich überprüfe, ob die $sut wurde erschaffen. Dies ist der allererste Test, den ich schreibe, wenn ich den Code einer neuen Klasse schreibe. Dies ist der Test, den ich schreibe, noch bevor der Kurs existiert. Dafür ist schließlich der Konstruktor da. Roter Balken. Dann erstelle ich die Klasse. Grüner Balken.

Nehmen wir an, ich habe eine Email Klasse, die eine Zeichenfolge akzeptiert und nur erstellt wird, wenn eine gültige E-Mail übergeben wird und andernfalls eine Ausnahme auslöst. das ist deiner Frage sehr ähnlich. Ein Konstruktor, der nur “die Erstellung zulässt” oder “sie verweigert, indem er das System explodiert”.

Normalerweise würde ich so etwas tun:

//-------------------------------------------------//
// Tests                                           //
//-------------------------------------------------//

/** @dataProvider validEmailProvider **/
public function testCreationIsOfProperClass( string $email )
{
    $sut = $this->getSut( $validEmail );
    $this->assertInstanceOf( Email::class, $sut );
}

/** @dataProvider invalidEmailProvider **/
public function testCreationThrowsExceptionIfEmailIsInvalid( string $invalidEmail )
{
    $this->expectException( EmailException::class );
    $this->getSut( $invalidEmail );
}

//-------------------------------------------------//
// Data providers                                  //
//-------------------------------------------------//

public function validEmailProvider() : array
{
    return
    [
        [ '[email protected]' ],
        [ 'bob.with-several+symbols@subdomain.another.subdomain.example.verylongTLD' ],
    ]
}

public function invalidEmailProvider() : array
{
    return
    [
        [ 'missing_at_symbol' ],
        [ 'charlie@cannotBeOnlyTld' ],
    ]
}

//-------------------------------------------------//
// Sut creators                                    //
//-------------------------------------------------//

private function getSut( string $email ) : Email
{
    return new Email( $email );
}

Da ich PHP 7.0 verwende und überall Typen einfüge, sowohl bei der Eingabe der Parameter als auch in den Rückgabetypen, würde die Funktion getSut() zuerst fehlschlagen, wenn das erstellte Objekt keine E-Mail wäre.

Aber selbst wenn ich es unter Weglassen des Rückgabetyps geschrieben habe, testet der Test, was erwartet wird: new Email( '[email protected]' ); ist selbst ein Ausdruck, der “etwas” von Klasse auswerten sollte Email::class.

Wie man einen Konstruktor testet, der etwas tut

Code-Geruch. Der Konstruktor sollte wahrscheinlich nicht funktionieren. Falls vorhanden, speichern Sie einfach Parameter. Wenn der Konstruktor außer dem Speichern von Parametern “funktioniert”, sollten Sie eine verzögerte Verarbeitung auf Gettern in Betracht ziehen oder diese Arbeit in einer Fabrik oder so delegieren.

So testen Sie einen Konstruktor, der “nur Parameter speichert”

Genau wie vorher + dann die Daten abrufen.

  1. Testen Sie in Ihrem ersten Test, ob die Schöpfung eine Instanz von etwas ist.
  2. Dann üben Sie in einem anderen anderen Test so etwas wie einen Getter aus, der Ihnen das gibt, was in den Konstruktor eingegeben wurde, selbst wenn der Konstruktor nichts getan hat (außer es zu speichern).

Hoffe, dass dies hilft.

  • In der Tat funktioniert es, aber bitte beachten Sie, dass, wenn die Methode einen PHP 7.1-Rückgabetyp „void“ verwendet, sie immer noch funktioniert, aber von vielen statischen Analysetools als „void method result used“ gemeldet wird.

    – Pierre-Yves

    15. November 2017 um 17:06 Uhr

  • Tolle Antwort, aber: “Was ist der Zweck eines Konstruktors? Erstellen Sie eine Klasse eines bestimmten Typs, richtig?” Tatsächlich instanziiert ein Konstruktor ein Objekt einer bestimmten Klasse/eines bestimmten Typs.

    – alexg

    14. Juni 2019 um 13:07 Uhr

  • Ja, Ihre subtile Korrektur ist wahr. Der Konstruktor „erzeugt keine Klasse“, sondern „erzeugt eine Instanz einer bestimmten Klasse“. Das ist richtig. Ich werde die Antwort bearbeiten, um Ihren Beitrag zu integrieren. Danke für deinen Beitrag!!

    – Xavi Montero

    15. Juni 2019 um 11:34 Uhr

  • Dieser komplizierte Ansatz wird ab PHPUnit 7.1, 2018 nicht mehr benötigt. Siehe meine Antwort unten.

    – Tomas Votruba

    16. Dezember 2020 um 17:34 Uhr

In PHPUnit 7.2+ können Sie auch verwenden TestCase::expectNotToPerformAssertions()

public function test()
{
    // ...

    $this->expectNotToPerformAssertions();
}

Dies hat das gleiche Verhalten wie die @doesNotPerformAssertions Anmerkung.

  • Dies. Ich habe versucht, keine Behauptungen zu schreiben, und PHPUnit hat den Test als riskant gekennzeichnet. Dies ist der richtige Weg, es zu tun.

    – RedDragonWebDesign

    30. August 2021 um 10:53 Uhr

Benutzer-Avatar
Tomás Votruba

2018+

Heutzutage ist die Annotation genau für diese Fälle die beste Praxis:

/**
 * @doesNotPerformAssertions
 */
public function testSomething()
{
    $someService = new SomeObject();
    $someService->shallNotFail();
}

  • Die dortige Dokumentation tut es nicht Geben Sie an, dass diese Anmerkung “zum Testen dient, dass eine Methode nichts tut”. aber um Warnmeldungen zu vermeiden, was immer eine schlechte Praxis ist. Wörtlich heißt es “@doesNotPerformAssertions verhindert, dass ein Test, der keine Behauptungen durchführt, als riskant angesehen wird.”. Die Frage tut es nicht Fragen Sie “Wie kann ich Code ausführen, ohne etwas zu testen?” aber “wie kann ich testen, dass es nichts tut”, was anders ist. „Berichte zum Schweigen bringen“ sollte vorübergehend nur verwendet werden, um die Ausführlichkeit zu deaktivieren, wenn wir etwas halb codiert haben, nicht als endgültiges Verhalten.

    – Xavi Montero

    17. Dezember 2020 um 9:35 Uhr

  • Danke, dass Sie sich gemeldet haben. Ihr Kommentar ist für mich schwer nachvollziehbar. Wenn wir uns nur den Eingabecode in der Frage ansehen (ohne ihn umzugestalten), wird die @doesNotPerformAssertions ist genau der Weg, um es zu testen.

    – Tomas Votruba

    17. Dezember 2020 um 10:54 Uhr


  • In meiner Antwort gebe ich den Fall der Quelle in der Frage im Abschnitt “So testen Sie einen Konstruktor, der nichts tut” an. Das new Operator “macht” tatsächlich etwas: Erzeugt eine neue Instanz der gegebenen Klasse. Das heißt, der Test ist $this->assertInstanceOf( HappyClass::class, new HappyClass( $whatever );

    – Xavi Montero

    17. Dezember 2020 um 21:20 Uhr

  • Die Frage ist eher allgemein “Phpunit, wie testet man, ob die Methode “nichts” tut?” Obwohl Ihre Antwort technisch richtig ist, suchen Leute, die von Google kommen, nach Antworten für ihren eigenen Code. Bei der Antwort soll nicht nur der genaue Detailcode des Fragestellers helfen, sondern all seiner Follower. @doesNotPerformAssertions steht stark da

    – Tomas Votruba

    18. Dezember 2020 um 0:12 Uhr


Es ist nicht möglich. Hinzufügen return Aussage und behaupte das Ergebnis.

class Testme()
{
    public function testMe ($a)
    {
        if ($a == 1)
        {
            throw new Exception ('YAY');
        }

        return true;
    }
}

und dann

$object = new Testme();
$this->assertTrue($object->testMe(2));

Benutzer-Avatar
Quasdunk

Notiz: Die Credits für diese Lösung gehen an diese verwandte Antwort. Der Kontext mag etwas anders erscheinen, aber die Lösung / Problemumgehung funktioniert auf die gleiche Weise. Das Testen, dass keine Ausnahme ausgelöst wird, ist genauso wie das Testen einer Methode ohne Rückgabewert.

Entsprechend diesen Thementhreadgibt es keine eingebaute Lösung zum Testen von etwas wie DoesNotThrowException in PHPUnit (noch).

Also ja, eine Lösung wäre, einen Dummy-Wert von Ihrer Methode zurückzugeben, wie z

public function testMe ($a)
{
    if ($a == 1) { throw new Exception ('YAY'); }

    return true;
}

und behaupte es dann in deinem Test. Wenn Sie den Code jedoch nicht nur für den Test ändern möchten, können Sie dies umgehen:

public function testExceptionIsNotThrown()
{
    try {
        new Testme(2);
    }
    catch(Exception $e) {
        /* An exception was thrown unexpectedly, so fail the test */
        $this->fail();
    }

    /* No exception was thrown, so just make a dummy assertion to pass the test */
    $this->assertTrue(true);
}

Es mag hacky und nicht sehr intuitiv erscheinen, aber wenn es dumm ist, aber funktioniert, ist es nicht dumm.

Benutzer-Avatar
Dip Hasan

Dies ist eine sehr interessante Frage, obwohl viele Antworten geschrieben wurden, scheint keine von ihnen die Frage richtig zu beantworten, da Sie mit der Klasse gefragt haben, lassen Sie es mich auf diese Weise erklären.

Bitte beachten Sie, dass eine Instanzmethode, die Sie im Unterricht erstellt haben, nur 2 Absichten haben sollte.

  1. Es kann den Zustand einer Klasse ändern (die Klasseneigenschaften wie private Variablen ändern)
  2. Es gibt den Zustand der Klasse zurück ( Getter )

Alles andere ist bedeutungslos, es sei denn, es handelt sich um eine statische Methode. zum Beispiel, wenn Sie Klasse wie diese haben

class Foo {

   private $prop = null;
   public function fooMethod() {
      $this->prop = "string";
   }
   public function getProp() {
     return $this->prop;
   }
}

Die Methode fooMethod() gibt nichts zurück, beeinflusst aber den Status der $prop-Eigenschaft in der Klasse, Sie können die Methode testen, indem Sie sie testen

$this->assertNotNull( $instance->getProp() );

weil Sie wussten, dass wenn diese Methode ausgeführt wird, die Eigenschaft $prop betroffen sein sollte und der Status dieser Variablen geändert wird.

Sonstiges Szenario: Meine Methode ändert den Zustand nicht und gibt auch keine Zustandsvariablen zurück.

Dann ist die Methode static. Es sollte keine Instanzmethode sein, und die statischen Methoden haben normalerweise einen Rückgabetyp, da sie den Zustand der Klasse nicht beeinflussen und auch keine Zustandsvariablen zurückgeben können. Dies hindert die statischen Methoden daran, ein Ergebnis irgendwo zu speichern (es sei denn, Sie speichern sie global, tun Sie das nicht), also sollte es definitiv eine Ausgabe zurückgeben. Wenn Sie keine Ausgabe zurückgeben möchten, können Sie einen booleschen Wert von der statischen Methode zurückgeben.

Benutzer-Avatar
Chyngyz Sagynov

public function testThrowingException()
{
    $this->expectException(Exception::class);
    $this->expectExceptionMessage('YAY');
    (new Testme())->testMe(1);
}

public function testNotThrowingException()
{
    $this->expectNotToPerformAssertions();
    (new Testme())->testMe(2);
}

  • Erklärungen oder Kommentare würden helfen

    – Alexander Kutscherjuk

    18. November 2021 um 21:43 Uhr

1252740cookie-checkPhpunit, wie kann man testen, ob die Methode “nichts” tut?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy