En primer om reflektion i PHP (och hur det spelar in i enhetstestning)
Under de senaste veckorna har jag skrivit om enhetstestning för medlemmarna på sajten (och något som jag planerar att fortsätta göra i nästa inlägg). Det är något som jag tror; om du skriver kod på serversidan bör du göra det.
Naturligtvis är det lättare för mig att säga det än att göra det, så även om jag försöker se till att jag gör ett bra jobb med det, finns det alltid utrymme för mig att förbättra. Jag säger det mer som en personlig magkontroll än något annat, så jag avviker.
Ett av de begrepp som ofta dyker upp under tester är idén om att testa värderingarna av privata attribut. Låt oss till exempel säga att du har en setter, men att du inte nödvändigtvis har en getter för just det värdet.
Det är lätt att säga "Jaha, då måste du skriva en getter", men det är inte alltid fallet. Jag menar om du lagrar information inom klassen som inte behöver exponeras för tredjepartsklasser?
Hur ska vi då skriva tester mot den typen av data när vi vill komma åt den men inte har möjlighet att göra det och inte vill äventyra integriteten i vårt arbete?
Det är där reflektion spelar in.
Reflektion i PHP
Specifikt, för att förstå hur man inspekterar värdet på en given variabel utifrån och in, måste man veta hur man använder reflektion.
Lyckligtvis ger PHP oss ett kraftfullt API för att göra det, och det är förmodligen värt att gå in på de djupa detaljerna om det i ett annat inlägg. Men för den här, kommer att hålla sig till grunderna.
En fungerande definition
Först en titt på vad reflektion betyder. PHP-manualen definierar det som :
Klassen ReflectionClass rapporterar information om en klass.
Men jag tycker att det är värt att ha något lite mer gediget. Låt oss gå med något sånt här åtminstone för det här inlägget:
Reflektion är hur ett program ska inspektera sig själv och modifiera sig själv under körning.
Kanske är det inte bra; kanske inte.
Men det kommer att tjäna syftet med detta inlägg.
Läsa ett värde via reflektion
Låt oss anta att du för det här inlägget har en namnrymdsklass på AcmePluginAPIClient och den har en egenskap som heter användarnamn. Vi ska ta en titt på hur en mycket grundläggande implementering av detta kan se ut senare.
Naturligtvis skulle det vara mycket mer konkretiserat i ett verkligt plugin.
Låt oss dock säga att du vill ställa in värdet på attributet och sedan läsa dess värde. Varningen är att fastigheten är markerad som privat och det finns inget sätt att läsa den från utsidan.
Det är här reflektion kommer väl till pass. Det vill säga att vi kan använda en del av programmet för att titta på sig självt och rapportera tillbaka vad det ser. (Reflektion, förstår? Det är som när vi vill veta vad som händer med oss själva och ingen annan är i närheten så vi tittar in i en spegel och ser vad som finns där.)
För att göra detta måste du göra fem saker:
- Instantiera klassen du vill testa,
- Ställ in värdet på variabeln,
- Ta en instans av ReflectionClass för klassen vi vill testa,
- Ställ in dess egendom till tillgänglig,
- Läs värdet.
Så här är en serie sammanfattningar som ger de steg som krävs för att göra exakt det.
1 Instantiera klassen
<?php
namespace AcmePlugin;
class APIClient
{
private $username;
// Other functions for class implementation...
}
2 Ställ in värdet
<?php
namespace AcmePlugin;
class APIClient
{
private $username;
public function setUsername($username)
{
$this->username = $username;
}
// Other functions for class implementation...
}
3 Instantiera reflekterade objekt
<?php
namespace AcmePluginTests;
use AcmePluginAPIAPIClient;
class APIClientTest
{
public function setUsername()
{
// Instantiate the class.
$client = new APIClient();
$username = 'tommcfarlin';
$client->setUsername($username);
// Now get a reflected instance of the class.
$reflectedClient = new ReflectionClass('AcmePluginAPIAPIClient');
// More to come...
}
}
4. Ställ in egenskapen, markera den som tillgänglig
<?php
namespace AcmePluginTests;
use AcmePluginAPIAPIClient;
class APIClientTest
{
public function setUsername()
{
// Instantiate the class.
$client = new APIClient();
$username = 'tommcfarlin';
$client->setUsername($username);
// Now get a reflected instance of the class.
$reflectedClient = new ReflectionClass('AcmePluginAPIAPIClient');
$usernameProperty = new ReflectionObject($client);
$usernameProperty->setAccessible(true);
// More to come...
}
}
5 Läs värdet
<?php
namespace AcmePluginTests;
use AcmePluginAPIAPIClient;
class APIClientTest
{
public function setUsername()
{
// Instantiate the class.
$client = new APIClient();
$username = 'tommcfarlin';
$client->setUsername($username);
// Now get a reflected instance of the class.
$reflectedClient = new ReflectionClass('AcmePluginAPIAPIClient');
// Grab a reference to the private property by making it accessible.
$usernameProperty = new ReflectionObject($client);
$usernameProperty->setAccessible(true);
// And finally, read it's value.
$usernameValue = $usernameProperty->getValue($client);
}
}
Och det är den grundläggande primern om reflektion
Vid det här laget bör det här ge dig lite grundläggande information om vad Reflection är, hur man använder det och varför det är användbart, särskilt när det gäller enhetstestning.
Detta är ett av de koncept som kan bli mer komplexa eftersom PHPs reflektions-API är ganska kraftfullt (men relativt lätt att förstå). Men när du kopplar ihop det med enhetstestning finns det många saker som kan göras.
Min skamlösa plugg
Med det sagt, om du är intresserad av att lära dig in-och-outs för den här typen av saker, tveka inte att kolla in det område som endast är för medlemmar på webbplatsen. Jag bygger, varje vecka, en eftersläpning av saker för att hjälpa oss fokusera på att anamma bättre metoder som WordPress-utvecklare.
Enhetstestning, reflektion och mer är bara den senaste delen av det.
