Testuj w izolacji
Podczas opracowywania wtyczki najlepszym sposobem na przetestowanie jej jest bez ładowania środowiska WordPress.
Jeśli napiszesz kod, który można łatwo przetestować bez WordPressa, Twój kod będzie lepszy .
Każdy komponent, który jest testowany jednostkowo, powinien być testowany osobno : podczas testowania klasy wystarczy przetestować tę konkretną klasę, zakładając, że cały inny kod działa idealnie.
Z tego powodu testy jednostkowe nazywane są „jednostkami”.
Dodatkową korzyścią jest to, że bez ładowania rdzenia test będzie przebiegał znacznie szybciej.
Unikaj haków w konstruktorze
Wskazówka, którą mogę ci dać, to unikanie wprowadzania haków do konstruktorów. To jedna z rzeczy, która sprawi, że Twój kod będzie testowany w izolacji.
Zobaczmy kod testowy w OP:
class CustomPostTypes extends WP_UnitTestCase {
function test_custom_post_type_creation() {
$this->assertTrue( post_type_exists( 'foo' ) );
}
}
Załóżmy, że ten test się nie powiedzie . Kto jest winowajcą ?
- hak nie został w ogóle dodany, czy nieprawidłowo?
- metoda, która rejestruje typ postu, nie została w ogóle wywołana lub zawierała nieprawidłowe argumenty?
- w WordPress jest błąd?
Jak można to poprawić?
Załóżmy, że twój kod klasy to:
class RegisterCustomPostType {
function init() {
add_action( 'init', array( $this, 'register_post_type' ) );
}
public function register_post_type() {
register_post_type( 'foo' );
}
}
(Uwaga: w dalszej części odniosę się do tej wersji klasy)
Sposób, w jaki napisałem tę klasę, pozwala tworzyć instancje klasy bez wywoływania add_action
.
W powyższej klasie do przetestowania są 2 rzeczy:
- metoda
init
faktycznie wywołuje add_action
przekazanie jej odpowiednich argumentów
- metoda
register_post_type
faktycznie wywołuje register_post_type
funkcję
Nie powiedziałem, że musisz sprawdzić, czy istnieje typ postu: jeśli dodasz odpowiednią akcję i zadzwonisz register_post_type
, niestandardowy typ postu musi istnieć: jeśli nie istnieje, jest to problem WordPress.
Pamiętaj: kiedy testujesz wtyczkę, musisz ją przetestować swój kod, a nie kod WordPress. W testach musisz założyć, że WordPress (podobnie jak każda inna zewnętrzna biblioteka, której używasz) działa dobrze. Takie jest znaczenie testu jednostkowego .
Ale ... w praktyce?
Jeśli WordPress nie jest załadowany, jeśli spróbujesz wywołać metody klas powyżej, pojawi się błąd krytyczny, więc musisz wyśmiać funkcje.
Metoda „ręczna”
Na pewno możesz napisać swoją kpiącą bibliotekę lub „ręcznie” kpić z każdej metody. To jest możliwe. Powiem ci, jak to zrobić, ale potem pokażę ci łatwiejszą metodę.
Jeśli WordPress nie jest ładowany podczas testów, oznacza to, że możesz przedefiniować jego funkcje, np . add_action
Lub register_post_type
.
Załóżmy, że masz plik załadowany z pliku bootstrap, w którym masz:
function add_action() {
global $counter;
if ( ! isset($counter['add_action']) ) {
$counter['add_action'] = array();
}
$counter['add_action'][] = func_get_args();
}
function register_post_type() {
global $counter;
if ( ! isset($counter['register_post_type']) ) {
$counter['register_post_type'] = array();
}
$counter['register_post_type'][] = func_get_args();
}
Ponownie napisałem funkcje, aby po prostu dodać element do globalnej tablicy za każdym razem, gdy są wywoływane.
Teraz powinieneś utworzyć (jeśli jeszcze go nie masz) rozszerzenie swojej podstawowej klasy przypadków testowych PHPUnit_Framework_TestCase
: pozwala to na łatwą konfigurację testów.
Może to być coś takiego:
class Custom_TestCase extends \PHPUnit_Framework_TestCase {
public function setUp() {
$GLOBALS['counter'] = array();
}
}
W ten sposób przed każdym testem licznik globalny jest resetowany.
A teraz twój kod testowy (odnoszę się do przepisanej klasy, którą zamieściłem powyżej):
class CustomPostTypes extends Custom_TestCase {
function test_init() {
global $counter;
$r = new RegisterCustomPostType;
$r->init();
$this->assertSame(
$counter['add_action'][0],
array( 'init', array( $r, 'register_post_type' ) )
);
}
function test_register_post_type() {
global $counter;
$r = new RegisterCustomPostType;
$r->register_post_type();
$this->assertSame( $counter['register_post_type'][0], array( 'foo' ) );
}
}
Należy pamiętać:
- Mogłem wywołać te dwie metody osobno, a WordPress w ogóle nie został załadowany. W ten sposób, jeśli jeden test się nie powiedzie, wiem dokładnie, kto jest winowajcą.
- Jak powiedziałem, tutaj testuję, że klasy wywołują funkcje WP z oczekiwanymi argumentami. Nie ma potrzeby sprawdzania, czy CPT naprawdę istnieje. Jeśli testujesz istnienie CPT, to testujesz zachowanie WordPress, a nie zachowanie wtyczki ...
Fajnie .. ale to PITA!
Tak, jeśli musisz ręcznie wyśmiewać wszystkie funkcje WordPressa, to naprawdę ból. Kilka ogólnych rad, których mogę udzielić, to korzystania z jak najmniejszej liczby funkcji WP: nie musisz przepisywać WordPress, ale abstrakcyjne funkcje WP, których używasz w niestandardowych klasach, aby można je było wyśmiewać i łatwo testować.
Na przykład w odniesieniu do powyższego przykładu możesz napisać klasę, która rejestruje typy postów, dzwoniąc register_post_type
„init” z podanymi argumentami. Dzięki tej abstrakcji nadal musisz przetestować tę klasę, ale w innych miejscach kodu rejestrujących typy postów możesz skorzystać z tej klasy, kpiąc z niej w testach (zakładając, że działa).
Niesamowite jest to, że jeśli napiszesz klasę, która streszcza rejestrację CPT, możesz stworzyć dla niej osobne repozytorium, a dzięki nowoczesnym narzędziom, takim jak Composer, osadzić ją we wszystkich projektach, w których jest to potrzebne: przetestuj raz, używaj wszędzie . A jeśli kiedykolwiek znajdziesz w nim błąd, możesz go naprawić w jednym miejscu i za pomocą prostego rozwiązania composer update
wszystkie projekty, w których jest on używany.
Po raz drugi: napisanie kodu, który można przetestować w izolacji oznacza napisanie lepszego kodu.
Ale wcześniej czy później muszę gdzieś użyć funkcji WP ...
Oczywiście. Nigdy nie powinieneś działać równolegle do rdzenia, to nie ma sensu. Możesz pisać klasy, które otaczają funkcje WP, ale klasy te również muszą zostać przetestowane. Metodę „ręczną” opisaną powyżej można stosować do bardzo prostych zadań, ale gdy klasa zawiera wiele funkcji WP, może to być uciążliwe.
Na szczęście są tam dobrzy ludzie, którzy piszą dobre rzeczy. 10up , jedna z największych agencji WP, utrzymuje bardzo dobrą bibliotekę dla osób, które chcą przetestować wtyczki we właściwy sposób. Jest WP_Mock
.
Pozwala wyśmiewać funkcje WP i haki . Zakładając, że załadowałeś w swoich testach (zobacz plik repo) ten sam test, który napisałem powyżej, wygląda następująco:
class CustomPostTypes extends Custom_TestCase {
function test_init() {
$r = new RegisterCustomPostType;
// tests that the action was added with given arguments
\WP_Mock::expectActionAdded( 'init', array( $r, 'register_post_type' ) );
$r->init();
}
function test_register_post_type() {
// tests that the function was called with given arguments and run once
\WP_Mock::wpFunction( 'register_post_type', array(
'times' => 1,
'args' => array( 'foo' ),
) );
$r = new RegisterCustomPostType;
$r->register_post_type();
}
}
Proste, prawda? Ta odpowiedź nie jest tutorialem WP_Mock
, więc przeczytaj plik repo, aby uzyskać więcej informacji, ale myślę, że powyższy przykład powinien być dość jasny.
Co więcej, nie musisz pisać żadnych szydzonych add_action
lub register_post_type
samodzielnie, ani utrzymywać żadnych zmiennych globalnych.
A zajęcia WP?
WP ma też pewne klasy, a jeśli WordPress nie jest ładowany podczas uruchamiania testów, musisz je wyśmiewać.
To o wiele łatwiejsze niż kpiące funkcje, PHPUnit ma wbudowany system do kpienia z obiektów, ale tutaj chcę zasugerować kpinę . Jest to bardzo potężna biblioteka i bardzo łatwa w użyciu. Co więcej, jest to zależność odWP_Mock
, więc jeśli ją masz, masz także Kpinę.
Ale co z WP_UnitTestCase
?
Pakiet testowy WordPress został stworzony do testowania rdzenia WordPress , a jeśli chcesz przyczynić się do rdzenia, jest on kluczowy, ale użycie go do wtyczek sprawia, że nie testujesz go osobno.
Spójrz na świat WP: istnieje wiele nowoczesnych frameworków PHP i CMS, a żaden z nich nie sugeruje testowania wtyczek / modułów / rozszerzeń (lub jakkolwiek są one nazywane) przy użyciu kodu frameworka.
Jeśli tęsknisz za fabrykami, przydatną funkcją pakietu, musisz wiedzieć, że są niesamowite rzeczy .
Gotcha i wady
Zdarzało się, że brakuje przepływu pracy, który zasugerowałem tutaj: niestandardowe testowanie bazy danych .
W rzeczywistości, jeśli użyć standardowych tabel WordPress i funkcje, aby tam pisać (na najniższym poziomie $wpdb
metod) nie trzeba faktycznie zapisu danych lub testu, jeśli dane są rzeczywiście w bazie danych, po prostu mieć pewność, że odpowiednie metody są wywoływane z odpowiednimi argumentami.
Możesz jednak pisać wtyczki z niestandardowymi tabelami i funkcjami, które budują zapytania, aby tam pisać i sprawdzać, czy te zapytania działają, to na twoją odpowiedzialność.
W takich przypadkach pakiet testowy WordPress może ci bardzo pomóc, a ładowanie WordPress może być w niektórych przypadkach konieczne do uruchomienia takich funkcji dbDelta
.
(Nie trzeba mówić, że do testów używa się innej bazy danych, prawda?)
Na szczęście PHPUnit umożliwia organizowanie testów w „pakietach”, które można uruchamiać osobno, dzięki czemu możesz napisać zestaw niestandardowych testów bazy danych, w których ładujesz środowisko WordPress (lub jego część), pozostawiając wszystkie pozostałe testy bez WordPressa .
Pamiętaj tylko, aby pisać klasy, które wyodrębniają jak najwięcej operacji bazy danych, w taki sposób, aby wszystkie inne klasy wtyczek korzystały z nich, aby za pomocą prób można poprawnie przetestować większość klas bez zajmowania się bazą danych.
Po raz trzeci pisanie kodu łatwo testowalnego w izolacji oznacza pisanie lepszego kodu.
phpunit
, czy widzisz testy zakończone niepowodzeniem lub pozytywnie? Czy zainstalowałeśbin/install-wp-tests.sh
?