| Autor |
Správa |
johno


Ujo Janko
Založený: 23 Okt 2002 Príspevky: 1158 Bydlisko: Bratislava
|
| Predmet: Test Driven Development session #1 - Základy |
 |
|
Nazdar,
tak som našiel dobrovoľníka, ktorý by si chcel skúsiť TDD a zhodli sme sa na tom, že to budeme skúšať tu. Je na to viac dôvodov a ten najdôležitejší je evangelizácia TDD a objektového programovania v radoch PHP. Aby som to ja nemusel stále dokola opakovať, tak to bude verejné. A možno sa naučím niečo aj ja.
Keby sa chcel niekto pridať, niečomu nerozumel tak nech sa tu smelo ozve. Je to tu presne kvôli tomu.
Začnem úplne od začiatku, takže v krátkosti o TDD. Je to metóda (aj keď to nesprávne volajú metodológia) programovania, ktorá postaví na hlavu všetko to, čo ste doteraz vedeli o programovaní. Nepreháňam a nerobím si srandu.
Založená je totiž na tom, že testy kódu sa píšu skôr ako samotný kód, ktorý majú testovať. Preto testom riadený vývoj. Tieto testy nie sú na papieri alebo v hlave, sú to normálne programy, ktoré sa dajú vykonávať. Tieto programy testujú správnosť funkčnosti niečo iného. Na konci spustenia testu sú len dve možnosti. Zelená = všetko v pohode, červená = niečo nefunguje.
Pri kódovaní sa postupuje, takto:
Na začiatku chceme nejakú novú funkcionalitu. Napíšeme teda test, ktorý túto funkcionalitu otestuje. Skúsime test spustiť. Samozrejme máme červenú, lebo sme vlastne nič nenakódovali.
Teraz urobíme minimálne úsilie, aby test prešiel. Keď píšem minimálne, tak vravím MINIMÁLNE. Nič viac. Uvidíte neskôr, prečo to tak zdôrazňujem. Čiže potom máme zelenú.
Dôležitý krok je refaktoring. Ak mám zelenú, tak po sebe poupratujem. Vyhodím duplicity, ...
Teraz požadovanú funkcionalitu mám. Môžem pridať ďaľšiu. Pridám test, červená, pridám kód, zelená, refaktoring. Toto sa volá TDD mantra (red-green-refactor).
Čo ideme vyvýjať
Po dlhom zvažovaní sme sa zhodli na tom, že ideme robiť niečo ako Routing v RoR. Kto to nepozná nech si to predstaví, tak že mám predpis
| kód: |
'articles/:year/:month/:day',
:controller => 'articles',
:action => 'find_by_date',
:year => /\d{4}/, :month => /\d{1,2}/, :day => /\d{1,2}/
|
A hodím mu articles/2006/11/01 a chcem, aby to pochrúmal a vrátil mi pekné krásne pole s tými hodnotami array(controller => 'articles', action => 'findbydate', 'year' => 2006, ...);
Vytvoril som repository na http://code.google.com/p/dcf-workshop/ čo bude slúžiť na všetky kódy čo sa tu vyskytnú. Ak niekto nevie používať SVN, tak nech sa to rýchlo naučí.
Toľko na začiatok. _________________ TC/OPT™ Group Leader
Naposledy upravil johno dňa Št Nov 09, 2006 11:58 pm, celkom upravené 2 krát |
So Nov 04, 2006 8:21 pm |
|
 |
hvge


Lepší ako hvge
Založený: 22 Okt 2002 Príspevky: 992 Bydlisko: Praha / Dubnica - mesto patich žaluďov
|
| Predmet: |
 |
|
Skvele!! _________________ intype & fshl & game & ex .net & ex game developer |
So Nov 04, 2006 8:43 pm |
|
 |
johno


Ujo Janko
Založený: 23 Okt 2002 Príspevky: 1158 Bydlisko: Bratislava
|
| Predmet: |
 |
