Hem / Enhetstestning i WordPress

Enhetstestning i WordPress

TL; DR

Enhetstester är kod som testar att dina funktioner returnerar vad du har tänkt dig. Du har större nytta av detta ju mer komplext ditt projekt är.

Vad är enhetstester?

Du kanske redan har hört talas om enhetstester, eller t o m använt dem själv? Inte? Det borde du börja med!

Enhetstester är kod som testar kod. Närmare bestämt att de metoder man har skrivit returnerar det resultat man har tänkt sig. Framförallt har man nytta av det i mer komplexa projekt där en klass kan användas av flera andra klasser och ju mer generell klassen är, desto mer vältestad behöver den vara.

Enhetstestning i WordPress

I WordPress är testning lite speciell. Dels är flödet lite annorlunda i WordPress jämfört med många ramverk. I WordPress, liksom i Drupal och en del andra system, använder man sig t ex av hooks och filter, vilket leder till att det kan vara svårt att spåra flödena i koden. Dels är det svårt att testa vy-delen, såsom teman och visuella funktioner. I många frameworks finns det stöd för att testa vyer också, men i WordPress är vi hänvisade till Selenium-tester och liknande. Det finns helt enkelt ganska mycket kod som inte går att enhetstesta.

Verktyg

WordPress har dock ett bra stöd för enhetstester. Dels finns det enhetstester för alla core-funktioner, dels finns det verktyg för att enkelt sätta upp skelett till tester för egna projekt med hjälp av WP-CLI. Vi använder dessutom några ytterligare verktyg som hjälper oss med vårt testarbete.

  • Travis CI – Continous Integration innebär att man kontinuerligt bygger sitt system och kör de tester man har för att upptäcka problem. Frekvensen kan vara olika, men ofta är det en del av flödet när man committar sin kod. För publika projekt på github är Travis gratis att använda. Man kan även sätta upp en matris av browsers, WordPress-version och Php-version som man vill testa.
  • Scrutinizer – Detta verktyg hjälper till att öka kodkvalité genom att leta efter möjliga buggar och förslag till kodförbättringar.
  • CodeSniffer – I CodeSniffer kan man sätta upp regler för hur ens kodstandard ser ut, t ex om variabler ska använda CamelCase, eller om kodblock ska påbörjas på samma rad som föregående eller ny rad. Programmet hjälper en sedan att upptäcka om man man bryter mot dessa regler och kan även hjälpa till att laga e del av problemen.

Liksom nästan alla php-projekt använder sig WordPress av biblioteket PHPUnit. När man skriver sitt test så skapar man ett objekt av den klass man vill testa. Man skickar in olika parametrar och jämför sedan det resultat man får tillbaka med det resultat man förväntar sig. Det finns en stor uppsättning jämförelsemetoder, som assertTrue, assertEquals, assertException osv.

Vad man bör och inte bör testa

Vad ska man testa, då? Det finns några saker som kan vara lämpliga att testa.

  • Korrekta värden – Skicka in värden du förväntar dig att metoden kommer att få ta emot och jämför med vad den borde returnera. Om du har en metod som tar emot en sträng, så skicka in en sträng och se till att du får tillbaka rätt resultat.
  • Extremvärden – Vad händer om du skickar in flyttal till en metod som förväntar sig heltal? Vad händer om du skickar in en tom sträng i en metod?
  • Felhantering – Kontrollera vad som händer om man använder metoden på ett sätt den inte är byggd för, t ex om du skickar in parametrar med fel format (t ex sträng istället för tal) eller kanske fel antal parametrar.

I WordPress finns det en del specifika saker man kan testa.

  • Är mina Custom Post Types korrekt uppsatta och kan man skapa nya objekt med dem?
  • Är pluginets roller skapade och har de korrekta rättigheter?

Det ligger alltså ett stort ansvar på dig att skriva lämpliga tester, men precis som med vilken programmering som helst finns det alltid möjlighet att gå tillbaka och förbättra sina tester. Faktum är att det är ett bra sätt att jobba med buggar. När man upptäcker ett oönskat beteende skriver man ett nytt test som hanterar detta.

Saker som är mindre lämpliga att testa är t ex saker som WordPress testar själv. Det finns t ex ingen mening med att testa att WordPress core-funktioner gör det de ska eftersom alla stabila WordPress-versioner bygger med de tester som finns.

Fixtures

En sak som kan vara intressant att känna till är fixtures. Om ens metod använder sig av egna datastrukturer vill man ofta kunna kontrollera resultatet. Fixturer är ett sätt att kontrollera data genom att sätta upp färdiga data-objekt. På så sätt kan man alltid veta att ens data innehåller det man tror att de ska göra. Om du t ex vill kontrollera att din metod som hämtar alla bokningar mellan 5-10 april så kan du med en fast uppsättning data kontrollera att din metod fungerar som den ska. Värt att notera att stödet för fixturer i WordPress inte är så utbyggt, åtminstone inte än.

