Testing in the Dark – protected und private Methods

| 4 Kommentare

Eigentlich werden protected und private Methoden ja nicht getestet, weil sie gewöhnlich in Punkto Schnittstelle ohnehin unsichtbar sind. Die Erfahrung zeigt aber auch, dass es an verschiedenen Stellen wesentlich einfacher und sicherer ist, wenn sich auch die “versteckten” Methoden einzeln testen lassen.

Bei meiner Suche nach einer Lösung für dieses Problemchens habe ich zwei verschiedene Ansätze herausgefunden.

Der folgende Beispielcode von Sebastian Bergmann macht ja genau das was wir wollen, wenn unsere PHP-Version über 5.3.2 ist. Es wird mit Reflections ein Methoden-Objekt erzeugt, der Zugriff öffentlich gemacht und letztendlich die Methode ausgeführt. Einfach und wie ich finde auch übersichtlich.

<?php
class FooTest extends PHPUnit_Framework_TestCase
{
    /**
     * @covers Foo::doSomethingPrivate
     */
    public function testPrivateMethod()
    {
        $method = new ReflectionMethod(
          'Foo', 'doSomethingPrivate'
        );

        $method->setAccessible(true);

        $this->assertEquals(
          'blah', $method->invoke(new Foo)
        );
    }
}

Leider gibt es aber auch immer mal wieder Situationen, in denen man nur auf einer älteren PHP-Version arbeiten kann, weil ein Update nicht möglich ist.

In diesem Fall sein, kann man sich mit einem kleinen Hack behelfen und einfach eine Dummy-Klasse erzeugen. Das ist zwar nicht sonderlich schön, aber darum geht es ja bei Hacks auch nicht. Es soll funktionieren.

<?php
class Custom_Test_TestCase extends PHPUnit_Framework_TestCase
{
  // more methods here...

  protected function _createAnonymousDummyClass()
  {
    if (class_exists($this->getDummyClassName(), false)) {
      return;
    }

    $reflection = new ReflectionClass($this->getClassName());
    $lines = file($reflection->getFileName());
    $start = $reflection->getStartLine() - 1;
    $end = $reflection->getEndLine() - 1;

    $code = '';
    foreach ($lines as $key => $value) {
      if ( $start <= $key && $key <= $end ) {
        $code .= $value . PHP_EOL;
      }
    }

    $code = str_replace($this->getClassName(), $this->getDummyClassName()
                        . ' extends ' . $this->getClassName(), $code);
    $code = preg_replace( '/(.*)(protected|private)
                        (.*function|.*\$.*)/i', '$1public $3', $code );
    eval($code);
  }
}

Der Hack baut wie auch das erste Beispiel auf Reflections auf. Über Reflections wird die Stelle im Code lokalisiert, an der die Originalklasse definiert wird.

Nachdem der Code aus der Datei gelesen wurde wird er dann so verändert, dass unsere Dummy-Klasse von der eigentlichen Klasse erbt und auch gleich alle ihre Methoden erneut public deklariert werden.

Der Basistyp der Klasse bleibt erhalten und so kann das Dummyobjekt weitesgehens normal verwendet werden.

4 Kommentare

  1. Wann genau hast du es für sinnvoll erachtet irgendwas zu testen was nicht public ist, und warum?

    Dass man mit Reflection’s einiges drehen kann, ist klar & wahrscheinlich sogar bekannt – aber sonst?!

  2. Persönlich finde ich es hilfreich, sich während der Entwicklung auch verschiedene ansich protected und privaten Methoden testen lassen, damit sich evtl. auftretende Fehler leichter finden lassen. Sicher könnte man das auch anders überprüfen, aber über diesen Weg muss nicht der eigentliche Code verändert werden.

    Wie sind deine Erfahrungen dazu?

  3. Allenfalls lassen sich diese Probleme doch auch einfach über Pre- und Postconditions vermeiden? So wie ich das verstehe willst du ja einfach den Fehlerbereich dadurch eingrenzen oder…?