|
Dobre, takže keď si teraz spravíte
| kód: |
| svn checkout http://dcf-workshop.googlecode.com/svn/trunk/ dcf-workshop |
a spustíte routes_tests.php tak by ste mali vidieť krásne zelené okienko, že všetky testy prebehli dobre. Nič to preto, že ich je nula.
Takže teraz je čas zaviesť nejaké pravidlá pre prácu s repository.
1. Do repository sa robí commit, len keď je zelená, čiže všetky testy v tvojej working copy sú ok. Budeme tam síce teraz pristupovať asi len dvaja, ale toto je veľmi dôležité pravidlo.
2. Commit treba robiť čo najčastejšie, ale len ak je splnené prvé pravidlo. U nás by mal byť commit po každom novom pridanom teste a funkcionalite.
3. To čo, nie je v repository neexistuje. Nikoho netankuje, čo máš vo svojej zabordelenej working copy.
Takže, pripravení? _________________ TC/OPT™ Group Leader |
So Nov 04, 2006 9:08 pm |
|
 |
Michal Hantl


Ružový praclík
Založený: 12 Júl 2006 Príspevky: 55
|
| Predmet: Ready! |
 |
|
Připřaven:) _________________ Eighty percent of success is showing up. |
So Nov 04, 2006 9:11 pm |
|
 |
johno


Ujo Janko
Založený: 23 Okt 2002 Príspevky: 1158 Bydlisko: Bratislava
|
| Predmet: |
 |
|
Takže prvé čo potrebujeme je takzvaná userstory. Konkrétny príklad funkcionality, ktorá nám chýba. Niečo viac ako požiadavka. Tá prvá je vždy trochu ťažká, tak pomôžem.
Keď špecifikujem vzor pre cestu "articles/:year", tak keď mu potom strčím "articles/2006", tak chcem, aby mi to vrátilo array('year' => 2006)
Skús na toto napísať test. Najprv sem. Potom uvidíme. _________________ TC/OPT™ Group Leader |
So Nov 04, 2006 9:20 pm |
|
 |
Michal Hantl


Ružový praclík
Založený: 12 Júl 2006 Príspevky: 55
|
| Predmet: |
 |
|
Tak tu je můj pokus
| kód: |
testYear()
{
$router = new Router();
$sid = "articles/2006";
$route = $router->route($sid);
$this->assertEqual($route["year"],2006);
}
|
_________________ Eighty percent of success is showing up. |
So Nov 04, 2006 9:40 pm |
|
 |
johno


Ujo Janko
Založený: 23 Okt 2002 Príspevky: 1158 Bydlisko: Bratislava
|
| Predmet: |
 |
|
Veľmi dobre na začiatok. Aj keď dve chyby. Jedna horšia, jedna malá.
Aby sme sa posúvali rýchlejšie, tak sa pýtam, že odkiaľ vie Router aký vzor má na starosti? V požiadavke som hovoril o articles/:year ale v teste to nie je. Niekde bude chyba.
No a tá malá, ale sakra dôležitá vec je nazývanie vecí správnymi názvami. Router nie je úplne zlé, ale skôr by som skúsil niečo ako RoutePattern. Metóda route() nehovorí nič o tom čo sa deje, niečo ako parse by bolo lepšie, ale myslím, že lepšie by bolo nazvať to extractParameters(); $sid taktiež nedáva veľký význam. Je to skôr $uri. Nakoniec, keď mám Vzor cesty, tak z $uri potrebujem vybrať(extrahovať) také parametre, ktoré vzoru vyhovujú.
No a samotný názov testu je zle. Netestujeme rok. Rok v kontexte Routingu nemá žiadny význam skôr by sa to mohlo volať. testRoutePatternCanExtractParameter()
Takže by som to prepísal na
| kód: |
testRoutePatternCanExtractParameter() {
$pattern = new RoutePattern('articles/:year');
$parameters = $pattern->extractParameters('articles/2006');
$this->assertEqual($parameters['year'], 2006);
} |
Takže napíš tento test a skús napísať minimálny kód, ktorý tento test naplní. Zdorazňujem minimálny! _________________ TC/OPT™ Group Leader |
So Nov 04, 2006 10:02 pm |
|
 |
