2010-08-10 19 views
13

Mam pytanie dotyczące testowania zapytań w transakcji. Używam transakcji MySQL od dłuższego czasu, i za każdym razem robię to, używam coś takiego:Jak przetestować transakcje MySQL?

$doCommit = true; 
$error = ""; 
mysql_query("BEGIN"); 

/* repeat this part with the different queries in the transaction 
    this often involves updating of and inserting in multiple tables */ 
$query = "SELECT, UPDATE, INSERT, etc"; 
$result = mysql_query($query); 
if(!$result){ 
    $error .= mysql_error() . " in " . $query . "<BR>"; 
    $doCommit = false; 
} 
/* end of repeating part */ 

if($doCommit){ 
    mysql_query("COMMIT"); 
} else { 
    echo $error; 
    mysql_query("ROLLBACK"); 
} 

Teraz, często zdarza się, że chcemy przetestować moją transakcję, więc mogę zmienić mysql_query("COMMIT"); do mysql_query("ROLLBACK"); , ale mogę sobie wyobrazić, że nie jest to bardzo dobry sposób testowania tego rodzaju rzeczy. Zwykle nie jest możliwe kopiowanie każdej tabeli do temp_table i aktualizowanie i wstawianie do tych tabel i usuwanie ich później (na przykład, ponieważ tabele mogą być bardzo duże). Oczywiście, kiedy kod zostanie wprowadzony do produkcji, zostanie wprowadzona odpowiednia obsługa błędów (zamiast samego drukowania błędu).

Jaki jest najlepszy sposób robienia takich rzeczy?

+0

Och kochanie ... Właśnie się dowiedziałem, że PHP nie obsługuje niczego takiego jak 'finally' lub' ensure'. –

+2

Tak, wiem ... PHP jest do bani i ja go używam ... –

+0

Istnieje teraz wsparcie dla 'finally', od PHP 5.5 – Alex

Odpowiedz

10

Po pierwsze, występuje błąd w implementacji. Jeśli zapytanie się nie powiedzie, bieżąca transakcja zostanie automatycznie wycofana, a następnie zamknięta. W związku z tym, że nadal będziesz wykonywać kwerendy, nie będą one w ramach transakcji (zostaną one przypisane do DB). Następnie, po uruchomieniu Rollback, po cichu zawiedzie. Od MySQL docs:

Rolling back can be a slow operation that may occur implicitly without the user 
having explicitly asked for it (for example, when an error occurs). 

Wyraźne polecenie ROLLBACK powinny być stosowane tylko wtedy, gdy można określić we wniosku, że trzeba wycofać (z powodów innych niż błąd zapytań). Jeśli na przykład odejmujesz środki z konta, możesz wycofać konto, jeśli dowiesz się, że użytkownik nie ma wystarczających funduszy, aby ukończyć wymianę ...

Jeśli chodzi o testowanie transakcji, robię skopiuj bazę danych. Tworzę nową bazę danych i instaluję zestaw "danych fikcyjnych". Następnie uruchamiam wszystkie testy za pomocą automatycznego narzędzia. Narzędzie faktycznie zatwierdza transakcje i wymusza wycofywanie zmian oraz sprawdza, czy oczekiwany stan bazy danych jest utrzymywany podczas testów. Ponieważ trudniej jest programowo rozpoznać stan końcowy transakcji, jeśli masz nieznany wkład do transakcji, testowanie danych na żywo (lub nawet skopiowanych z życia) nie będzie łatwe. Możesz to zrobić (i powinieneś), ale nie polegaj na tych wynikach, aby określić, czy twój system działa. Użyj tych wyników, aby zbudować nowe przypadki testowe dla zautomatyzowanego testera ...

+0

Wow, nie odtworzyłem wtedy mojej pracy domowej ... Naprawdę nie wiedziałem, że MySQL cofa się po wystąpieniu błędu. Dzięki za ten wgląd. Dzięki za resztę, jest to bardzo pomocna wiadomość. –

+0

Pewnie. Często widzę ten błąd (ponieważ jest to sprzeczne z intuicją, jeśli się tego nie spodziewasz) ... Problemem jest również debugowanie, gdy przeglądasz dziennik wykonania, próbując dowiedzieć się, dlaczego kilka zapytań został popełniony, mimo że polecenie rollback zostało wykonane ... Dlatego zawsze zgłaszam wyjątki w przypadku błędów w zapytaniach (Zwłaszcza biorąc pod uwagę, że kod produkcyjny nie powinien nigdy być błędem, chyba że coś poszło naprawdę źle (jak serwer odszedł) Więc po co próbować kontynuować, jeśli nie wiesz, co poszło nie tak?) ... O wiele trudniej jest zignorować w ten sposób ... – ircmaxell

+0

Masz rację. W swoim poście mówisz o automatycznym testerze, czy zrobiłeś to sam? Mogę sobie wyobrazić, że różne testery są potrzebne dla różnych projektów (a może nawet dla różnych przypadków testowych). –

2

Generalnie używam coś (używam pdo na moim przykładzie):

$db->beginTransaction(); 
try { 
    $db->exec('INSERT/DELETE/UPDATE'); 
    $db->commit(); 
} 
catch (PDOException $e) { 
    $db->rollBack(); 
    // rethrow the error or 
} 

Lub jeśli masz własny obsługi wyjątku, należy użyć specjalnej klauzuli dla PDOExceptions, gdzie cofnąć wykonanie. Przykład:

function my_exception_handler($exception) { 
    if($exception instanceof PDOException) { 
    // assuming you have a registry class 
    Registry::get('database')->rollBack(); 
    } 
} 
+0

Tak, ale jak przetestujesz transakcję? Chodzi mi o to, że chcę wiedzieć, jak przetestować wszystkie zapytania w jednej transakcji. W twoim przypadku zastąpię '$ db-> commit();' z '$ db-> rollBack();', ale to jest to samo co w pierwotnym pytaniu. –

3

Może mógłbyś refaktoryzować swój pierwszy przykład i użyć klasy otoki dostępowej DB?

W tej klasie otoki możesz mieć zmienną $ normalCommit = true; i metoda SetCommitMode(), która ustawia zmienną $ normalCommit. I masz metodę Commit(), która zatwierdza, jeśli ($ normalCommit == true) Lub nawet zmienna $ failTransaction, która wywołuje mysql_query ("ROLLBACK"); jeśli chcesz (możesz zdać/zawieść wiele testów sekwencyjnych).

Następnie po uruchomieniu testu można ustawić gdzieś w pliku kodu testowego: $ myDBClass-> SetCommitMode (false); lub $ myDBClass-> RollBackNextOperation (true); przed operacją, której nie chcesz, a to się nie uda.W ten sposób testowany kod nie będzie zawierał sprawdzeń typu fail/commit, tylko klasa DB je zawiera.

Normalnie kod testowy (szczególnie w przypadku testowania jednostkowego) powinien wywoływać metody SetCommitMode i RollBackNextOperation, aby przypadkiem nie zostawiać tych wywołań w kodzie produkcyjnym.

Albo możesz przekazać jakieś szalone dane do swojej metody (jeśli testujesz metodę), takie jak zmienne negatywne do zapisywania w polach UNSIGNED, a wtedy twoja transakcja powinna zakończyć się niepowodzeniem w 100%, jeśli twój kod nie popełni tego po takim Błąd SQL (ale nie powinien).

+0

Dzięki za odpowiedź, to rozsądne rozwiązanie. Gdybym mógł podzielić nagrodę, dostałbyś 50% ... –