• Willkommen im Zend Framework Forum

    ZF1 Zend Framework 1 + ZF2 Zend Framework 2

    Das Zend Framework Forum ist seit 2006 die erste Anlaufstelle für Zend Framework Entwickler in Deutschland. Mit über 70.000 Beiträgen und einer steigenden Nutzerzahl bietet das Forum hilfreiche Themen und ZF-Tutorials für professionelle Entwickler, fortgeschrittene Programmierer sowie Zend Framework Einsteiger.
    Wenn dies Dein erster Besuch in der Zend Framework Community ist, lies bitte zuerst die Hilfe - FAQ durch. Du musst Dich registrieren, bevor Du Beiträge verfassen kannst. Klicke oben auf 'Registrieren', um die Registrierung zu starten. Du kannst auch jetzt schon Beiträge lesen. Hier im Forum findest Du die Zend Framework Hilfe, die Du suchst!

    Grüße an alle Zend Framework Entwickler. Das Team vom Zend Framework Forum!

    Drupal Agentur

PhpUnit Test und Events triggern ZF2

aspn

New member
Hallo ihr lieben,

ich bin gerade dabei meinen ersten Unit test mit ZF2 zu schreiben und tu mir noch einwenig schwer.

Ich hab ein Modul welches mit einen ViewHelper setzt und konfiguriert. Es handelt sich dabei um den Translator der durch ein Escaping erweitert wird.

aspn\View\Translator.php
PHP:
class Translate extends AbstractTranslatorHelper
{

    /**
     * The Escaper Object
     * @var $escper
     */
    protected $escaper;

    /**
     * Translate a message
     *
     * @param  string $message
     * @param  string $textDomain
     * @param  string $locale
     * @param string $escape
     *
     * @throws RuntimeException
     * @throws InvalidArgumentException
     *
     * @return string
     */
    public function __invoke($message, $textDomain = null, $locale = null,$escape='escapeHtml')
    {
        $translator = $this->getTranslator();
        if (null === $translator) {
            throw new RuntimeException('Translator has not been set');
        }
        if (null === $textDomain) {
            $textDomain = $this->getTranslatorTextDomain();
        }

        if($this->escaper===null){
            $this->escaper = new Escaper();

        }

        if($escape===false){
            return $translator->translate($message, $textDomain, $locale);
        }

        if(!method_exists($this->escaper,$escape)){
            throw new InvalidArgumentException('Escaping method "'.$escape.'" does not exists.');
        }

        return $this->escaper->{$escape}($translator->translate($message, $textDomain, $locale));
    }

}

In der Module.php hab ich einen Event auf den Renderer gesetzt, welcher mir die Translation Files und den Translater setzt.
PHP:
    /**
     * Bootstrap
     *
     * a mehtod gets attached to the render event.
     *
     * @param MvcEvent $e
     */
    public function onBootstrap(MvcEvent $e){

        $em = $e->getApplication()->getEventManager();
        $em->attach(MvcEvent::EVENT_RENDER,array($this,'setTranslator'),-100);

    }


    /**
     * Translator
     *
     * the locale get set with the following settings and prior.
     * - take the url lang param
     * - take the logged in user lang settings
     * - take the browser lang
     * - fallback if nothing fits
     *
     * only the actual lang file gets loaded.
     *
     * !!!important only the language is set. Not the language_land!!!
     *
     * @param MvcEvent $e
     */
    public function setTranslator(MvcEvent $e){
        //create a new Translator
        $translator = new Translator();

        //get matched route
        $route = $e->getRouteMatch();

        //configure the locales
        //the countries are ignored!
        if($lang = $route->getParam('lang')){}
        elseif($e->getApplication()->getServiceManager()->get('aspnUser\Auth\Service')->hasIdentity()){
            $user = $e->getApplication()->getServiceManager()->get('aspnUser\Auth\Service')->getIdentity();
            $lang = strtolower($user->language);
        }else{
            $lang = substr(\Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']),0,2);
        }
        $translator->setLocale(strtolower($lang));

        //TODO take fallback from database
        $translator->setFallbackLocale('de');

        //add the module translation file. Only add the actually needed module translation.
        //$modules = array_keys($event->getApplication()->getServiceManager()->get('ModuleManager')->getLoadedModules());
        $controller = $route->getParam('controller');
        $modules[] = substr($controller,0,strpos($controller,'\\'));
        foreach($modules as $module){
            if(substr($module,0,4)=="aspn"){
                $path =  realpath(__DIR__.'/../'.$module.'/languages');
                if($path!==false){
                    $translator->addTranslationFilePattern('gettext', $path ,'%s.mo', $module);
                }
            }
        }


        //set the translator and the text domain
        $e->getApplication()->getServiceManager()->get('ViewHelperManager')->get('translate')->setTranslator($translator)->setTranslatorTextDomain($modules[0]);
    }

So nun wollte ich für das ganze einen PHP Unit test schreiben. Wie gesagt, es handelt sich um den ersten Test in meinem leben :)
Da ich in diesem Modul keine sinstigen Controller oder Views habe, wollte ich in dem Test einfach eine View erstellen.

