• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Dwa inne wyniki przy tych samych wartościach. [PHPUnit]

VPS Starter Arubacloud
0 głosów
561 wizyt
pytanie zadane 29 stycznia 2017 w PHP przez HaKIM Szeryf (87,590 p.)

Hej!

Po 5 godzinie spędzonej nad jednym błędem, decyduję się zapytać.

Otóż, testuję czy dane, podane w formularzu, są poprawnie walidowane przez bibliotekę Respect\Validation.

Gdy robię to w kontrolerze:

//[...]

class SignUpNewUserController extends Controller
{
    public function index(Request $request, Response $response): ResponseInterface
    {
        $validator = new SignUpNewUserValidation();

        $validator->validate($request, new DataToValidate([
            'login' => v::noWhitespace(),
            'email' => v::email(),
            'password' => v::notEmpty()->equals('confirm'),
        ]));

        var_dump($validator->error);

        //[...]
    }
}

Poprzez GET walidator nie zwraca żadnego błędu.

Dane wprowadzone w GET:

login=HaKIM&
email=example%40email.com&
password=confirm&
confirmPassword=confirm

0 błędów.

Pora na test:

//[...]
    public function testCorrectlyValidatingNewUser()
    {
        $data = [
            'login' => 'Test',
            'email' => 'hello@gmail.com',
            'password' => 'confirmPassword',
            'confirmPassword' => 'confirmPassword'
        ];

        $request = mockery::mock(Request::class);
        $request->shouldReceive('getParam')
            ->andReturn($data);

        $dataToValidate = new DataToValidate([
            'login' => v::notEmpty(),
            'email' => v::email(),
            'password' => v::equals($request->getParam(['confirmPassword']))
        ]);

        $validator = new SignUpNewUserValidation();
        $validator->validate($request, $dataToValidate);

        $this->assertEquals([], $validator->error);
    }
[...]

Ten sam wynik zwraca mi błąd w walidowanym email'u.

object(Respect\Validation\Validator)#531 (3) {
  ["rules":protected]=>
  array(1) {
    ["0000000015b587de0000000028d9d949"]=>
    object(Respect\Validation\Rules\Equals)#521 (3) {
      ["compareTo"]=>
      array(4) {
        ["login"]=>
        string(4) "Test"
        ["email"]=>
        string(17) "example@email.com"
        ["password"]=>
        string(15) "confirmPassword"
        ["confirmPassword"]=>
        string(15) "confirmPassword"
      }
      ["name":protected]=>
      string(8) "Password"
      ["template":protected]=>
      NULL
    }
  }
  ["name":protected]=>
  string(8) "Password"
  ["template":protected]=>
  NULL
}


Time: 130 ms, Memory: 12.00MB

There was 1 failure:

1) Tests\Validation\SignUpNewUserValidationTest::testSignUpNewUserStrategy
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
 Array (
+    0 => '- Email must be valid email'
 )

/opt/lampp/htdocs/paradox/tests/Validation/SignUpNewUserValidationTest.php:40

Przy okazji z var_dump na dane walidowane.

Ja już nie mam pomysłów.

Ps. Jakby ktoś miał jakąś poradę, może nie odnośnie tego błędu, to proszę pisać w komentarzach, chętnie ulepszę kod. 

2 odpowiedzi

+1 głos
odpowiedź 29 stycznia 2017 przez Comandeer Guru (599,730 p.)
wybrane 31 stycznia 2017 przez HaKIM
 
Najlepsza

Może palnę głupotę, ale… Request::getParam nie powinno zwracać pojedynczej wartości a nie całą tablicę?

komentarz 31 stycznia 2017 przez HaKIM Szeryf (87,590 p.)

Zacny framework. 

Aczkolwiek:

Brak przymusu VO.

Co to jest VO?

Jedyne skojarzenia z VO to nazwa Virutal Object <- Co może w ogóle nie istnieć. :p 

Active record (no need Data mapper)

Active Record można traktować jako zaletę przy nieskomplikowanej warstwie biznesowej. Gdy takowa ma miejsce, nadchodzi czas na DataMapper. Czyli, na tę chwilę, Doctrine. Mam nadzieję, że yii nie wymusza na użytkownikach eloquenta. :D

komentarz 31 stycznia 2017 przez efiku Szeryf (75,160 p.)
VO - Value Object, co mim zdaniem jest zaletą, gdy dobrze zrobi się domenę : P

Well, w DDD wchodź Hakim jak już dobrze ogarniesz OOP i będziesz sprawnie się poruszał, znaczy.. gdy uznasz, że już te wzorce cię nie kręcą.