Michal Hantl


Ružový praclík
Založený: 12 Júl 2006 Príspevky: 55
|
| Predmet: |
 |
|
Doplnil jsem tedy route_pattern.php, s minimem kódu
| kód: |
<?php
class routePattern
{
private $pattern;
function __construct($pattern) {
$this->pattern = $pattern;
}
function extractParameters($uri) {
$parameters = explode('/',$uri);
return array('year' => $parameters[1]);
}
}
?>
|
_________________ Eighty percent of success is showing up. |
So Nov 04, 2006 10:24 pm |
|
 |
johno


Ujo Janko
Založený: 23 Okt 2002 Príspevky: 1158 Bydlisko: Bratislava
|
| Predmet: |
 |
|
Dobre. Ideme ďalej. Asi by nebolo od veci keby som teraz dal parsovať do toho istého vzoru 'blabla/2006' a malo by mi to hodiť false, lebo to ten vzor neobsahuje. Čiže navrhujem test
| kód: |
testExtractReturnsFalseOnUnsatisfiedPattern() {
$pattern = new RoutePattern('articles/:year');
$parameters = $pattern->extractParameters('blabla/2006');
$this->assertFalse($parameters);
} |
Len tak, pre zaujímavosť, tak úplne minimum kódu by bolo v extractParameters()
| kód: |
| return array('year' => 2006); |
O tom načo je toto dobré niekedy inokedy. _________________ TC/OPT™ Group Leader |
So Nov 04, 2006 10:34 pm |
|
 |
Michal Hantl


Ružový praclík
Založený: 12 Júl 2006 Príspevky: 55
|
| Predmet: |
 |
|
Upravený kód :
| kód: |
<?php
class RoutePattern {
private $pattern;
public function __construct($pattern) {
$this->pattern = explode("/",$pattern);
}
public function extractParameters($uri) {
$parameters = explode('/', $uri);
if ($parameters[0] === $this->pattern[0]) {
return array('year' => $parameters[1]);
} else return false;
}
}
?> |
_________________ Eighty percent of success is showing up. |
So Nov 04, 2006 10:49 pm |
|
 |
johno


Ujo Janko
Založený: 23 Okt 2002 Príspevky: 1158 Bydlisko: Bratislava
|
| Predmet: |
 |
|
V tom kóde vidím natvrdo year. Skúsim teraz niečo, čo ťa to donúti dať to preč. Máme vzor 'articles/:id' a $uri 'articles/4'. Test si napíš sám. _________________ TC/OPT™ Group Leader |
So Nov 04, 2006 11:06 pm |
|
 |
Michal Hantl


Ružový praclík
Založený: 12 Júl 2006 Príspevky: 55
|
| Predmet: |
 |
|
přibylo do test.php
| kód: |
public function testRoutePatternCanExtractId() {
$pattern = new RoutePattern('articles/:id');
$parameters = $pattern->extractParameters('articles/4');
$this->assertEqual($parameters['id'], 4);
}
|
změna v routing_tests.php
| kód: |
public function extractParameters($uri) {
$parameters = explode('/', $uri);
if ($parameters[0] === $this->pattern[0]) {
return array([b]substr($this->pattern[1],1)[/b] => $parameters[1]);
} else return false;
}
|
_________________ Eighty percent of success is showing up. |
So Nov 04, 2006 11:14 pm |
|
 |
johno


Ujo Janko
Založený: 23 Okt 2002 Príspevky: 1158 Bydlisko: Bratislava
|
| Predmet: |
 |
|
Ten test by som nazval skôr testRoutePatternCanExtractDifferentPlaceholderName()
Vidím, že minimálne zmeny už robiť vieš, skúsime to trochu zamotať. Máme 'articles/category/:category' a 'articles/category/php' _________________ TC/OPT™ Group Leader |
So Nov 04, 2006 11:18 pm |
|
 |
Michal Hantl


Ružový praclík
Založený: 12 Júl 2006 Príspevky: 55
|
| Predmet: |
 |