Ich hab mir wie in der Zend Doku beschrieben eine Bootstrap angelegt und die xml.
Unit Testing a Zend Framework 2 application — Zend Framework 2 2.1.5 documentation - Zend Framework

Folgendes Testscript hab ich mittlerweile aufgesetzt und es funktioniert.
Aber ich hab in der methode setUp den Translator selbst hinzugefügt. Ich möchte aber dass dieser von der Bootstrap hergenommen wird.
Somit müsste ich den Event irgendwie händisch im setUp triggern, da die View ja in dem Test nicht wirklich gerendert wird...
Suche nun schon seit 2 Stunden aber finde leider nichts darüber. Vielleicht könnt ihr mir hier einwenig weiterhelfen.

PHP:
<?php

namespace aspnI18n\View;
use aspnI18n\View\Helper\Translate;
use PHPUnit_Framework_TestCase;
use Zend\I18n\Translator\Translator;
use Zend\View\Renderer\PhpRenderer;


class TranslateTest extends \PHPUnit_Framework_TestCase
{

//    protected $helper;
    protected $view;
    protected $manager;

    protected function setUp()
    {
        $helper = new Translate();
        $this->view = new PhpRenderer();
        $this->manager = $this->view->getHelperPluginManager();
        $this->manager->setService('translate', $helper);

        $translator = new Translator();
        $this->view->plugin('translate')->setTranslator($translator);

    }

    public function testTranslateWithDefaultEscape()
    {
        $this->assertEquals('<a>test</a>', $this->view->translate('<a>test</a>'));
    }

    public function testTranslateGivenEscaper()
    {
        $this->assertEquals('<a>test</a>', $this->view->translate('<a>test</a>',null,null,'escapeHtml'));
    }

    public function testTranslateFalseEscaper()
    {
        $this->setExpectedException('InvalidArgumentException');
        $this->view->translate('<a>test</a>',null,null,'escapeXYZ');
    }


}
Vielen Dank für eure Bemühungen
aspn
 

michl

New member
.
Aber ich hab in der methode setUp den Translator selbst hinzugefügt.
genau richtig so
Ich möchte aber dass dieser von der Bootstrap hergenommen wird.
wieso? Der test sollte isoliert sein.
Somit müsste ich den Event irgendwie händisch im setUp triggern, da die View ja in dem Test nicht wirklich gerendert wird...
PHP:
$return = $translator($message, $textDomain, $locale, $escape);
PHP::__invoke()


Blöd ist halt, dass du dir den Escaper in deinem Translator selbst instanzierst. Hast also nochmal ne Abhängigkeit geschaffen auf die du von außen keinen Einfluss hast. Sowas findet man leider auch häufiger in diversen ZF2 Klassen vor, ist allerdings mMn. nicht gut. Da hieß es, man legt Wert auf DI und findet sowas dann trotzdem an allen Ecken und Enden wieder.... Wenigstens hat man aber, sofern man so ne Stelle findet, die Möglichkeit das benötigte Objekt per setter zu injecten...

Setz also deinen Escaper bitte vorher auch explizit per Setter. Was du gemacht hast ist zwar bequem, aber bad practice. Die Abhängigkeiten kannst du ja über eine Factory managen.
 
Zuletzt bearbeitet:

aspn

New member
Danke für deine Antwort michl,

ich habe nun den translator abgeändert:
PHP:
        //$translator = new Translator();
        $translator = $e->getApplication()->getServiceManager()->get('translator');
somit kann man diesen jetzt in der Config abändern, falls benötigt.

wieso? Der test sollte isoliert sein.
Aber will man den Test mit seinem Module nicht mit ZendFramework verknüpfen? Damit man z.B.: auf eine höhere Zend Version updaten kann und somit testet ob auch mit der neuen Version alles ohne Probleme klappt?

Deshalb wollte ich dies eigentlich mit der onBootstrap verknüpfen.
 

crash

New member
Das Zusammenspiel von Klassen testest du mit Integrationstests: Integration testing - Wikipedia, the free encyclopedia

Gegenstand von Unittest (Unit testing - Wikipedia, the free encyclopedia) ist das isolierte Testen von kleinen Einheiten (units), also meistens Methoden. Ein Test sollte sich daher auf eine einzelne Methode beziehen und nur mit den für den Test benötigten Daten gefüttert werden. Isoliert heisst auch, isoliert vom Dateisystem, Schnittstellen, Datenbanken etc.

Wenn du ein Framework für Integrationstests suchst, ist Behat sicher das bekannteste: Behat - BDD for PHP Integrationstest schreiben sich bei bereits durch Unittests getesteten Code sehr schnell und lohnen sich schnell, denn die Integrationstest sagen dir dann beim entwickeln, ob deine Anwendung tut was sie soll.
 
Oben