Złap mnie na irc, to Ci podrzucę jakieś materiały o DDD.

Ja tam wole DataMapper ;)
komentarz 31 stycznia 2017 przez event15 Szeryf (93,790 p.)

Już kiedyś w tym pracowałem.

Przyjemny kod.

Brak przymusu VO.

Świetna walidacja po stronie modelu.

Brak problemu ze staticami. (podpowiedzi phpdoc etc.)

Tworzenie resta w 5min :D (dosłownie tyle mi zajeło stworzenie podstawowego resta)

Active record (no need Data mapper)

Przejrzysta dokumentacja.

Gii (generator) i Debug toolbar

integracja z Codeception (testy)

Odniosę się do kilku tylko. 

VO nie jest przymusem frameworka ani nawet narzędzia do mapowania obiektów na tabele. Jest to raczej konsekwencja przemyślanego projektowania aplikacji. Dość wygodnie obsługuje się takie obiekty w Encji za pomocą Doctrine. Nie udało mi się zrobić tego nigdy na czymś opartym o Active Record. Nie wiem w ogóle skąd się to wzięło w tej liście :D

 

Model - nie jest w znaczeniu biznesowym, pracując z Yii2 zakładamy, że modelem jest logika zachowań obiektów jak i jednocześnie Query do baz danych przeplatane z tym. Im więcej kodu, tym częściej przenika to do kontrolerów więc imo to wadliwy sposób twozenia aplikacji. Aczkolwiek to jedynie drobna uwaga. Swoją drogą generalnie programista powinien zapewnić poprawność danych zanim trafią do jakiejkolwiek formy modelu. Jeśli to model sprawdza czy dane są prawidłowe, to jest to delikatne naruszenie. Często naruszane.

Active Record - spoko sprawa, gdy ma się do 5 tabel czy 5 relacji różnego typu między nimi. Przy większej liczbie tabel i relacji zaczyna być męczący i mocno namnaża liczbę metod obsługi bazy.

Gii - nie znam nie wiem co to jest. 

Codeception - świetne narzędzie! Imo niedoceniane. Mimo iż spija śmietankę ze wszystkich dobrze napisanych fw to tworzenia testów. Do tego dodaje dużo od siebie. Jeśli ktoś myśli o wielu etapach testowania od jednostkowych do funkcjonalnych - polecam :) 

komentarz 31 stycznia 2017 przez HaKIM Szeryf (87,590 p.)

Swoją drogą generalnie programista powinien zapewnić poprawność danych zanim trafią do jakiejkolwiek formy modelu. Jeśli to model sprawdza czy dane są prawidłowe, to jest to delikatne naruszenie. Często naruszane.

Wait... Jak wysyłamy request, np. w formularzu to ma on trafić do.. Specjalnej klasy obsługujące dane, następnie stamtąd przesyłać do jakiejś klasy walidującej czy wszystko jest OK i na sam koniec model?

To daje 4 klasy na jedną rzecz i wychodziłby, że SRP.

Model obliczający liczby: Zamiast trzech klas (Kontroler -> walidacja -> model) nie lepiej po prostu machnąć w setterze modelu strict type, pierdyknąć int'a metodę pomocniczą isEmpty (w sumie to i tego nie potrzebujemy, gdy będzie empty to zwróci błąd a 0 to też cyfra) i z głowy?

komentarz 31 stycznia 2017 przez event15 Szeryf (93,790 p.)
Model w znaczeniu biznesowym jest encją lub zbiorem encji i i wartości których zadaniem jest jedynie być wzorcem dla danych. W mocnym uproszczeniu.

W kontrolerze uruchamia się na przykład komendę, która na przykład robi AddUser(). Możesz sobie zrobić walidator dla requestu w kontrolerze (w jakiejkolwiek formie) następnie w najprostszym przypadku w implementacji komendy wybierasz sobie entityManagera i za pomocą Repozytorium wrzucasz dane do bazy.

To, co jest lepiej jest zależne od wielkości aplikacji i wymogów jej niezawodności.

 

Przy czymś większym, polecam to https://zawarstwaabstrakcji.pl/20170130-cqrs-w-praktyce-wprowadzenie-php/

https://zawarstwaabstrakcji.pl/20151020-save-repository-from-save/