|
do testu přibylo
| kód: |
public function testRoutePatternCanExtractOnDifferentLevel() {
$pattern = new RoutePattern('articles/category/:category');
$parameters = $pattern->extractParameters('articles/category/php');
$this->assertEqual($parameters['category'], 'php');
}
|
route_pattern.php se nám změnil
| kód: |
<?php
class RoutePattern {
private $pattern = array();
public function __construct($pattern) {
$patternParts = explode('/',$pattern);
foreach ($patternParts as $pPart)
{
if ($pPart[0]==':') {
$this->pattern[]=array('parameter', substr($pPart,1));
} else {
$this->pattern[]=array('equals', $pPart);
}
}
}
public function extractParameters($uri) {
$extracted = array();
$parameters = explode('/', $uri);
foreach ($this->pattern as $i=>$pPart)
{
if ($pPart[0]==='equals') {
if ($parameters[$i]!==$pPart[1]) return false;
} else {
$extracted[$pPart[1]]=$parameters[$i];
}
}
return $extracted;
}
}
?>
|
_________________ Eighty percent of success is showing up. |
So Nov 04, 2006 11:43 pm |
|
 |
johno


Ujo Janko
Založený: 23 Okt 2002 Príspevky: 1158 Bydlisko: Bratislava
|
| Predmet: |
 |
|
Ok, takže test si pekne nazval. Teraz k tomu kódu. Chcelo by to jeden refactoring step. Nezabúdaj na mantru: red-green-refactor. Refaktoring znamená v tomto prípade vyhodiť ten bordel z konštruktora a dať to priamo do logiky.
| kód: |
class RoutePattern {
private $pattern = array();
public function __construct($pattern) {
$this->pattern = explode('/', $pattern);
}
public function extractParameters($uri) {
$extracted = array();
$parameters = explode('/', $uri);
foreach ($this->pattern as $index => $part) {
if($part[0] == ':') {
$name = substr($part, 1);
$extracted[$name] = $parameters[$index];
} elseif($part !== $parameters[$index]) {
return false;
}
}
return $extracted;
}
} |
Ok ideme ďalej. Viem, že vieš čo teraz príde. 'articles/:name' 'articles/category/php' by malo hodiť false. _________________ TC/OPT™ Group Leader |
So Nov 04, 2006 11:53 pm |
|
 |
Michal Hantl


Ružový praclík
Založený: 12 Júl 2006 Príspevky: 55
|
| Predmet: |
 |
|
Kvůli lomítku ba konci patternu to po přidání testu prošlo, do testu přišlo :
| kód: |
public function testRoutPatternRequiresExactMatch() {
$pattern = new RoutePattern('articles/:name/');
$parameters = $pattern->extractParameters('articles/category/php');
$this->assertFalse($parameters);
}
|
teď to udělám test i functionalitu pro 'articles/:name' :
test pozměněn :
| kód: |
public function testRoutPatternRequiresExactMatch() {
$pattern = new RoutePattern('articles/:name');
$parameters = $pattern->extractParameters('articles/category/php');
$this->assertFalse($parameters);
}
|
do route_pattern.php přidána podmínka stejného počtu činitelů patternu a uri
| kód: |
if (count($parameters)!==count($this->pattern)) {
return false;
}
[quote][/quote] |
_________________ Eighty percent of success is showing up. |
Ne Nov 05, 2006 12:03 am |
|
 |
johno


Ujo Janko
Založený: 23 Okt 2002 Príspevky: 1158 Bydlisko: Bratislava
|
| Predmet: |
 |
|
Ok, výborne. A teraz to začne byť zaujímavé. Chceme niečo ako defaults.
Malo by to fungovať tak, že keď mám napríklad 'articles/:action' a nastavím, že default pre action je 'browse', tak pri 'articles' mi vráti array('action' => 'browse');
Skús ten test najprv sem, lebo sa trochu mení API triedy. Aby sme sa dohodli. _________________ TC/OPT™ Group Leader |
Ne Nov 05, 2006 12:17 am |
|
 |
Michal Hantl


