The reasoning at the base of Test Driven Development

Acest articol explica modul de gandire care este necesar pentru Test-Driven-Development…

This might sound obvious, but it’s not. Most of the time people want to test that the code does what the spec wanted it to do. That’s wrong! You know what you want your code to do, but how can you make sure the computer understands your code? The answer is unit tests.

Testele trebuie sa verifice 1. daca codul face cu succes ceea ce trebuie sa faca cu un input valid, dar 2. ceea ce multi uita: daca codul refuza sa faca ceea ce ar face, daca primeste input invalid.

Multi uita sa testeze daca the SUT fails correctly, adica punctul 2.

2 Likes

Sau mai pe intelesul tuturor, testele trebuie sa testeze atat ca functia (sau metoda, sau clasa) sa faca ce trebuie, cat si sa refuze orice input invalid.

Un lucru am observat, insa, pe forumul asta, discutiile sunt teoretice, dar de multe ori nu se da nici un exemplu in cod, desi de multe ori s-ar scuti multe discutii pe langa subiect daca s-ar introduce cod, in argument. Orice cod, inclusiv pseudo-cod… Asa ca m-am decis sa fiu primul care sa incerce asta.

<?php
// functia de testare
function test($function_name, $function_input, $desired_output){
    $function_call = $function_name.'('.$function_input.');';
    $function_output = exec($function_call);
    if($function_output != $desired_output)
        echo 'Failed: '.$function_name.
             '<br/>-want: '.$desired_output.
             '<br/>-got : '.$function_output.
             ' :<br/>-input:<br/>'.$function_input;
    else
        echo Passed: '.$function_name.
             '<br/>-got: '.$desired_output.
             '<br/>-input: <br/>'.$function_input;
}

Vom folosi functia de testare de mai sus, in exemplele de mai jos. Stiu ca poate nu este facuta profesional, insa consider necesar a se explica si motorasele din spatele testingului, nu doar “how to use”…

<?php
// funtia pe care sa o testam
function division($dempartit,$impartitor){
    $rezultat=$dempartit / $impartitor;
    return $rezultat;
}

// testele propriuzise
test('division', '3,1',3);       // 3/1 = 3
test('division', '3,2','1.5');   // 3/2 = 1.5
test('division', '3,0',null);    // 3/0 = null

In exemplul dat, am testat impartirea a doua numere. Dupa cum puteti vedea, functia data a esuat sa evite cazul impartirii la zero, asa ca vom rescrie functia si rulam aceleasi teste, adaugand un nou test, pentru a ne asigura ca returnam ce trebuie.

<?php

/* functia pe care sa o testam, dupa ce am gasit un caz in care nu functioneaza 
 * si am reparat-o. Testele raman neschimbate. Nu funcita decide ce teste 
 * scriem, ci testele decid ce functie scriem, deci functia decide datele de 
 * intrare si datele de iesire pe care le dorim.
 */
function division($dempartit,$impartitor){
    if($impartitor==0)
        return null;
    $rezultat=$dempartit / $impartitor;
    return $rezultat;
}

// testele propriuzise
test('division', '3,1',3);       // 3/1 = 3
test('division', '3,2','1.5');   // 3/2 = 1.5
test('division', '3,0',null);    // 3/0 = null
test('division', '3,0','null');  // null != String(null)

Acum toate testele ar trebui sa treaca. Daca am facut vre-o greseala, va rog s-o corectati!

1 Like

Cred că Uncle Bob și-a uzat corzile vocale spunând același lucru :smiley:

1 Like
function_call = function_name.'('.function_input.');';
function_output = exec(function_call);

pentru asa ceva exista functia call_user_func()

Si, vezi ca ai uitat ca scrii codul in PHP. Ai uitat sa folosesti “$”

1 Like

[quote=“GarryOne, post:5, topic:2373”]
Ai uitat sa folosesti “$”
[/quote]Ce-i drept, n-am mai scris de cam multisor cod PHP… eh, facultatea asta… Am corectat postul…

Nu stiam de call_user_func() … In orice caz, varianta scrisa de mine ar putea fi mai usor de inteles de cineva care nu prea are tangente cu php-u…

Edit: Chiar daca as fi folosit call_user_function(), tot as fi preferat sa afisez numele functiei… De ce? Pai hai sa mai adaug o functie, la exemplul de mai sus, cand testez totul, s-ar putea sa ma incurc in output-urile testelor…

<?php
function wierd_math($first, $second, $tird, $fourth){
    $result = division($first, $second) + division($tirth, $fourth);
    return $result;
}

test('wierd_math','3,1,4,2',5); // 3/1 + 4/2 = 3 + 2 = 5
test('wierd_math','3,1,3,2',4.5); // 3/1 + 4/2 = 3 + 1.5 = 4.5
test('wierd_math','3,1,3,2','4.5'); // just to be sure we did it right...
test('wierd_math','3,1,4,0',null); // 3/1 + 4/0 = 3 + null = ...null?

Ultimul test pica, asa ca modificam functia wierd_math

<?php
function wierd_math($first, $second, $tird, $fourth){
    if($second == 0 || $fourth == 0)
        return null;
    $result = division($first, $second) + division($tirth, $fourth);
    return $result;
}

Edit2: De notat ca nu am folosit nici un try-catch…

De fapt, din experienta mea si alui Uncle Bob si a multor altora, de obicei ajungi la cel mai performant aloritm daca programezi definind cazurile exceptionale intai. Oarecum in legatura si cu Transformation Priority Premise.

Ca asa, dupa ce ai tratat cazurile rele, caile de executie exceptionale, tot ce ramane este algoritmul tau.

In alte cuvinte Defensive Programming.

