Pomoć - interface u methodi?


#1

Dakle opet žicam pomoć…
Nikad nisam vidio da se u metodu stavlja interface? Razumijem sa klasom, ali sa metodom? Kako inicirati (u prilogu je code)?
Ideja? Objašnjenje? Bilo šta?
Na netu ništa slično ne nađoh da barem mogu shvatiti kako inicirati “Play::startPlay()” metodu (nakon “pokreni test” komentara) koja u definiciji sadrži interface…
Ima tko ideju?

Hvala!

<?php     
interface PlayerInterface
     {
          public function getId(): int;
          public function getAttempts(): int;
          public function getScore(): int;
          public function setPoints(int $points);
     }
     
    class Play
     {
          public function startPlay(PlayerInterface $player1, PlayerInterface $player2)
         {
             $player2->setPoints($player2->getScore()-1);
         }
     }

    class PlayTest extends PHPUnit_Framework_TestCase {

        public function testStartPlay()
        {
          // pokreni test
          ????????
        }

    }
?>

#2

Pa i nemozes, moras imati klasu koja implementira tvoj interface i onda nju prebacujes kao parametar

Ali ovo i nije nesto sto mozes testirati. Sta zelis nazad, sta si si zadao, prvo napisi test pa onda implementaciju itd. Ima jedna grafika koja kaze test -> implementacija -> refactoring


#3

1_Gu12DromEZ7hl_6c9pmYIQ


#4

Tako sam si i mislio. Nije moj code, morao sam pokrenuti test (kao test)
Ukratko, digao sam ruke od toga ali bih ga svakako htio shvatiti.


#5

Evo kompletnog codea (c/p):

<?php

interface HeroInterface
{

    public function getForce(): int;

    public function getImmunity(): int;

    public function getHealthPoints(): int;

    public function setHealthPoints(int $healthPoints);
}

class DamageHelper
{

    public static function getDamage(HeroInterface $attacker, HeroInterface $defender)
    {
        if ($attacker->getForce() < $defender->getForce()) {
            return 0;
        }

        return round(($attacker->getForce() - $defender->getForce())/$defender->getImmunity());
    }
}

class Fight
{

    public function makeFight(HeroInterface $attacker, HeroInterface $defender)
    {
        $damage = DamageHelper::getDamage($attacker, $defender);
        $defender->setHealthPoints($defender->getHealthPoints()-$damage);
    }
}

class FightTest extends PHPUnit_Framework_TestCase {



    public function testMakeFight()
    {
      // implement the test 


    }

}
?>

Ja odustah i dignuh ruke. Ne znam kako uopće implementirati test ovdje.
Ako ima tko ideju, volio bih znati kako.
Ideja skripte je: postoje dva “lika” (attacker i deffender) i kada započnu borbu jedan može oslabiti.
I sad ti pokreni i implementiraj test.
Je*i ga, ne znam.


#6

Nitko nema ideju? Barem od kuda krenuti?


#7

Ma ima, nego nema se vremena, kasnije popodne sjednem pa napravim primjer.


#8

Hvala ti, visim ti pivu za to :slight_smile: Znam osnove testiranja, ali ovo je (meni) napredno i ne znam uopće od kuda bih krenuo.


#9

Pa ajmo ovako. Najlakse ti je zapitati se kako bi to rucno testirao, odnosno sto bi te zanimalo kod rucnog testiranja da si kazes “aha ok, ovo radi”?

Vjerojatno bi si kreirao neku implementaciju Hero interfacea, pa da mozes poslati attackera i defendera u bitku, i na kraju bi provjerio vrijednost defendera da vidis da se umanjila tocno za onoliko koliko ocekujes? :slight_smile:

Prije svega ja bih osobno neke stvari malo drukcije nazvao, kao i izbjegavao staticke pozive koje radis (DamageHelper::getDamage), jer samo se skrivaju dependencyji na taj nacin i otezava testiranje sto vodi do losijeg koda. Idealno, svi dependencyji bilo koje klase bi uvijek trebali biti injectani kroz konstruktor, tako da cim otvoris klasu tocno se u konstruktoru vidi o cemu dependa. Ponavljam, idealno, u idealnom svijetu gdje svi pisemo dobar kod.

Dakle, prije svega nesto drukciji naminzi:
Fight -> BattleSimulator
DamageHelper -> DamageCalculator

Inace ako imas potrebu nazvati klasu da ima suffix Helper vjerojatno moze bolje.

I onda bih se zapitao sto mi je bitno da se ovdje odvije, koje poruke se trebaju razmijeniti/dogoditi.

Kod BattleSimulatora mi je bitno da se defenderu posalje poruka da postavi novo stanje healthpointsa, odnosno da se oduzmu healthpointi za onoliko koliko je slabiji od napadaca.

Primjerice: ako je snaga napadaca 10, a defendera 8, bitno mi je da se defenderu oduzmu 2 healthpointa, odnosno da se pozvala metoda na defenderu setHealthpoints tocno jednom i da se proslijedio broj njegovog trenutnog stanja umanjen za 2.

Kad sam napisao iznad “koje su se poruke trebale razmijeniti”, tu mislim na to – ako je steta 2 , poruke koje se trebaju razmijeniti je da BattleSimulator zatrazi defendera trenutno stanje healtha, i zatim mu posalje poruku da postavi novo stanje na X vrijednost (gdje je X = trenutno stanje umanjeno za 2).

Zelim da mi BattleSimulator samo postavlja novo stanje healthpointa defenderu ilitiga da oduzima healthe defenderu. Ne zelim da mi BattleSimulator radi i kalkulaciju stete, jer bi onda radio dvije stvari – i kalkulaciju damagea i slanje poruke defenderu koje mu je novo stanje healthpointsa. Ako postujemo SOLID principe, onda znamo da time krsimo ovo “S” – Single responsibility principle.