Generalnie jestem mocnym przeciwnikiem MVC, gdzie Model ma mocno dowolne znaczenie, w moim mniemaniu zbyt wiele mu można. Jest inne podejście, które jednak wymaga dużo zakuwania, ciężkiej pracy i jednocześnie takie, które rzadko kiedy się przydaje przy malutkich aplikacyjkach. Jednak warto je znać chociażby dla samego skilla w pisaniu rozbudowanego kodu.
+2 głosów
odpowiedź 31 stycznia 2017 przez event15 Szeryf (93,790 p.)
<?php 
public function testCorrectlyValidatingNewUser()
    {
        $data = [
            'login' => 'Test',
            'email' => 'hello@gmail.com',
            'password' => 'confirmPassword',
            'confirmPassword' => 'confirmPassword'
        ];
 
        $request = mockery::mock(Request::class);
        $request->shouldReceive('getParam')
            ->andReturn($data);
 
        $dataToValidate = new DataToValidate([
            'login' => v::notEmpty(),
            'email' => v::email(),
            'password' => v::equals($request->getParam(['confirmPassword']))
        ]);
 
        $validator = new SignUpNewUserValidation();
        $validator->validate($request, $dataToValidate);
 
        $this->assertEquals([], $validator->error);
    }

Na moje to w ogóle nie walidujesz $data. Skąd to v::email() - gdzie to zasilasz danymi?
Kolejna sprawa - nie szanujesz Single Responsiblitity Principle. Istnieją takie metody jak (w zależności od fw do testów) let, before, setup itp. 

W jednej metodzie :

  • Szykujesz dane do testu kontrolera - a co gdy będzie tych metod więcej (a za chwilę będzie)
  • Mockujesz Request
  • Sprawdzasz co zwraca Request
  • Szykujesz dane do validacji - w mojej opini źle.  (przy okazji używasz jednolinijkowej nazwy klasy do tego małą literą)
  • tworzysz validatora który sprawdza te dane
  • assertujesz błędy

Moje pytanie brzmi: Po co? Po co walidujesz validator własnoręcznie pisany w metodzie do testowania kontrolera? Czemu w jednej metodzie robisz tak wiele rzeczy? Możliwe nawet, że błąd nie twki w samym kodzie tylko w przekombinowaniu. Zbyt wiele rzeczy w jednej chwili chcesz zrobić. 

Najpierw przeczytaj https://docs.google.com/document/d/1uC_jfFm7jWwspVBNZxHpC_bVJwmR862aIAHLpK6d0TA/edit?usp=sharing

Tu sobie kiedyś robiłem roboczo-notatki z książek o TDD gdy czytałem. 

W rzeczywistości tworzysz kontroler, który sam w sobie ma sprawdzanie poprawności danych. Moim zdaniem - zwalnia cię to z testowania tego kontrolera w PHPUnicie czy czymkolwiek. W Codeception lub Behacie możesz zrobić prosty test specyfikacji, czy dodanie nowego usera kończy się powodzeniem i czy jest stosowny komunikat w przypadku niepowodzenia. 

 

Inną sprawą jest to, że podstawową cechą testów jest to, iż są szybkie, proste, czytelne i łatwe w tworzeniu jak i pisaniu. Twoje testy łamią nieco kilka tych zasad. Generalnie powinieneś po prostu dać dane wejściowe do testu, assertować je w jak najprostszy sposób i tyle. Niepotrzebnie wrzucasz tam jeszcze swoje walidatory. 

komentarz 31 stycznia 2017 przez HaKIM Szeryf (87,590 p.)

Dzięki za link jak i spory review.

Na moje to w ogóle nie walidujesz $data. Skąd to v::email() - gdzie to zasilasz danymi? 

//[...]
    public function testCorrectlyValidatingNewUser()
//[...]

Te kwadraty oznaczają resztę kodu dla „poprawnego” działania testu. Klasa, importy, składowe i reszta. Dane biorą się z request, bo tego wymaga klasa walidacji - a raczej metody getParam() <- Ta klasa również powinna dostać refaktor, po usunięciu związania z Request nie byłoby w ogóle tego problemu... Test miał na celu testować tę klasę:

class SignUpNewUserValidation implements ValidationFactory
{
    public $error;

    public function validate(Request $request, DataToValidate $dataToValidate)
    {
        foreach ($dataToValidate->params as $index => $data)
        {
            try {
                $data->setName(ucfirst($index))
                    ->assert($request->getParam($index));
                var_dump($data);
            } catch (NestedValidationException $exception) {
                $this->error[] = $exception->getFullMessage();
            }
        }

        return $this;
    }
}

 

Kolejna sprawa - nie szanujesz Single Responsiblitity Principle. Istnieją takie metody jak (w zależności od fw do testów) let, before, setup itp. 