Si pentru ca este Craciun, un blog post de la Uncle Bob care m-a marcat in urma cu cativa ani. Cum sa ajungi sa implementezi bubble sort si cum sa eviti sa implementezi bubble sort si sa te trezesi cu quick sort. (din pacate site-ul nu mai merge: aici a fost blog postul)

2 Likes

Am avut de explicat cuiva TDD-ul, m-am gandit sa folosesc codul de mai sus si am incercat sa-mi dau seama timp de vre-o doua ore de ce nu merge… foloseam exec in loc de eval

AIci aveti codul functional, iar mai jos rezultatul (ce afiseaza):

<?php


// function to test stuff
function test($function_name, $function_input, $desired_output){
    $function_call = 'return '.$function_name.'('.$function_input.');';
    $output = array();
    $function_output = eval($function_call);
    /* echo '<br/>['. $function_call. ']<br/>{' .$function_output. '}<br/>'; */
    $desired_output_ = ($desired_output == null)
        ? '`null`' : $desired_output;
    $function_output_ = ($function_output == null)
        ? '`null`' : $function_output;
    $function_input_ = ($function_input == null)
        ? '`null`' : $function_input;
    // finished debug variables cleaning
    if($function_output != $desired_output) {
        echo '[X] Failed: &#9;'.$function_name.
             '<br/>| wanted: &#9;'.$desired_output_.
             '<br/>| obtained : &#9;'.$function_output_.
             '<br/>| input: &#9;'.$function_input_;
    } else {
        echo '[O] Passed: &#9;'.$function_name.
             '<br/>| obtained: &#9;'.$desired_output_.
             '<br/>| input: &#9;'.$function_input_;
    }
    echo '<br/><br/><br/>';
}

echo '<style>pre, code {
    font-family: "Lucida Console", Consolas;font-size:13px;
    </style><pre><code class="html">'; // formatting the output

/*************************************************************/


// the function we want to test
function division($to_divide,$divide_by){
    $rezultat=$to_divide / $divide_by;
    return $rezultat;
}

// the tests themselves
test('division', '3,1',3);       /* 3/1 = 3 */
test('division', '3,2','1.5');   /* 3/2 = 1.5 */
test('division', '3,0',null);    /* 3/0 = null */


/*************************************************************/


/* the function to test, after which we found a case in which it doesen't works
 * and we have repaired it. The tests remain unchanged. Not the function decides
 * what tests we write, but the tests decide what function we write, so the 
 * function decides the input data and output data we want/seek.
 */
function division2($to_divide,$divide_by){
    if($divide_by == 0)
        return null;
    $rezultat=$to_divide / $divide_by;
    return $rezultat;
}

// the tests themselves
test('division2', '3,1',3);       /* 3/1 = 3 */
test('division2', '3,2','1.5');   /* 3/2 = 1.5 */
test('division2', '3,0',null);    /* 3/0 = null */
test('division2', '3,0','null');  /* null != String(null) */


/*************************************************************/


function wierd_math($first, $second, $tird, $fourth){
    $result = division2($first, $second) + division2($tird, $fourth);
    return $result;
}

test('wierd_math','3,1,4,2',5);     /* 3/1 + 4/2 = 3 + 2 = 5 */
test('wierd_math','3,1,3,2',4.5);   /* 3/1 + 4/2 = 3 + 1.5 = 4.5 */
test('wierd_math','3,1,3,2','4.5'); /* just to be sure we did it right... */
test('wierd_math','3,1,4,0',null);  /* 3/1 + 4/0 = 3 + null = ...null? */


/*************************************************************/


function wierd_math2($first, $second, $tird, $fourth){
    if($second == 0 || $fourth == 0)
        return null;
    $result = division2($first, $second) + division($tird, $fourth);
    return $result;
}


test('wierd_math2','3,1,4,2',5);     /* 3/1 + 4/2 = 3 + 2 = 5 */
test('wierd_math2','3,1,3,2',4.5);   /* 3/1 + 4/2 = 3 + 1.5 = 4.5 */
test('wierd_math2','3,1,3,2','4.5'); /* just to be sure we did it right... */
test('wierd_math2','3,1,4,0',null);  /* 3/1 + 4/0 = 3 + null = ...null? */


echo '</code></pre>'; // formatting the output

Rezultat/Afisaj:

[O] Passed: 	division
| obtained: 	3
| input: 	3,1


[O] Passed: 	division
| obtained: 	1.5
| input: 	3,2




Warning:  Division by zero in localhost\_testing.php on line 38

[X] Failed: 	division
| wanted: 	`null`
| obtained : 	INF
| input: 	3,0


[O] Passed: 	division2
| obtained: 	3
| input: 	3,1


[O] Passed: 	division2
| obtained: 	1.5
| input: 	3,2


[O] Passed: 	division2
| obtained: 	`null`
| input: 	3,0


[X] Failed: 	division2
| wanted: 	null
| obtained : 	`null`
| input: 	3,0


[O] Passed: 	wierd_math
| obtained: 	5
| input: 	3,1,4,2


[O] Passed: 	wierd_math
| obtained: 	4.5
| input: 	3,1,3,2


[O] Passed: 	wierd_math
| obtained: 	4.5
| input: 	3,1,3,2


[X] Failed: 	wierd_math
| wanted: 	`null`
| obtained : 	3
| input: 	3,1,4,0


[O] Passed: 	wierd_math2
| obtained: 	5
| input: 	3,1,4,2


[O] Passed: 	wierd_math2
| obtained: 	4.5
| input: 	3,1,3,2


[O] Passed: 	wierd_math2
| obtained: 	4.5
| input: 	3,1,3,2


[O] Passed: 	wierd_math2
| obtained: 	`null`
| input: 	3,1,4,0