Okej dakle zvuci mi da ce simulator morati pitati neki izvor da mu kaze za koliko treba oduzeti health defenderu, pa ajmo taj izvor nazvati DamageCalculator koji ce biti zaduzen za racunanje damagea.
BattleSimulator je tu samo da postavi healthpointse defenderu na neku vrijednost, odnosno onoliko koliko mu je DamageCalculator rekao.

Okej, idemo onda napisati test za taj slucaj i razbiti u dijelove radi lakseg razumijevanja:

Primijeti kako na prve dvije linije stubbam DamageCalculator klasu. Ne zelim/ne moram koristiti konkretnu implementaciju DamageCalculatora dok testiram BattleSimulator iz jednog ili vise razloga:

  • jer unit testiram samo BattleSimulator i mozda me ne zanima me nista drugo nego da se odvije tocno ona komunikacija i razmijene tocno one poruke izmedju objekata koje ocekujem
  • jer cu napisati i dodatno odvojeni integracijski test koji ce koristiti stvarnu implementaciju pa cu tamo to testirati
  • jer mozda u trenutku testiranja BattleSimulatora niti nemam implementaciju DamageCalculatora (npr mozda je samo interface/contract o kojem ovisi BattleSimulator)

Dakle, stubbam ju ovdje

i sve sto govorim je “ako te netko pita da izracunas damage, vrati broj 4”. Mogao sam to napisati i drukcije, npr “ocekujem da te tocno jednom netko pita da izracunas damage, i vrati mu broj 4”. Sve zavisi sto bi me kao developera zadovoljilo i dalo mi miran san. :slight_smile:

Zatim mockam defendera

Ono sto ovdje govorim je “kad te netko trazi da mu kazes trenutno stanje healthpointsa, vrati broj 10”, te postavljam assertion na mocku da je metoda “setHealthPoints” pozvana tocno jednom s brojem 6. Zasto 6? Pa ako je trenutno stanje 10, a znamo da ce DamageCalculator vratiti 4 jer smo ga tako stubbali, onda ocekujem da se pozove ta metoda koja ce postaviti novu vrijednost health pointsa na 6 jer 10 - 4 = 6.

Na kraju testa se pokrene kod tj meso svega:

i testovi mi failaju jer nisam nikakav kod napisao, odnosno dobit cu gresku da metoda “fight” na klasi BattleSimulator ne postoji.

Okej, zatim napravim implementaciju

i pokrenem testove – prolaze.

I to je to. Istestirao sam ono sto je meni bilo bitno da se dogodi kod simulatora.

Dok sam pisao te testove, mozda nisam imao ni implementaciju kalkulatora niti heroja.
U ovom slucaju jesam imao implementaciju kalkulatora, i mogu napisati novi set integracijskih testova ako mislim da mi je potrebno da se pobrinem da konkretne implementacije simulatora+kalkulatora rade onako kako mislim da bi trebali, ali u ovom slucaju mi mozda nije potrebno jer je jako jednostavan case, ali cu zato napisati unit testove za implementaciju kalkulatora.

Primijeti da kad idem raditi konkretnu implementaciju kalkulatora simulator sam vec napisao i testirao, a iako simulator ovisi o kalkulatoru da ga testiram nije mi bila potrebna prvo implementacija kalkulatora. :slight_smile:

Okej kalkulator je ful jednostavan. Ako je defender snazniji od attackera, steta je 0. U protivnom vratimo onu vrijednost za koliko je napadac snazniji. Super, ajmo napisati testove za oba slucaja, a zatim i implementirati:

Pokrenem testove - prolaze. Super, imam i kalkulator rijesen sada.

Implementaciju heroja (odnosno klasu koja implementira Hero interface) niti nemam jos jer ne znam sto ce biti niti kako ce se ponasati, tko zna, to je neka odluka koju cu donijeti u buducnosti i s kojom se ne moram zamarati sada dok testiram simulaciju borbe.

Link do repoa: https://github.com/toniperic/wmhr


#10

E sad tebi visim pivu! Hvala ti, izvrsno objašnjeno i danas ću se poigrati s tim čim sjednem za komp. Super, hvala još jednom!


#11

Molio bih pomoć s terminologijom i značenjem.
Stub sam shvatio i razumijem (zašto se koristi i kako ga koristiti), ALI mock ne shvaćam. Googlao ali ga ne “procesuiram” :slight_smile:
Da li je mocking kada kreiraš “zamjenu prave” klase i koja vraća predefinirane rezultate koji mogu ovisiti o inputu ali bez “oslanjanja” na rezultate koji bi se dobili iz “prave klase”?
Slično kao stub, ali ne i isto?


#12

Nope, to sto si opisao je stub.

Stub -> kreiras “zamjenu prave klase” kako si rekao, ali definiras output. Iz naseg primjera gore, to bi bilo ovo:

Mock je objekt koji ce verificirati odredjeno ponasanje.
Iz naseg primjera ranije, to bi bilo ovo:

Verificiramo da ce se metoda setHealthPoints pozvati tocno jednom gdje je prvi argument 6.

Mock

The practice of replacing an object with a test double that verifies expectations, for instance asserting that a method has been called, is referred to as mocking .

Stub

The practice of replacing an object with a test double that (optionally) returns configured return values is referred to as stubbing

Iz PHPUnit docsa: Mock vs Stub


#13

Eto. Hvala! To su već 2 pive koje ti visim :smiley: