Wątek przeniesiony 2023-11-08 18:51 z PHP przez Riddle.

Jak testować warstwę bazy w statycznych metodach?

0

W projekcje mam bardzo duzo klas ktore sa zbudowane wylacznie ze statycznych metod ::. Bede niektore przepisywal, zeby nie byly statyczne i do nich napisze testy jednostkowe. Tylko tak zastanawiam sie czy daloby rade jakos napisac moca tych statycznych klas? Moze jakis adapter patter czy cos? I napisac testy jednostkowe do tych mockowanych klas? A pozniej powoli przeprawodzac refactoring.

5
poniatowski napisał(a):

W projekcje mam bardzo duzo klas ktore sa zbudowane wylacznie ze statycznych metod ::. Bede niektore przepisywal, zeby nie byly statyczne i do nich napisze testy jednostkowe. Tylko tak zastanawiam sie czy daloby rade jakos napisac moca tych statycznych klas? Moze jakis adapter patter czy cos? I napisac testy jednostkowe do tych mockowanych klas? A pozniej powoli przeprawodzac refactoring.

Lepiej ich nie mockować i nie testować osobno.

Jak chcesz przetestować funkcję statyczną, to zobacz gdzie jest użyta, i napisz test pod to miejsce. Np jak funkcja statyczna jest użyta w kontrolerze, to napisz test pod ten kontroler, i za jego pośrednictwem je przetestuj.

Testy jednostkowe to nie są testy jednej klasy. Spokojnie możesz użyć tych metod w teście i nie mockować nic.

Możesz pokazać przykład takiej jednej funkcji ? To Ci powiem jak można napisać test do niej

0

Niby tak, tylko wiekszosc tych statycznych metod po prostu zawieraja zapytania SQL. A ja nie chce laczyc sie z testowa baza danych w testach jednostkowych.

4
poniatowski napisał(a):

Niby tak, tylko wiekszosc tych statycznych metod po prostu zawieraja zapytania SQL. A ja nie chce laczyc sie z testowa baza danych w testach jednostkowych.

Zacznij od napisania testów które strzelają do bazy. Testy które mają to wydzielone zostaw na później.

To że metody statyczne zawierają zapytania SQL znaczy że kod nie jest łatwo testowalny. Nic z tym nie zrobisz (chyba że przepisanie). Dlatego niestety musisz napisać testy pod to co masz. Zrób test który podnosi bazę, odpala migracje, ustawia jakiś początkowy stan, odpala kod, i napisz asercje pod to. Potem kolejny test. Jakbyś teraz chciał jakoś bokiem wyciągać te SQL'e z tych metod statycznych żeby napisać testy pod nie, to obawiam się że zrobisz jeszcze gorszy mess.

Nie będzie to łatwe, to jest właśnie problem z metodami statycznymi że ciężko się je testuje.

Masz jakieś przekonanie że testy "małych rzeczy", np takie które nie strzelają do bazy są jakieś "lepsze". W przypadku legacy code nie - zrób test o szerokim zakresie.

0
poniatowski napisał(a):

Niby tak, tylko wiekszosc tych statycznych metod po prostu zawieraja zapytania SQL. A ja nie chce laczyc sie z testowa baza danych w testach jednostkowych.

Pokaż przykład.
Zawierają zapytania SQL w sensie stringi z treścią zapytania czy metody które strzelają zapytaniami?
Zamienić statyczną klasę na singleton można prostym find and replace, nie trzeba nic przepisywać a singletony chociaż nie są dużo lepsze od klas statycznych to da się już je podmieniać i testować a to dobry początek żeby dało się napisać testy do jakiegoś starego legacy kodu. Z singletona z kolei już łatwo przejść na dependency injection jeśli chcesz to pociągnąć dalej. Nie pamiętam jak to w php było ale można zacząć od konstruktora który przyjmuje zależność i przeciążenie które przekazuje singleton.Instance.

pseudokod

Foo(Zaleznosc bar) {}

Foo() {
  this(Zaleznosc.Instance);
}

// lub jeśli język na to pozwala
Foo(Zaleznosc bar = Zaleznosc.Instance) {}
4

Tytuł troche bez sensu

  1. Nie ma absolutnie żadnego problemu w testowaniu statycznych metod i nie trzeba ich przepisywać na niestatyczne. Metody statyczne testuje się nawet łatwiej, bo nie trzeba tworzyć obiektu przed wywołaniem :-)
  2. Jest problem z testowaniem efektów.
    a) przydaje się Dependency Injection - czyli, że np. metoda dostaje jako argument połączenie do bazy danych, (nawet nie trzeba tego robić przez konstruktor i zmiany metody na niestatyczną, po prostu konstruktor przydaje się jak masz więcej takich metod i szkoda Ci się babrać w podawanie argumentu za każdym razem),
    b) w przypadku testów z bazą danych często używa sie Testcontainers i widze, że PHP jakies wsparcie ma https://github.com/Frontify/testcontainers-php
0

Taki bardzo prosty przyklad klasy to np:

<?php

namespace app\helpers;

use Yii;

class CompanyHelper
{

    public static $all;

    public static function getCompanyById($companyId)
    {
        if (!self::$all) {
            $sql = 'SELECT * FROM company;';

            $params = [
                'company_id' => $companyId,
                'status' => 'active',
                'deleted' => 'false',
            ];

            self::$all = Yii::$app->db->createCommand($sql, $params)->queryAll();
        }
        
        return self::$all;
    }
}

Mam bardzo duzo takich helper klas. Masa. I tam juz jest wszytko. Zapytania SQL, troche logiki biznesowej etc etc.

Te metody statyczne sa wywolywane na kazdym poziome tj. controller, models, views.

W sumie mockowac tutaj chyba nie ma sensu. Lepiej to przetestowac. Np testami integracyjnymi czy finkcjonalnymi. Mam na mysli, ze podlaczyc testowowa baze danych i jazda z testami. Jak dobrze napisane testy z uzycie bazy danych to powinny byc w sumie lekkie. Prawda?

Wiem, ze mozna testowac kilka warst na raz. Ale np w niektorych tj testach jednostokowe wolabym chyba jednak zamykac testy w danej klasie. Czyli jak mam UserController, UserSerivce (logika) oraz UserModel (ORM). To pisze testy jednostkowe dla UserController oraz UserSerivce a dla UserModel pisze testy integracyjne z uzyciem testowej bazy danych. Czy ma to sens?

1

Yii::$app-

Masz burdel i tyle. Niezaleznie czy masz statyczną czy dynamiczną metodę: nie powninieneś czytać globali. Czy da się to jakoś zrefaktorować?

0
slsy napisał(a):

Yii::$app-

Masz burdel i tyle. Niezaleznie czy masz statyczną czy dynamiczną metodę: nie powninieneś czytać globali. Czy da się to jakoś zrefaktorować?

Wydaje mi sie, ze tak powinno dac rade. Np przy uzyciu dependency injection. Moge napisac jakas klase i interface pod ta klase. Na koniec podpiac oba w DI. I juz w klasach wstrzykiwac ten interface. Wiadomo, ze tutaj powinien w sumie powininen zostac uzyty ORM.

0

Utrzymuje moja radę: napisz testy pod kontroler i nie mockuj niczego. Przetestuj wszystko przez te kontrolery.

1
poniatowski napisał(a):
slsy napisał(a):

Yii::$app-

Masz burdel i tyle. Niezaleznie czy masz statyczną czy dynamiczną metodę: nie powninieneś czytać globali. Czy da się to jakoś zrefaktorować?

Wydaje mi sie, ze tak powinno dac rade. Np przy uzyciu dependency injection. Moge napisac jakas klase i interface pod ta klase. Na koniec podpiac oba w DI. I juz w klasach wstrzykiwac ten interface. Wiadomo, ze tutaj powinien w sumie powininen zostac uzyty ORM.

Powinieneś mieć klasę CompanyRepository, która ma wstrzyknięte db.

Tak czy owak zgadzam się z tym co pisze @Riddle : niezależnie od tego co jest pod spodem i tak warto przetestować wszystko za pomocą kontrolerów. Jak będziesz miał takie testy to refaktor wszystkiego do ludzkiej formy będzie dużo łatwiejszy

0

Na upartego da się testować także metody statyczne: https://codeception.com/07-31-2013/nothing-is-untestable-aspect-mock.html. Używałem tego X lat temu i działało. Dziś bym nie użył, a zamiast tego napisałbym test innym poziomie. Potem zabrałbym się za refaktor.

Proponuję wykonać eksperyment myślowy: zakładając, że klasa CompanyHelper zostanie funkcjonalnie bez zmian, jakie asercje byłyby w CompanyHelperTest? Czy te asercje pomogłyby wyłapać jakiś błąd? Moim zdaniem skończyłoby się na bezmyślnym dostosowywaniu testu do klasy - gdy zmieni się SQLka to zmieniamy expects w teście i tyle. To jest strata czasu.

1
Pafnucy napisał(a):

Na upartego da się testować także metody statyczne: https://codeception.com/07-31-2013/nothing-is-untestable-aspect-mock.html. Używałem tego X lat temu i działało. Dziś bym nie użył, a zamiast tego napisałbym test innym poziomie. Potem zabrałbym się za refaktor.

Proponuję wykonać eksperyment myślowy: zakładając, że klasa CompanyHelper zostanie funkcjonalnie bez zmian, jakie asercje byłyby w CompanyHelperTest? Czy te asercje pomogłyby wyłapać jakiś błąd? Moim zdaniem skończyłoby się na bezmyślnym dostosowywaniu testu do klasy - gdy zmieni się SQLka to zmieniamy expects w teście i tyle. To jest strata czasu.

Najgorsze co możesz zrobić, to żeby przetestować klasę CompanyHelper to zrobić CompanyHelperTest :|

0
slsy napisał(a):
poniatowski napisał(a):
slsy napisał(a):

Yii::$app-

Masz burdel i tyle. Niezaleznie czy masz statyczną czy dynamiczną metodę: nie powninieneś czytać globali. Czy da się to jakoś zrefaktorować?

Wydaje mi sie, ze tak powinno dac rade. Np przy uzyciu dependency injection. Moge napisac jakas klase i interface pod ta klase. Na koniec podpiac oba w DI. I juz w klasach wstrzykiwac ten interface. Wiadomo, ze tutaj powinien w sumie powininen zostac uzyty ORM.

Powinieneś mieć klasę CompanyRepository, która ma wstrzyknięte db.

Tak czy owak zgadzam się z tym co pisze @Riddle : niezależnie od tego co jest pod spodem i tak warto przetestować wszystko za pomocą kontrolerów. Jak będziesz miał takie testy to refaktor wszystkiego do ludzkiej formy będzie dużo łatwiejszy

Moze i tak, tylko to nie jest API. Wiekszosc tych kontrollerow zwraca view czyli XHTML. I jak w ogole taki kontroller przetestowac? Chodzi Ci o testy jednostkowe? (new Controller($myDependenciesMock))->actionGetAllCompaniesById(1234)?

1
poniatowski napisał(a):

Moze i tak, tylko to nie jest API. Wiekszosc tych kontrollerow zwraca view czyli XHTML. I jak w ogole taki kontroller przetestowac? Chodzi Ci o testy jednostkowe? (new Controller($myDependenciesMock))->actionGetAllCompaniesById(1234)?

Okej, no to faktycznie jest trochę ciężej.

No to masz dwa wyjścia:

  • Jeśli to jest SSR (czyli faktycznie tam jest tylko HTML), to:
    • Albo musisz niestety w testach przeparsować tego HTML'a (np używając wbudowanego w php php-domlib, można wyciągnąć elementy przez xpath), i na nim robić asercje - i to w sumie nie byłoby głupie rozwiązanie, tylko no po prostu trochę ciężkie do wprowadzania.
    • Albo wyciągnąć funkcję z kontrolera, i tą funkcje przetestować - tylko w ten sposób nie przetestujesz logiki http, więc to jest chyba gorsze.

1 użytkowników online, w tym zalogowanych: 0, gości: 1