  4. Denke mir Sinn macht es auf jeden Fall bei Methoden, welche schon mal einen Bug hatten.

Hinterlasse eine Antwort

Pflichtfelder sind mit * markiert.


Schlagwörter: A/B-Test, AbstractType, Adapter, AddOn, Administration, Ajax, Amazon, Animation, Annotations, Anonyme Klasse, Ant, Apache, API, Array, ArrayAccess, Attachment, Auftrag, Ausbildung, Auswertung, Authentifizierung, AutoLoader, AWS, Bedienung, Bedingung, Benchmark, Berechtigung, Berlin, Bildbearbeitung, Bildschirmfoto, Blog, Blogroll, BOM, Bootstrap, Bot, Browser, Bugtracker, Byte Order Mark, Bücher, Cache, CakePHP, Call-Center, Callback, CamelCase, Canvas, Captcha, CDN, Cheatsheet, CLI, Clickout, Closure, Cloud, CodeSniffer, Collection, Community, Comparator, Config, Contest, Controller, Converter, CouchDB, Countable, Cronjob, CRUD, CSS, CSV, CustomLibrary, Custom_Model, Daemon, Data Mapper, Datei, Datenbank, Datenstruktur, Datentypen, Dating, Datum, Debug, Decorator, Dekorierer, Design, Design Patterns, Doctrine, Dokumentation, Dump, Duplikat, each, EC2, Eclipse, Email, Entwicklung, Entwurfsmuster, Enum, Erweiterung, Event, Eventhandling, Exception-Handling, Extension, Facebook, Factory, Fallback, Fehler, Fehlermeldung, Filter, Firefox, Flash, flexigrid, Foreach, Formatierung, Formular, Framework, FTP, Funktion, Futon, ga:pi(), Getter, Google Analytics, Hash, Hash-Bang, Header, htaccess, HTML5, htpasswd, HTTP, HTTPS, IDE, If, Implementierung, InnoDB, Interceptor, Interface, Internet Explorer, isset, Iterator, Java, JavaScript, Job, jQuery, Kommentar, Konfiguration, Konsole, Kontrollstruktur, kopieren, kostenlos, Kundenbetreuung, Late Static Binding, Layout, Links, Linux, Listeners, Lizenz, Logging, Löschen, Magento, Magic Methods, Manual, ManyToMany, Marketing, Methode, Model, Monolog, MVC, MySQL, NetBeans, Network, Nirvanix, Objekt, Observable, Observer, OneToMany, Online Tool, OOP, Open Source, Operator, OR-Mapper, Order, ORM, O’Reilly, Parameter, Partnersuche, Passwort, Performance, PHP, php.ini, PHP hates me, phpMyAdmin, PHPUnit, Plugin, Popup, Proxy, Prüfsumme, Prüfung, QR-Code, Qualitätssicherung, Query, Queue, Redesign, Refactoring, Reflection, Request, Response, Responsive Design, Rest-API, Rockstar, Rollback, Routing, S3, Samba, Scheifen, Schleife, Schutz, Screenshot, Secure Shell, Selbstreferenz, Server, Setter, setTimeout, Shop, Sicherheit, Sicherung, Sichtbarkeit, Singleton Pattern, Skin, SOAP, Social Network, Software, Sortierung, Sourcecode, Spam, Speicherproblem, Spickzettel, SPL, Splittest, SSH, SSL, Stammtisch, Statement, static, Statistik, Status, Stellvertreter, Strategy Pattern, Stream, String, Stuttgart, Stylesheet, Subversion, Sun VirtualBox, Support, SVN, Switch, Symfony, Symfony2, Symfony Live, Tag, Template, Template Method, Ternär Operator, Testing, Theme, Thumbnail, Tool, Tour, Tracking, Twig, Twitter, Type-Cast, Ubuntu, Umwandlung, Underscore, unset, Update, Upload, Url, User Story, Validierung, Vererbung, Versionskontrolle, Versionsnummer, Verzweigung, Video, Videospiel, Virtualisierung, Visitor Pattern, Vorschaubild, walk, Warteschlange, Webserver, Webservice, Weiterleitung, Werkzeug, Windows, WindowsAzure, WordPress, Wrapper, Writer, XML, Youtube, Zeitschleife, Zeitsteuerung, Zend Framework, Zend_Application, Zend_Cloud, Zend_CodeGenerator, Zend_Http_Client, Zend_Reflection, Zend_Service, ZPress, Zugangskontrolle, Zugriffsmethode