Ankündigung

Einklappen
Keine Ankündigung bisher.

UnitTest

Einklappen

Neue Werbung 2019

Einklappen
X
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • UnitTest

    Soweit ich das verstanden habe, überlegt man sich zuerst die Klasse.
    Dann erstellt man Tests dafür und erst dann entwickelt man die Klassen.
    Ist es soweit richtig?

    Ist so ein Skript überhaupt testbar:
    PHP-Code:
    <?php
    /******************************************************************************\
    |*  ##producttitle##  ##version##
    |*  Author: ##author##
    |*  Copyright ##year## by Daniel Fatkic aka ragtek
    |*  Visit http://ragtek.org/blog for more information
    |*
     ******************************************************************************/

    require_once DIR '/ragtek/library/ragtekDAO.php';
    require_once 
    DIR '/ragtek/library/ragtek_Profileblock.php';

    class 
    invitedDAO extends ragtekDAO
    {
        protected 
    $fromUser;
        protected 
    $showAvatar;

        public function  
    __construct(vB_Registry $registry$userid$usecache false$showAvatar true) {
            
            
    $this->fromUser $userid;
            
    $this->showAvatar $showAvatar;
            
    $this->condition "  where referrerid = "  $this->fromUser;
            if (
    $usecache)
            {
                
    $this->initialCache();
            }
            
    parent::__constuct($registry);
        }

            
    // activate the cachefunction
        
    protected function initialCache()
        {
            
    $this->cacheEvents = array(
                
    'ragtekNewuser',
                
    'ragtekRenameUser'
            
    );
            
            
    $this->cacheKey 'ragtekInviteTab_' $this->fromUser;
            
    parent::initialCache();
        }

        protected function 
    fetchAvatar($invite)
        {
            
    $avwidth '';
        
    $avheight '';
        if (
    $invite['avatarid'])
        {
                
    $avatarurl $invite['avatarpath'];
        }
        else
        {

                if (
    $invite['hascustom'])
                {
                        if (
    $this->registry->options['usefileavatar'])
                        {
                            
    $avatarurl $this->registry->options['avatarurl'] . "/avatar$invite[userid]_$invite[avatarrevision].gif";
                        }
                        else
                        {
                            
    $avatarurl 'image.php?' $vbulletin->session->vars['sessionurl'] . "u=$invite[userid]&amp;dateline=$invite[avatardateline]";
                        }

                        if (
    $invite['avheight'] AND $invite['avwidth'])
                        {
                            
                            
    $avheight "height=\"$invite[avheight]\"";
                            
    $avwidth "width=\"$invite[avwidth]";
                            
                            
    $avatarurl $avatarurl '" ' $avheight ' ' $avwidth;
                        }
                    }
            else
            {
                        
    $avatarurl '';
            }
            }
            return 
    $avatarurl;
        }



        
    /**
         * fetch the data
         * first try to get it from cache, if cache is disabled
         * or there are no data, get it from DB
         * and write them to cache (if enabled)
         */
        
    protected function fetchData()
        {
           if (
    $this->data $this->readFromCache())
            {
                return;
        }
            else
            {
                
    $query "SELECT user.userid, user.username, user.joindate, user.avatarrevision "
                        
    . ($this->showAvatar ?
                              
    ", avatar.avatarpath, NOT ISNULL(customavatar.userid) AS hascustom,
                                customavatar.dateline AS avatardateline, customavatar.filedata_thumb,
                                customavatar.height AS avheight, customavatar.width AS avwidth,
                                customavatar.width_thumb AS avwidth_thumb, customavatar.height_thumb AS avheight_thumb" 
    ""
                        
    ) . "
                        FROM " 
    .
                        
    TABLE_PREFIX "user as user"
                            
    . ($this->showAvatar "
                                        LEFT JOIN " 
    TABLE_PREFIX "avatar AS avatar ON (avatar.avatarid = user.avatarid)
                                        LEFT JOIN " 
    TABLE_PREFIX "customavatar AS customavatar ON (customavatar.userid = user.userid) " '') .
                        
    $this->condition 
                        
    " order by userid ASC";
                
                
    $invites $this->registry->db->query_read($query);
                
    $return = array();
                while (
    $invite $this->registry->db->fetch_array($invites))
                {          
                    if (
    $this->showAvatar)
                    {
                          
    $invite['avatarurl']  = $this->fetchAvatar($invite);
            }
                    
    $this->data[] = $invite;
                }
             }
                
    $this->registry->db->free_result($invite);
                
    $this->writeToCache();
        }
    }

    class 
    vB_ProfileBlock_Referrer extends ragtek_ProfileBlock
    {
        protected 
    $showAvatar false;
        public 
    $template_name 'memberblock_block_invitedUsers';
        

        function 
    prepare_output($id ''$options = array())
        {
                    
    // here it makes not really sense for caching,
                    // alse there are too many events, i would need to observ
                    // for cleaning the cache (new registration, avatarchange, username change)
                
                
    $showAvatar $this->registry->options['avatarenabled'];
                
                
    $invited = new invitedDAO($this->registry$this->profile->userinfo['userid'], false$showAvatar);
                    
    $users $invited->getData();
                    unset(
    $invited);
                    if (
    count($users) > 0)
                    {
                        
    $this->block_data['invited'] = '';
                        foreach (
    $users AS $user)
                        {
                            
    $user['joindate'] = vbdate($this->registry->options['dateformat'], $user['joindate']);

                            
    $referrerBits vB_Template::create('referrerUserbit');
                            
    $referrerBits->register('user'$user);
                            
    $invitedbits .= $referrerBits->render();
                        }
                        
    $this->block_data['invited'] = $invitedbits;
                    }
        }
            public function 
    confirm_display()
            {
                return (
    $this->block_data['invited'] != '') ? true false;
            }
    }
    /*======================================================================*\
    || ####################################################################
    ||
    || # Buildtime: ##buildtime##
    || ####################################################################
    \*======================================================================*/


  • #2
    Bei TTD (test-driven-development) sollte man erst die Tests schreiben und dann die Klasse.

    Dein Code ist schon testbar. Du hast besitmmte Testdaten in der DB und weißt was theoretisch herauskommen sollte. Z.B. fertiger HTML Code. Dann kannst du natürlich entsprechend testen ob das auch wirklich am Ende rauskommt.

    Kommentar


    • #3
      Schwierig, da du erstens Caches und zweitens komplexe Arrays benutzt ($registry->options, $invite). Ein Cache hat grundsätzlich in einer Klasse relativ wenig verloren. Was du tun kannst ist eine Klasse bauen, die das Cachen für eine andere übernimmt, sich also drüberstülpt (Proxy-Pattern?)

      Die Lösung mit der Registry finde ich auch nicht gut. Ein Mensch der deine Klassen nicht kennt, aber verwenden will wird verzweifeln und letztlich den Code durchgehen müssen um (Trial-and-Error) um herauszufinden welche Optionen möglich und vor allem benötigt werden.

      Du benötigst also nicht nur die Tests für die oben geposteten Klassen, sondern auch Pre- oder sogar Post-Conditions. Ein Fehlerhandling mit Exceptions gibt es auch nicht, so dass deine Unittests vermutlich einen eigenen Error-Handler registrieren müssen um undefined-Notices etc. zu fangen.
      "Mein Name ist Lohse, ich kaufe hier ein."

      Kommentar


      • #4
        Zitat von Chriz Beitrag anzeigen
        Schwierig, da du erstens Caches und zweitens komplexe Arrays benutzt ($registry->options, $invite). Ein Cache hat grundsätzlich in einer Klasse relativ wenig verloren. Was du tun kannst ist eine Klasse bauen, die das Cachen für eine andere übernimmt, sich also drüberstülpt (Proxy-Pattern?)
        Wieso hat der Cache in einer Klasse nichts verloren?
        Die Klasse liefert mir die Benutzer und da ist es doch (primär) egal, ob die aus der DB oder dem Cache kommen.
        Zitat von Chriz Beitrag anzeigen
        Die Lösung mit der Registry finde ich auch nicht gut. Ein Mensch der deine Klassen nicht kennt, aber verwenden will wird verzweifeln und letztlich den Code durchgehen müssen um (Trial-and-Error) um herauszufinden welche Optionen möglich und vor allem benötigt werden.
        Naja, die Leute, die meine Klasse benutzen, werden sich den Code kaum anschauen.
        Und die, die sich den anschauen, wissen was mit registry gemeint ist(vBulletin Objekt)
        Zitat von Chriz Beitrag anzeigen
        Du benötigst also nicht nur die Tests für die oben geposteten Klassen, sondern auch Pre- oder sogar Post-Conditions. Ein Fehlerhandling mit Exceptions gibt es auch nicht, so dass deine Unittests vermutlich einen eigenen Error-Handler registrieren müssen um undefined-Notices etc. zu fangen.
        Phu, ok sehe schon, das ist ein komplexes Thema. Mal scahuen was ich im inet dazu finde.

        Mir fehlt einfach der Durchblick wann man was wie testet.
        Danke für die Antwort.

        Kommentar


        • #5
          Und die, die sich den anschauen, wissen was mit registry gemeint ist(vBulletin Objekt)
          Klar, wenn ich allen sage, dass ich statt A eigentlich B meine wirds auch irgendwann verstanden, gut machts das trotzdem nicht.

          PHP-Code:
          <?php
          class MyFile {
            public function 
          __construct($fileName) {
              
          $this->setFileName($fileName);
            }  
            public function 
          setFileName($fileName) {
              
          $this->_fileName $fileName;
              
          // $this->_fileSize = null; // das sollte eigentlich noch eingebaut sein
            
          }
            public function 
          getFileSize() {
              if (
          $this->_fileSize === null) { // einfacher Cache
                
          $this->_fileSize filesize($this->_fileName);
              }
              return 
          $this->_fileSize;
            }
          }

          // test_small.txt = 1KB, test_large.txt  = 25 KB

          $file = new MyFile("test_small.txt");
          echo 
          $fileSize->getFileSize(); // Istwert: 1 KB, Sollwert: 1 KB
          $file->setFileName("test_large.txt");
          echo 
          $fileSize->getFileSize(); // Istwert: 1 KB, Sollwert: 25 KB
          ?>
          Nur diese "unglückliche" Aufrufkombination offenbart den Bug. Recht unwahrscheinlich, dass du das per Unittesting aufdeckst. Deshalb ist Caching und Unittesting etwas sehr schlechtes in Kombination, da du das Caching-Verhalten nicht manipulieren kannst ohne den Code zu kennen, was du ja gerade nicht können musst (im Optimalfall schreibt dir eine andere Person die Unittests).

          Abgesehen davon: Single-Responsibility - eine Klasse hat eine Verantwortlichkeit, klar kleine Ausnahmen kann man machen, wozu ich jetzt aber das Cachen von Datenbankabfragen (wars das was du cachet, hab nicht genau drauf geachtet) sicherlich nicht zählen würde.
          "Mein Name ist Lohse, ich kaufe hier ein."

          Kommentar


          • #6
            Ähm zu deinem Code kann ich nicht wirklich was sagen, ausser das es $_fileName nicht gibt.

            Aber zu meinem Code:
            1. man kann doch das Caching deaktivieren
            2. Wenn ich weiß das im Cache zB 30 passende Benutzer liegen und 30 beim Test rauskommen, weiß ich doch, dass es richtig rennt.

            Aber OK, mit
            PHP-Code:
            Recht unwahrscheinlichdass du das per Unittesting aufdeckstDeshalb ist Caching und Unittesting etwas sehr schlechtes in Kombinationda du das Caching-Verhalten nicht manipulieren kannst ohne den Code zu kennenwas du ja gerade nicht können musst (im Optimalfall schreibt dir eine andere Person die Unittests). 
            ergibt es schon einen Sinn. Da ich ja allein bin, bin ich nicht auf so etwas gekommen.

            Kommentar


            • #7
              Zumindest sollte man ein Caching-Mock injecten können, um das Speicher/Rückgabeverhalten kontrollieren zu können.

              Was ich auch kritisch finde sind Zeilen wie diese:

              parent::__constuct($registry);

              Was passiert da? Schon weil das Parent-Objekt ja eine komplett andere Schnittstelle besitzt. Wird da was in die Registry geschrieben oder was ausgelesen? Warum machst Du das nicht als Parameter? Du hast beim Testen keine Kontrolle darüber.

              [edit]
              Übrigens steht da __constuct. Soll das so?
              --

              „Emoticons machen einen Beitrag etwas freundlicher. Deine wirken zwar fachlich richtig sein, aber meist ziemlich uninteressant.
              Wenn man nur Text sieht, haben viele junge Entwickler keine interesse, diese stumpfen Texte zu lesen.“


              --

              Kommentar


              • #8
                Zitat von nikosch Beitrag anzeigen
                Übrigens steht da __constuct. Soll das so?
                Autsch.
                Sollen => nein und dadurch das es funktioniert, ist es absolut niemanden aufgefallen

                Danke

                Kommentar


                • #9
                  Gerne. Höchste Zeit fürs Unittesting, was?
                  --

                  „Emoticons machen einen Beitrag etwas freundlicher. Deine wirken zwar fachlich richtig sein, aber meist ziemlich uninteressant.
                  Wenn man nur Text sieht, haben viele junge Entwickler keine interesse, diese stumpfen Texte zu lesen.“


                  --

                  Kommentar


                  • #10
                    Zusatz:
                    Mir fehlt einfach der Durchblick wann man was wie testet.
                    Du solltest deine normalen Methoden relativ klein halten und möglichst wenige Parameter verwenden, Faustregel hieß mal (ich glaube) maximal 20 Zeilen Code pro Methode. Alles was drüber geht solltest du durch Verteilen auf andere (Hilfs-)Methoden zerstückeln. Dann kannst du relativ einfach die einzelnen Methoden testen.

                    Beim Testen gilt grundsätzlich: Definiere die Randbereiche deiner erlaubten Parameter.
                    Bei Zahlen z.B.: kleinster möglicher negativer Wert, -1, 0, 1, größert möglicher negativer Wert.

                    Beispiel Kreditvergabe (Definition, z.B. Pflichtenheft)
                    a) Kredite für unter 18 jährige garnicht
                    b) Kredite für 19-25-jährige bis 5000 EUR
                    c) Kredite für 26-60-jährige bis 10.000 EUR
                    d) Kredite für Rentner (> 60) nur unter 500 EUR

                    Randbereiche:
                    a) Alter: 17, 18, 19
                    b) Alter: 18, 19, 20 & 24, 25, 26; Betrag: 4999, 5000, 5001
                    c) Alter: 25, 26, 27 & 59, 60, 61; Betrag: 9.999, 10.000, 10.001
                    d) Alter: 60, 61; Betrag: 499, 500, 501
                    usw.

                    So jetzt hast du alle "gefährlichen" Altersgruppen und Beträge:
                    Alter: 17, 18, 19, 20, 24, 25, 26, 27, 59, 60, 61
                    Beträge: 499, 500, 501, 4999, 5000, 5001, 9.999, 10.000, 10.001
                    und kombinierst jeden mit jedem und vergleichst sie mit dem per Definition gelieferten soll:
                    PHP-Code:
                    <?php

                    assert
                    ($giveCredit->check(17499) === false);
                    assert($giveCredit->check(17500) === false);
                    // ..
                    assert($giveCredit->check(265001) === true);
                    // ..
                    assert($giveCredit->check(6110000) === false);
                    assert($giveCredit->check(6110001) === false);
                    ?>
                    Zusätzlich kannst du wie erwähnt auch -1, 0 und +1 oder PHP_INT_MAX für Alter und Beträge mitkombinieren.

                    Eigentlich solltest du deine Objekte vor jedem einzelnen Test auch neu instanzieren ($giveCredit), den Grund und vor allem die URL, wo das erklärt wurde, hab ich leider vergessen

                    Ähm zu deinem Code kann ich nicht wirklich was sagen, ausser das es $_fileName nicht gibt.
                    Das meinte ich nicht, hab ich vergessen, richtig. Aber warum kannst du dazu nichts sagen? Der Seiteneffekt durch Caching ist doch offensichtlich.

                    Dass man dein Caching deaktivieren kann ist schön, aber obs stimmt testet immernoch der Unittest und der hat eben wie erwähnt Schwierigkeiten damit Seiteneffekte von Caching aufzudecken. Wenn du wie nikosch schon erwähnt die Möglichkeit hast einer Caching-Klasse ein Mockup unterzujubeln, dass du kontrollierst geht das schon einfacher. Dazu musst du aber das Caching aus der Klasse nehmen und in eine eigene Packen. Abgesehen davon reduzierst du damit auch die Anzahl deiner Unittests.

                    Wenn du eine Caching-Klasse baust, die Methodenaufrufe cacht kannst du sie überall verwenden, musst sie aber nur einmal testen. Bei 10 Klassen die noch zusätzlich diese Caching-Klasse verwenden macht das 11 Unittestgruppen (10 + 1), wenn deine 10 Klassen aber ihre eigenen Cachingmechanismen verwenden musst du 10 Klassen 2x testen, einmal mit und einmal ohne Caching, macht 10 x 2 Unittestgruppen. Und noch nicht getestet hast du, wie deine Klasse reagiert, wenn du das Caching an und abschaltest und das Objekt danach weiterverwendest (Caching aus, Klasse verwenden, Caching an, Klasse weiterverwenden, ..)

                    Naja, dir scheint meine Antwort nicht zu schmecken, aber du hast gefragt
                    "Mein Name ist Lohse, ich kaufe hier ein."

                    Kommentar


                    • #11
                      Eigentlich solltest du deine Objekte vor jedem einzelnen Test auch neu instanzieren ($giveCredit), den Grund und vor allem die URL, wo das erklärt wurde, hab ich leider vergessen
                      Naja, Caching und Registry sind ja beide gute Gründe dafür. Es änder sich schlicht das Verhalten des Gesamtsystems (und damit die Konsistenz für den Testvorgang), ich denke darauf läuft der erwähnte Passus auch hinaus.
                      --

                      „Emoticons machen einen Beitrag etwas freundlicher. Deine wirken zwar fachlich richtig sein, aber meist ziemlich uninteressant.
                      Wenn man nur Text sieht, haben viele junge Entwickler keine interesse, diese stumpfen Texte zu lesen.“


                      --

                      Kommentar


                      • #12
                        Zitat von nikosch Beitrag anzeigen
                        Gerne. Höchste Zeit fürs Unittesting, was?
                        Dabei wäre es ja auch nicht aufgefallen, da die Methode in der Elternklasse wirklich so geheißen hat => und Netbeans hats für die Kindklasse übernommen.
                        So etwas wäre ja nicht aufgefallen.

                        Kommentar


                        • #13
                          Zitat von Chriz Beitrag anzeigen
                          Naja, dir scheint meine Antwort nicht zu schmecken, aber du hast gefragt
                          Nein deine Antwort ergibt schon Sinn, aber ich glaube das ich mich wiedermal nicht richtig ausdrücken kann, deswegen lass ichs erstmal und probier einfach rum^^

                          So letzter Versuch: Bisher habe ich gedacht, dass man soetwas die DAO nicht "testet" da es dafür die Mock Objekte gibt, da man so "am einfachsten" sichergeht, das es 40 Einträge gibt und 30 davon zB meinen Kriterien entsprechen.

                          Aber werde mich erstmal damit beschäftigten, danke für die Erklärungsversuche.

                          Du solltest deine normalen Methoden relativ klein halten und möglichst wenige Parameter verwenden, Faustregel hieß mal (ich glaube) maximal 20 Zeilen Code pro Methode. Alles was drüber geht solltest du durch Verteilen auf andere (Hilfs-)Methoden zerstückeln. Dann kannst du relativ einfach die einzelnen Methoden testen.
                          Genau das versuche ich in letzer Zeit auch zu machen.
                          Deswegen habe ich ja auch zB die DAO Klasse erstellt mit mehreren Methoden
                          PHP-Code:
                          protected function fetchAvatar($invite
                           protected function 
                          fetchData() 
                          und lese nicht gleich alles in der vB_ProfileBlock_Referrer Klasse ein.

                          Kommentar


                          • #14
                            Mit unittests hat man ja üblicherweise auch einen Test der schaut ob die Konstruktion des Objektes wie gewünscht passiert, dann sollte sowas eigentlich schon auffallen.
                            robo47.net - Blog, Codeschnipsel und mehr
                            | Caching-Klassen und Opcode Caches in php | Robo47 Components - PHP Library extending Zend Framework

                            Kommentar


                            • #15
                              Dabei wäre es ja auch nicht aufgefallen, da die Methode in der Elternklasse wirklich so geheißen hat => und Netbeans hats für die Kindklasse übernommen.
                              So etwas wäre ja nicht aufgefallen.
                              Mit unittests hat man ja üblicherweise auch einen Test der schaut ob die Konstruktion des Objektes wie gewünscht passiert, dann sollte sowas eigentlich schon auffallen.
                              Im Zweifel beim Testen der Elternklasse
                              --

                              „Emoticons machen einen Beitrag etwas freundlicher. Deine wirken zwar fachlich richtig sein, aber meist ziemlich uninteressant.
                              Wenn man nur Text sieht, haben viele junge Entwickler keine interesse, diese stumpfen Texte zu lesen.“


                              --

                              Kommentar

                              Lädt...
                              X