Synlighet

Ett problem med att testa metoder handlar om synlighet. Eftersom man skapar ett objekt av den klass man ska testa så kommer man inte åt klassens privata metoder. Det finns lite olika filosofier kring hur man ska hantera dessa. Många tycker att man inte ska testa privata metoder, men om man behöver så finns det en lösning som heter ReflectionClass. Med hjälp av den skapar man ett objekt av sin klass och byter helt enkelt temporärt synlighet på den.

Kodblock

Exempel

För att börja med tester kan du använda WP-CLI för att sätta upp ett test-skelett.

wp scaffold plugin-tests coolstrings

Så här kan den skapade mappstrukturen se ut:

Mappvy

I vår plugin har vi skapat två metoder, en för att generera strängar och en för att ta bort oönskade tecken ur den:

public function generateCoolString( $length = 20 ) {
    $seed = 'abcdefghijklmnopqrstuvwxyz'
            . 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
            . '0123456789!@#$%^&*()';
 
    $rand = '';
    $len = strlen( $seed ) - 1;
    for ( $i = 0; $i < 20; $i++ ) {
        $k = rand( 0, $len );
        $rand .= $seed[ $k ];
    }
 
    return $rand;
}
 
public function sanitizeCoolString( $string ) {
    $allowed_chars = 'aeiuAEIU';
 
    $result = '';
    for ( $i = 0; $i < strlen( $string ); $i++ ) {
        if ( strpos( $allowed_chars, $string[ $i ] ) !== FALSE ) {
            $result .= $string[ $i ];
        }
    }
    return $result;
}

Våra tester, som ligger i tests/test-cool-string.php, ser ut såhär:

public function test_generate_cool_string() {
    $cool_object = new CoolString();
 
    // Test some good values
    $test_values = [
        20,
        30,
        100
    ];
 
    foreach ( $test_values as $value ) {
        $cool_string = $cool_object->generateCoolString( $value );
 
        $this->assertTrue( is_string( $cool_string ) );
        $this->assertEquals( strlen( $cool_string ), $value );
    }
 
    // Test some bad values
    $test_values = [
        -5,
        'kalle',
        null,
        false
    ];
 
    foreach ( $test_values as $value ) {
        $cool_string = $cool_object->generateCoolString( $value );
 
        $this->assertFalse( $cool_string );
    }
}
 
public function test_sanitize_cool_string() {
    $cool_object = new CoolString();
 
    // Test some good values
    $test_values = [
        'V4A#Pa1OSzuJ&8q0Tr$^' => 'AaOu',
        'eEVLmrQuxjl$q0MGzwNs' => 'eEu',
        'wUZckBvr0T4xJMYo*POH' => 'UoO'
    ];
 
    foreach ( $test_values as $key => $value ) {
        $cool_string_sanitized = $cool_object->sanitizeCoolString( $key );
 
        $this->assertTrue( is_string( $cool_string_sanitized ) );
        $this->assertEquals( $cool_string_sanitized, $value );
    }
}

Kör testerna genom att navigera till plugin-mappen och köra phpunit.

Console Fail

Med hjälp av våra tester kan vi se att vi har gjort några fel i koden.

  • Vi har hårdkodat hur lång strängen vi returnerar ska vara.
  • Vi har råkat glömma att o/O ska vara godkända bokstäver.
  • Vi hanterar inte felaktiga parametrar.

Med några ändringar i vår kod går testerna igenom:

public function generateCoolString( $length = 20 ) {
    if ( !is_numeric( $length ) || $length <= 0 ) {
        return false;
    }
 
    // [...]
 
    for ( $i = 0; $i < $length; $i++ ) {
 
    // [...]
 
public function sanitizeCoolString( $string ) {
    $allowed_chars = 'aeiouAEIOU';
 
    // [...]
}

Nu går våra tester igenom!

Console OK

TDD

Till sist, några ord om Test Driven Development, eller TDD. TDD är ett sätt att utveckla som utgår från att man först skriver sina tester, därefter kodar och refaktorerar tills testerna går igenom och upprepar i en cirkel.

En del belackare tycker att TDD tar för lång tid att använda, eftersom man även måste lägga tid på att skriva tester och inte bara på att implementera funktioner, medan förespråkarna anser att man snabbt sparar in den tiden i färre buggar.

WordPress lämpar sig bara delvis för TDD eftersom en betydande del av den kod man producerar inte kan testas.