Ružový praclík
Založený: 12 Júl 2006 Príspevky: 55
|
| Predmet: |
 |
|
do testu
| kód: |
public function testRoutePatternCanApplyDefaultValue() {
$pattern = new RoutePattern('articles/:action');
$pattern->setDefaultFor("action","browse");
$parameters = $pattern->extractParameters('articles');
$this->assertEqual($parameters["action"], "browse");
}
|
RoutePattern->extractParameters se nám změnil a pak rozdělil :
| kód: |
public function extractParameters($uri) {
$parameters = explode('/', $uri);
if (count($parameters) > count($this->pattern)) {
return false;
}
$extracted = array();
foreach ($this->pattern as $index => $part) {
if($part[0] == ':') {
$name = substr($part, 1);
$extracted[$name] = $this->getValue($name, $parameters[$index]);
} elseif($part !== $parameters[$index]) {
return false;
}
}
return $extracted;
}
function getValue($name, $parameter) {
if (isset($parameter)) {
return $parameter;
} else {
$default = $this->getDefaultFor($name);
if ($default!==null) {
return $default;
} else {
return false;
}
}
}
|
_________________ Eighty percent of success is showing up. |
Ne Nov 05, 2006 12:42 am |
|
 |
johno


Ujo Janko
Založený: 23 Okt 2002 Príspevky: 1158 Bydlisko: Bratislava
|
| Predmet: |
 |
|
Prerušujeme na chvíľu vysielanie, lebo u mňa je kvôli inému error reportingu červená a u Michala zelená. _________________ TC/OPT™ Group Leader |
Ne Nov 05, 2006 1:00 am |
|
 |
johno


Ujo Janko
Založený: 23 Okt 2002 Príspevky: 1158 Bydlisko: Bratislava
|
| Predmet: |
 |
|
Spravil som refaktoring na:
| kód: |
public function extractParameters($uri) {
$parameters = explode('/', $uri);
if (count($parameters) > count($this->pattern)) {
return false;
}
$extracted = array();
foreach ($this->pattern as $index => $part) {
if($part[0] == ':') {
$name = substr($part, 1);
if(count($parameters) <= $index) {
$value = $this->defaults[$name];
} else {
$value = $parameters[$index];
}
$extracted[$name] = $value;
} elseif($part !== $parameters[$index]) {
return false;
}
}
return $extracted;
}
|
Pridal som ti tam nejaké TODO testy. Ale nepodvádzaj a rob ich pekne postupne.
| kód: |
public function TODO_testRoutePatternReturnsFalseOnShortPath() {
$pattern = new RoutePattern('articles/:action');
$parameters = $pattern->extractParameters('articles');
$this->assertFalse($parameters);
}
public function TODO_testRoutePatternReturnsDefaultThatIsNotInPattern() {
$pattern = new RoutePattern('articles/:category');
$pattern->setDefaultFor('controller', 'ArticleController');
$parameters = $pattern->extractParameters('articles/php');
$this->assertEqual($parameters['category'], 'php');
$this->assertEqual($parameters['controller'], 'ArticleController');
}
public function TODO_testRoutePatternCanExtractMultipleValues() {
$pattern = new RoutePattern('articles/:id/:action');
$parameters = $pattern->extractParameters('articles/4/comments');
$this->assertEqual($parameters['id'], 4);
$this->assertEqual($parameters['action'], 'comments');
}
public function TODO_testRoutePatternRetursFalseOnMissingPlaceholderValue() {
$pattern = new RoutePattern('articles/:id/:action');
$parameters = $pattern->extractParameters('articles/4');
$this->assertFalse($parameters);
}
|
Tak idem definitívne spať. _________________ TC/OPT™ Group Leader |
Ne Nov 05, 2006 1:27 am |
|
 |
Michal Hantl


Ružový praclík
Založený: 12 Júl 2006 Príspevky: 55
|
| Predmet: |
 |