Szanuję, jest to jedna z moich ulubionych zasad SOLID. Acz... Powiem tak: Gdy mam buga, nie patrzę na koszty. W 9/10 przypadkach kod pogarsza się, wszędzie latają var_dump'y(Nawet w SignUpNewUserValidation jest jeden) i exit'y (xdebuger <- czaję, do wdrożenia) co powoduje po robocie (Znalezieniu błędu) mocną refaktoryzacje.

Ten kod, testu, czy inne, są tymi "W Robocie" - Stąd ten syf. To było dosłownie 5h. nieustannych prób znalezienia błędu. Co lepsze już po około 1h. odkryłem że getParam() zwraca mi całą tablicę, acz nie wierzyłem, a raczej zapomniałem, że komputer (Mockery) nie jest intuicyjny i może mi zrobić taki numer.

W rzeczywistości tworzysz kontroler, który sam w sobie ma sprawdzanie poprawności danych. Moim zdaniem - zwalnia cię to z testowania tego kontrolera w PHPUnicie czy czymkolwiek. W Codeception lub Behacie możesz zrobić prosty test specyfikacji, czy dodanie nowego usera kończy się powodzeniem i czy jest stosowny komunikat w przypadku niepowodzenia. 

Też się zastanawiałem czy warto... Jakby nie patrzeć to walidacja jest sama w sobie testem, a poprawność zwracanych danych poprzez walidowanie powinny zapewnić testy Respect\Validation. Test stworzyłem świeżo po spojrzeniu okiem na mockery, chcąc wykorzystać wiedzę w praktyce, a jakoż walidacja wydawała mi się najprostszą rzeczą do walidacji (Sprawdzanie rezultatu, mockowanie Request  i voilà).

Poza tym to testowanie każdego możliwego exception dla każdej z reguł dla każdego z inputów nie widziało mi się.

Prościej i bardziej fachowo: Musiałbym dla pełnego testu walidować każdą z flag  dla parametru czy aby na pewno działa i zwraca to, co powinna (Co przecież ma zapewniać biblioteka, nie ja). 

Te rzeczy miałem podać w temacie, widocznie byłem na tyle wyczerpany, że machnąłem bardziej okrojony temat.

 (przy okazji używasz jednolinijkowej nazwy klasy do tego małą literą)

Jeżeli chodzi o: 

'login' => v::notEmpty(),

To taka nazwa jest zalecana w samym repo Respect/Validation. Na początku uznałem, że jest to nazwa lipna i nic nie mówi, acz zrozumiałem, że to dlatego:

$userValidator = v::attribute('name', v::stringType()->length(1,32))
                  ->attribute('birthdate', v::date()->age(18));

Po zamianie na pełną nazwę obiektu:

$userValidator = Validator::attribute('name', Validator::stringType()->length(1,32))
                  ->attribute('birthdate', Validator::date()->age(18));

A atrybutów może być więcej, trochę beznadzieja. Choć, w sumie u mnie takie coś nie występuje, więc mógłbym przeprowadzić refaktoryzację tej nazwy.

PS. Nie staram się bronić w żaden sposób stanu kodu, jest gówniany i należałoby go pielęgnować od początku do końca.

komentarz 31 stycznia 2017 przez event15 Szeryf (93,790 p.)
jak mozesz daj link do projektu :)
komentarz 31 stycznia 2017 przez HaKIM Szeryf (87,590 p.)
Brak. :/

Ostatnio mam sporą zawieruchę, a tak będzie pewnie do piątku, zastałem na 3 dni z feature registration (Cały weekend poszedł w las). Przynajmniej parę nowych rzeczy się dowiedziałem. :)

Podobne pytania

0 głosów
1 odpowiedź 148 wizyt
+1 głos
2 odpowiedzi 298 wizyt
pytanie zadane 11 kwietnia 2021 w Systemy operacyjne, programy przez Skrzypek Nowicjusz (130 p.)
0 głosów
2 odpowiedzi 185 wizyt
pytanie zadane 12 lutego 2020 w Sieci komputerowe, internet przez Dexter24 Nowicjusz (190 p.)

92,451 zapytań

141,261 odpowiedzi

319,073 komentarzy

61,853 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Akademia Sekuraka 2024 zapewnia dostęp do minimum 15 szkoleń online z bezpieczeństwa IT oraz dostęp także do materiałów z edycji Sekurak Academy z roku 2023!

Przy zakupie możecie skorzystać z kodu: pasja-akademia - użyjcie go w koszyku, a uzyskacie rabat -30% na bilety w wersji "Standard"! Więcej informacji na temat akademii 2024 znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...