|
Trochu jsem to upravil pro první dva testy a ostatní už fungovaly samy.
| kód: |
public function extractParameters($uri) {
$parameters = explode('/', $uri);
if (count($parameters) > count($this->pattern)) {
return false;
}
$extracted = $this->defaults;
foreach ($this->pattern as $index => $part) {
if($part[0] == ':') {
$name = substr($part, 1);
if(count($parameters) <= $index) {
if (isset($this->defaults[$name])) {
$value = $this->defaults[$name];
} else return false;
} else {
$value = $parameters[$index];
}
$extracted[$name] = $value;
} elseif($part !== $parameters[$index]) {
return false;
}
}
return $extracted;
}
|
_________________ Eighty percent of success is showing up. |
Ne Nov 05, 2006 9:55 am |
|
 |
johno


Ujo Janko
Založený: 23 Okt 2002 Príspevky: 1158 Bydlisko: Bratislava
|
| Predmet: |
 |
|
Fajn. Nemám ani čo dodať. Takže rovno ideme na novú požiadavku. Chceme aby niektoré placeholdery mali na sebe regexp, ktorý musí byť splnený. Skús napísať test, ktorý zoberie 'articles/:id' a :id musí vyhovovať reguláru /^[0-9]+$/
Tentoraz napíš test najskôr sem, aby sme sa dohodli na API. _________________ TC/OPT™ Group Leader |
Ne Nov 05, 2006 10:41 am |
|
 |
Michal Hantl


Ružový praclík
Založený: 12 Júl 2006 Príspevky: 55
|
| Predmet: |
 |
|
nejsem si moc jistý názvem:)
| kód: |
public function testRoutePatternIdValidatesInput() {
$pattern = new RoutePattern('articles/:id');
$parameters = $pattern->extractParameters('articles/blabla');
$this->assertFalse($parameters);
}
|
_________________ Eighty percent of success is showing up. |
Ne Nov 05, 2006 10:54 am |
|
 |
johno


Ujo Janko
Založený: 23 Okt 2002 Príspevky: 1158 Bydlisko: Bratislava
|
| Predmet: |
 |
|
Takže 2 veci. Ako vie RoutePattern podľa čoho validovať? V požiadavke to vystupuje, v teste nie. Niečo bude zle. Samozrejme by si na to, že ti niečo chýba došiel sám, hneď ako by si začal kódovať tento test. Môžeme to skúsiť aj tak.
Druhá vec je ten názov. Rozmýšľaj v kontexte routingu (to je slovo!). Čo je to id? Keď si prečítam meno testu, tak musím okamžite vedieť, čo testuješ a ako to ma dopadnúť. Skús znova. _________________ TC/OPT™ Group Leader |
Ne Nov 05, 2006 10:59 am |
|
 |
johno


Ujo Janko
Založený: 23 Okt 2002 Príspevky: 1158 Bydlisko: Bratislava
|
| Predmet: |
 |
|
A ešte dve veci. Jedna sakra dôležitá a jedna o názvoch.
Takže tá dôležitá. Všimol som si, že si pridal test do repository. To je pekná vec, ale úplne zle. Týmto pádom je totiž repository v červenom stave. Predstav si, že by niekto teraz spravil checkout a pozeral by ako debil, že prečo mu to nejde. Repository preto musí byť vždy v zelenom stave. Keď spravíš checkout musíš mať working copy. Asi sme sa zle pochopili. Ja som chcel aby si ten test dal len sem.
Keď už silou mocou chceš dávať nejaký test do repository tak mu daj prefix TODO_ aby sa nevykonal. Tak ako som to spravil ja včera večer. Potom je jasné, čo je na pláne, ale repository sa nerozbije.
Ok a teraz tá druhá. Zmenil som názvy 2 testov, aby lepšie popisovali, to čo testujú.
| kód: |
testRoutePatternReturnsFalseWhenPatternDoesNotMatch()
testRoutePatternRequiresExactMatch() |
na
| kód: |
testRoutePatternReturnsFalseWhenPatternDoesNotMatchPath()
testRoutePatternReturnsFalseOnLongerPath() |
_________________ TC/OPT™ Group Leader |
Ne Nov 05, 2006 11:12 am |
|
 |
|