Если нельзя, но очень хочется, то нужно обязательно и ничего в мире не стоит того, чтобы делать из этого проблему!


Интересна Java? Кликай по ссылке и изучай!
Если тебе полезно что-то из того, чем я делюсь в своем блоге - можешь поделиться своими деньгами со мной.
с пожеланием
столько времени читатели провели на блоге - 
сейчас онлайн - 

четверг, 26 июля 2012 г.

TDD на PHP используя Zend Studio - собственно, калькулятор

Краткое содержание прошлых серий:
- подружили Zend Studio и Zend Server
- подружили Zend Studio и GitHub

Дальше у нас чистой воды TDD. Буду выкладывать код по очереди [тест], [фикс] [тест][фикс]. Всю хистори можно посмотреть тут (файлы Calculator.php и CalculatorTest.php).

[тест]
    public function testShould2When2Plus4() {
        $calculator = new Calculator();
        $actual =  $calculator->calculate('2+2');
        $this->assertEquals('4', $actual);
    }
[фикс]
class Calculator {
    public function calculate($expression) {
        return 4; // это самое простое, что заставит калькулятор работать!
    }
}
[коммит] 2+2 =4 !!!
 [тест]
    public function testShould3When4Plus7() {
        $calculator = new Calculator();
        $actual =  $calculator->calculate('3+4');
        $this->assertEquals('7', $actual);
    }
[фикс]
class Calculator {
    public function calculate($expression) {
        return substr($expression, 0, 1) + substr($expression, 1, 2); // теперь односимвольные числа умеем суммировать 
    }
}
[коммит] Теперь калькулятор может суммировать два однозначных числа

[рефакторинг] Выделил setup в тестах
class CalculatorTest extends PHPUnit_Framework_TestCase {

    private $Calculator; // поле :)                       

    protected function setUp() { // сетап вызовется перед каждым тестом                  
        parent::setUp ();                          
        $this->Calculator = new Calculator(); // и создаст калькулятор
    }                                              

    protected function tearDown() { // а это чудо восле каждого теста                 
        $this->Calculator = null; // обнулит ссылку                 
        parent::tearDown ();                       
    }                                              

    public function testShould4When2Plus2() {
        $actual = $this->Calculator->calculate('2+2'); // теперь можно пользоваться полем напрямую
        $this->assertEquals('4', $actual);
    }     

    public function testShould7When3Plus4() {
        $actual = $this->Calculator->calculate('3+4');
        $this->assertEquals('7', $actual);
    }

}
[коммит]

[тест]
    public function testShould33When11Plus22() { // новый тест
        $actual = $this->Calculator->calculate('11+22');
        $this->assertEquals('33', $actual);
    }
[фикс]
class Calculator {     

    public function calculate($expression) {
        preg_match_all("/[0-9]+/", $expression, $out);  // усложняем логику
        $sum = (int)($out[0][0]) + (int)($out[0][1]);        

        return $sum;
    }
}
[коммит] Теперь калькулятор умеет суммировать многозначные числа

[рефакторинг] Выделил data provider в тестах
class CalculatorTest extends PHPUnit_Framework_TestCase {

    ...

    public static function provider() { // поставщик данных теста экономит место
        return array(
            array(2, 2, 4),
            array(3, 4, 7),
            array(11, 22, 33),
        );
    }
    
    // эта аннотация важна
    /**
     * @dataProvider provider 
     */
    public function testShouldSumWhenXPlusY($x, $y, $expected) { // тест-шаблон наполнится данными
        $actual = $this->Calculator->calculate($x.'+'.$y);
        $this->assertEquals($expected, $actual);
    }
}
[коммит]

[тест]
    // эта аннотация говорит, что ловим исключение
    /**
     * @expectedException RuntimeException
     */
    public function testShouldExceptionWhenInvalidExpression() {
        $this->Calculator->calculate('1');
    }
[фикс]
    public function calculate($expression) {
        preg_match_all("/[0-9]+/", $expression, $out);

        if (count($out[0]) < 2) {  // вот и проверочка                             
            throw new RuntimeException('Invalid expression format'); 
        }                                                            

        $sum = (int)($out[0][0]) + (int)($out[0][1]);     

        return $sum;
    }
[коммит] Есть простая валидация выражения
[тест]
    /**
     * @expectedException InvalidArgumentException
     */
    public function testShouldExceptionWhenMoreThanOnePlus() {
         $this->Calculator->calculate('1++3');
    }
[фикс]
    public function calculate($expression) {
        preg_match_all("/[0-9]+/", $expression, $out);

        if (count($out[0]) < 2 || substr_count($expression, '+') != 1) { // добавили подусловие
            throw new RuntimeException('Invalid expression format');
        }

        $sum = (int)($out[0][0]) + (int)($out[0][1]);     

        return $sum;
    }
[коммит] Валидция выражения на количество знаков + (должно быть 1)

[рефакторинг] Добавил новый параметр методу - $base - основание системы счисления
class CalculatorTest extends PHPUnit_Framework_TestCase {

   ...

    public static function provider() {
        return array(
            array(2, 2, 10, 4), // передаю во всех тестах, для 10ричной системы
            array(3, 4, 10, 7),
            array(11, 22, 10, 33),
        );
    }

    /**
     * @dataProvider provider
     */
    public function testShouldSumWhenXPlusY($x, $y, $base, $expected) { // расширил метод-шаблон
        $actual = $this->Calculator->calculate($x.'+'.$y, $base);
        $this->assertEquals($expected, $actual);
    }

    ...

}
[фикс]
class Calculator {     
        public function calculate($expression, $base) { // и добавил в сигнатуре метода
            ...
[коммит] Первый шаг в сторону 16-ричного калькулятора - введение основы системы счисления как параметр

[тест]
    public static function provider() {
        return array(
            array(2, 2, 10, 4),
            array(3, 4, 10, 7),
            array(11, 22, 10, 33),
            array(9, 1, 16, A),  // новый тест   
        );
    }
[фикс]
    public function calculate($expression) {
        preg_match_all("/[0-9]+/", $expression, $out);

        if (count($out[0]) < 2 || substr_count($expression, '+') != 1) {
            throw new RuntimeException('Invalid expression format');
        }

        $sum = (int)($out[0][0]) + (int)($out[0][1]);     

        if ($sum == 10 && $base == 16) { // добавили проверочку (ужас, но работает!)
            return "A";                  
        }                                

        return $sum;
    }
[коммит] В 16ричной 9 + 1 = А

[тест]
    public static function provider() {
        return array(
            array(2, 2, 10, 4),
            array(3, 4, 10, 7),
            array(11, 22, 10, 33),
            array(9, 1, 16, A), 
            array(6, 9, 16, F), // еще тест    
        );
    }
[фикс]
    public function calculate($expression) {
        preg_match_all("/[0-9]+/", $expression, $out);

        if (count($out[0]) < 2 || substr_count($expression, '+') != 1) {
            throw new RuntimeException('Invalid expression format');
        }

        $sum = (int)($out[0][0]) + (int)($out[0][1]);     

        if ($base == 16) {
            if ($sum == 10) {
                return "A";
            } else if ($sum == 15) { // проверка усложнилась
                return "F";
            }
        }                                

        return $sum;
    }
[коммит] В 16ричной 6 + 9 = F

[тест]
    public static function provider() {
        return array(
            array(2, 2, 10, 4),
            array(3, 4, 10, 7),
            array(11, 22, 10, 33),
            array(9, 1, 16, A), 
            array(6, 9, 16, F),  
            array(6, 8, 16, E), // еще тест
        );
    }
[фикс]
class Calculator {

    private $Digits = "0123456789ABCDEF";  // выделили все буковки

    public function calculate($expression) {
        preg_match_all("/[0-9]+/", $expression, $out);

        if (count($out[0]) < 2 || substr_count($expression, '+') != 1) {
            throw new RuntimeException('Invalid expression format');
        }

        $sum = (int)($out[0][0]) + (int)($out[0][1]);     

        if ($base == 16) {
            if ($sum > 9) { // и тут сделали сразу для всех остальных буковок
                return $this->Digits[$sum];
            }
        }                               

        return $sum;
    }
}
[коммит] В 16ричной 6 + 8 = E

[тест]
    public static function provider() {
        return array(
            array(2, 2, 10, 4),
            array(3, 4, 10, 7),
            array(11, 22, 10, 33),
            array(9, 1, 16, A), 
            array(6, 9, 16, F),  
            array(6, 8, 16, E),
            array(A, 5, 16, F), // еще тест  
        );
    }
[фикс]
class Calculator {     
     
     private $Digits = "0123456789ABCDEF";
     
     public function calculate($expression, $base) {
          preg_match_all("/[0-9A]+/", $expression, $out); // тут добавили буковку
          
          if (count($out[0]) < 2 || substr_count($expression, '+') != 1) {
               throw new RuntimeException('Invalid expression format');
          }
               
          $sum = $this->toInt($out[0][0]) + // небольшой фикс 
                  (int)($out[0][1]);     

          if ($base == 16) {
               if ($sum > 9) {
                    return $this->Digits[$sum];
               } 
               
          }
          
          return $sum;
     }
     
     private function toInt($hex) { // новый метод
          if (is_numeric($hex)) return $hex; 
          if ($hex == "A") return 10;
     }
}
[коммит] В 16ричной A + 5 = F

[тест]
    public static function provider() {
        return array(
            array(2, 2, 10, 4),
            array(3, 4, 10, 7),
            array(11, 22, 10, 33),
            array(9, 1, 16, A), 
            array(6, 9, 16, F),  
            array(6, 8, 16, E),
            array(A, 5, 16, F), 
            array(1, E, 16, F), // еще тест
        );
    }
[фикс]
class Calculator {     
     
     private $Digits = "0123456789ABCDEF";
     
     public function calculate($expression, $base) {
          preg_match_all('/['.$this->Digits.']+/', $expression, $out); // сделали вообще универсальную регекспу
          
          if (count($out[0]) < 2 || substr_count($expression, '+') != 1) {
               throw new RuntimeException('Invalid expression format');
          }
               
          $sum = $this->toInt($out[0][0]) + 
                  $this->toInt($out[0][1]);  // тут исправили   

          if ($base == 16) {
               if ($sum > 9) {
                    return $this->Digits[$sum];
               } 
               
          }
          
          return $sum;
     }
     
     private function toInt($hex) {
          if (is_numeric($hex)) return $hex; 
          if ($hex == "A") return 10;
          if ($hex == "E") return 14; // тут добавили
     }
}
[коммит] В 16ричной 1 + E = F

[тест]
    public static function provider() {
        return array(
            array(2, 2, 10, 4),
            array(3, 4, 10, 7),
            array(11, 22, 10, 33),
            array(9, 1, 16, A), 
            array(6, 9, 16, F),  
            array(6, 8, 16, E),
            array(A, 5, 16, F), 
            array(1, E, 16, F),  
            array(2, B, 16, D), // еще тест
        );
    }
[фикс]
     private function toInt($hex) {
          if (is_numeric($hex)) return $hex; 
          return strpos($this->Digits, $hex); // сделали универсальнее
     }
[коммит] В 16ричной 2 + B = D

[тест]
    public static function provider() {
        return array(
            array(2, 2, 10, 4),
            array(3, 4, 10, 7),
            array(11, 22, 10, 33),
            array(9, 1, 16, A), 
            array(6, 9, 16, F),  
            array(6, 8, 16, E),
            array(A, 5, 16, F), 
            array(1, E, 16, F),  
            array(2, B, 16, D),
            array(B, E, 16, 19), // еще тест
        );
    }
[фикс]
class Calculator {     
     
     private $Digits = "0123456789ABCDEF";
     
     public function calculate($expression, $base) {
          ...    

          if ($base == 16) {                              
               if ($sum > 16) { // добавили подифчик
                    $sum = 10 + $sum - 16;
               } else if ($sum > 9) {
                    return $this->Digits[$sum];
               }
                
               
          }
          
          return $sum;
     }
     
     ...
}
[коммит] В 16ричной B + E = 19

[рефакторинг] Немного переделал логику
class Calculator {    
    
    private $Digits = "0123456789ABCDEF";
    
    public function calculate($expression, $base) {
        preg_match_all('/['.$this->Digits.']+/', $expression, $out);
        
        if (count($out[0]) < 2 || substr_count($expression, '+') != 1) {
            throw new RuntimeException('Invalid expression format');
        }
            
        $sum = $this->toInt($out[0][0]) + 
               $this->toInt($out[0][1]);    

        if ($base == 16) {                        
            if ($sum > 16) {
                return '1'.($sum - 16); // тут
            } else if ($sum > 9) {
                return $this->Digits[$sum];
            }                     
        }
        
        return (string)$sum; // и тут
    }
    ...
[коммит]

[тест]
     public static function provider() {
          return array(
              array('2', '2', 10, '4'),
              array('3', '4', 10, '7'),
              array('11', '22', 10, '33'),
              array('9', '1', 16, 'A'),
              array('6', '9', 16, 'F'),
              array('6', '8', 16, 'E'),
              array('A', '5', 16, 'F'),
              array('1', 'E', 16, 'F'),
              array('2', 'B', 16, 'D'),
              array('B', 'E', 16, '19'),
              array('C', 'F', 16, '1B'), // еще тест
          );
     }
[фикс]
class Calculator {     
     
     private $Digits = "0123456789ABCDEF";
     
     public function calculate($expression, $base) {
          ...     

          if ($base == 16) {                              
               if ($sum > 16) {
                    return '1'.$this->toHex($sum - 16); // исправили
               } else if ($sum > 9) {
                    return $this->Digits[$sum];
               }                          
          }
          
          return (string)$sum;
     }
     
     private function toHex($int) { // новый метод
          if ($int == 11) return "B";
          return $int;
     }
     
     ...
}
[коммит] В 16ричной C + F = 1B

[тест]
     public static function provider() {
          return array(
              array('2', '2', 10, '4'),
              array('3', '4', 10, '7'),
              array('11', '22', 10, '33'),
              array('9', '1', 16, 'A'),
              array('6', '9', 16, 'F'),
              array('6', '8', 16, 'E'),
              array('A', '5', 16, 'F'),
              array('1', 'E', 16, 'F'),
              array('2', 'B', 16, 'D'),
              array('B', 'E', 16, '19'),
              array('C', 'F', 16, '1B'),
              array('F', 'F', 16, '1E'), // еще тест
          );
     }
[фикс]
     private function toHex($int) {
          return $this->Digits[$int]; // сделали универсальным
     }
[коммит] В 16ричной F + F = 1E

[тест]
     public static function provider() {
          return array(
              array('2', '2', 10, '4'),
              array('3', '4', 10, '7'),
              array('11', '22', 10, '33'),
              array('9', '1', 16, 'A'),
              array('6', '9', 16, 'F'),
              array('6', '8', 16, 'E'),
              array('A', '5', 16, 'F'),
              array('1', 'E', 16, 'F'),
              array('2', 'B', 16, 'D'),
              array('B', 'E', 16, '19'),
              array('C', 'F', 16, '1B'),
              array('F', 'F', 16, '1E'),
              array('1C', '1', 16, '1D'), // еще тест
          );
     }
[фикс]
class Calculator {     
     
     private $Base; // теперь нам основание нужно в другом методе
     private $Digits = "0123456789ABCDEF";
     
     public function calculate($expression, $base) {
          $this->Base = $base; // потому сохраняем его в поле
          preg_match_all('/['.$this->Digits.']+/', $expression, $out);
          
          if (count($out[0]) < 2 || substr_count($expression, '+') != 1) {
               throw new RuntimeException('Invalid expression format');
          }
               
          $sum = $this->toInt($out[0][0]) + 
                  $this->toInt($out[0][1]);     

          if ($base == 16) {                              
               if ($sum > $base) { // тут чуть порефакторили
                    return '1'.$this->toHex($sum - 16);
               } else if ($sum > 9) {
                    return $this->Digits[$sum];
               }                          
          }
                    
          return (string)$sum;
     }
     
     private function toHex($int) {
          return $this->Digits[$int];
     }
     
     private function toInt($hex) {          
          if (strlen($hex) == 2) { // и тутки добавили целый if
               return $this->Base*$this->toInt(substr($hex, 0, 1)) + $this->toInt(substr($hex, 1, 1));
          }
          
          if (is_numeric($hex)) return $hex; 
          return strpos($this->Digits, $hex);
     }
[коммит] В 16ричной 1C + 1 = 1D

[тест]
     public static function provider() {
          return array(
              array('2', '2', 10, '4'),
              array('3', '4', 10, '7'),
              array('11', '22', 10, '33'),
              array('9', '1', 16, 'A'),
              array('6', '9', 16, 'F'),
              array('6', '8', 16, 'E'),
              array('A', '5', 16, 'F'),
              array('1', 'E', 16, 'F'),
              array('2', 'B', 16, 'D'),
              array('B', 'E', 16, '19'),
              array('C', 'F', 16, '1B'),
              array('F', 'F', 16, '1E'),
              array('1C', '1', 16, '1D'),
              array('11', '11', 16, '22'), // еще тест
          );
     }
[фикс]
class Calculator {     
     
     private $Base; 
     private $Digits = "0123456789ABCDEF";
     
     public function calculate($expression, $base) {
          ...    

          $sum = $this->toInt($out[0][0]) + 
                  $this->toInt($out[0][1]); 
         
          if ($base == 16) {                              
               if ($sum >= $base*2) { // добавили еще один if
                    return '2'.$this->toHex($sum - 32);
               } else if ($sum > $base) { // дальше без изменений
                    return '1'.$this->toHex($sum - 16);
               } else if ($sum > 9) {
                    return $this->Digits[$sum];
               }                          
          }                                            
                    
          return (string)$sum;
     }
     
     ...
[коммит] В 16ричной 11 + 11 = 22

[тест]
     public static function provider() {
          return array(
              array('2', '2', 10, '4'),
              array('3', '4', 10, '7'),
              array('11', '22', 10, '33'),
              array('9', '1', 16, 'A'),
              array('6', '9', 16, 'F'),
              array('6', '8', 16, 'E'),
              array('A', '5', 16, 'F'),
              array('1', 'E', 16, 'F'),
              array('2', 'B', 16, 'D'),
              array('B', 'E', 16, '19'),
              array('C', 'F', 16, '1B'),
              array('F', 'F', 16, '1E'),
              array('1C', '1', 16, '1D'),
              array('11', '11', 16, '22'),
              array('22', '22', 16, '44'), // еще тест
          );
     }
[фикс]
class Calculator {     
     
     private $Base;
     private $Digits = "0123456789ABCDEFG";
     
     public function calculate($expression, $base) {
          ...
               
          $sum = $this->toInt($out[0][0]) + 
                  $this->toInt($out[0][1]);     

          $big = (int)($sum / $base); // тут чуть поменяли логику
          if ($big <> 0) {
               return $this->toHex($big).$this->toHex($sum % $base);
          }
                    
          return $this->toHex($sum); // и тут :)
     }
     
     ...
}
[коммит] В 16ричной 22 + 22 = 44

[рефакторинг] Выделил новый метод из тела calculate
class Calculator {    
    
    private $Base;
    private $Digits = "0123456789ABCDEFG";
    
    public function calculate($expression, $base) {
        $this->Base = $base;
        preg_match_all('/['.$this->Digits.']+/', $expression, $out);
        
        if (count($out[0]) < 2 || substr_count($expression, '+') != 1) {
            throw new RuntimeException('Invalid expression format');
        }
            
        $sum = $this->toInt($out[0][0]) + 
               $this->toInt($out[0][1]); ...    

        return $this->intToHex ($sum); // выделяли отсюда
    }

    private function intToHex($int) { // новый метод
        $big = (int)($int / $this->Base);
        if ($big <> 0) {
            return $this->toHex($big).$this->toHex($int % $this->Base);
        }
                
        return $this->toHex($int);
    }
    ... 
[коммит]

[тест]
     public static function provider() {
          return array(
              array('2', '2', 10, '4'),
              array('3', '4', 10, '7'),
              array('11', '22', 10, '33'),
              array('9', '1', 16, 'A'),
              array('6', '9', 16, 'F'),
              array('6', '8', 16, 'E'),
              array('A', '5', 16, 'F'),
              array('1', 'E', 16, 'F'),
              array('2', 'B', 16, 'D'),
              array('B', 'E', 16, '19'),
              array('C', 'F', 16, '1B'),
              array('F', 'F', 16, '1E'),
              array('1C', '1', 16, '1D'),
              array('11', '11', 16, '22'),
              array('22', '22', 16, '44'),
              array('99', '99', 16, '132'), // еще тест
          );
     }
[фикс]
     private function intToHex($int) {
          $big = (int)($int / $this->Base);
          if ($big <> 0) {
               return $this->intToHex($big).$this->intToHex($int % $this->Base); // тут сделали рекурсию
          }
                    
          return $this->toHex($int);
     }
[коммит] В 16ричной 99 + 99 = 132

[рефакторинг] Выделил еще один махонький метод и переименовал (Внимание!) toInt в hexToInt.
class Calculator {    
    
    private $Base;
    private $Digits = "0123456789ABCDEFG";
    
    public function calculate($expression, $base) {
        $this->Base = $base;
        preg_match_all('/['.$this->Digits.']+/', $expression, $out);
        
        if (count($out[0]) < 2 || substr_count($expression, '+') != 1) {
            throw new RuntimeException('Invalid expression format');
        }
            
        $sum = $this->hexToInt($out[0][0]) + // переименовали
               $this->hexToInt($out[0][1]);    

        return $this->intToHex ($sum);
    }

    private function intToHex($int) { 
        $big = (int)($int / $this->Base);
        if ($big <> 0) {
            return $this->intToHex($big).$this->intToHex($int % $this->Base);
        }
                
        return $this->toHex($int);
    }

    
    private function toHex($int) { 
        return $this->Digits[$int];
    }
    
    private function hexToInt($hex) { // переименовали       
        if (strlen($hex) == 2) {
            return $this->Base*$this->hexToInt(substr($hex, 0, 1)) + $this->hexToInt(substr($hex, 1, 1));
        }
        
        return $this->toInt ($hex);

    }
    
    private function toInt($hex) { // выделили новый 
        if (is_numeric($hex)) return $hex; 
        return strpos($this->Digits, $hex);
    }

} 
[коммит]

[тест]
     public static function provider() {
          return array(
              array('2', '2', 10, '4'),
              array('3', '4', 10, '7'),
              array('11', '22', 10, '33'),
              array('9', '1', 16, 'A'),
              array('6', '9', 16, 'F'),
              array('6', '8', 16, 'E'),
              array('A', '5', 16, 'F'),
              array('1', 'E', 16, 'F'),
              array('2', 'B', 16, 'D'),
              array('B', 'E', 16, '19'),
              array('C', 'F', 16, '1B'),
              array('F', 'F', 16, '1E'),
              array('1C', '1', 16, '1D'),
              array('11', '11', 16, '22'),
              array('22', '22', 16, '44'),
              array('99', '99', 16, '132'),
              array('100', '1', 16, '101'), // еще тест
          );
     }
[фикс]
    private function hexToInt($hex) {  // полностью переписали      
        $sum = 0;
        for ($index = 0; $index < strlen($hex); $index++) {
            $sum = $this->Base*$sum + $this->toInt(substr($hex, $index, 1));
        }
        return $sum;

    }
[коммит] В 16ричной 100 + 1 = 101

[тест]
     public static function provider() {
          return array(
              array('2', '2', 10, '4'),
              array('3', '4', 10, '7'),
              array('11', '22', 10, '33'),
              array('9', '1', 16, 'A'),
              array('6', '9', 16, 'F'),
              array('6', '8', 16, 'E'),
              array('A', '5', 16, 'F'),
              array('1', 'E', 16, 'F'),
              array('2', 'B', 16, 'D'),
              array('B', 'E', 16, '19'),
              array('C', 'F', 16, '1B'),
              array('F', 'F', 16, '1E'),
              array('1C', '1', 16, '1D'),
              array('11', '11', 16, '22'),
              array('22', '22', 16, '44'),
              array('99', '99', 16, '132'),
              array('100', '1', 16, '101'),
              array('D', '3', 17, 'G'),  // добавили этот тест и все что ниже
              array('D', '2', 17, 'F'),
              array('D', '4', 17, '10'),
              array('G', '1', 17, '10'),
              array('G', 'G', 17, '1F'),
              array('1G', '1G', 17, '3F'),
          );
     }
[фикс]
class Calculator {    
    
    private $Digits = "0123456789ABCDEFG"; // фикс всего одна новая буковка
[коммит] Теперь калькулятор умеет считать и 17-ричные числа

[тест]
    /**
     * @expectedException InvalidArgumentException
     */
    public function testShouldExceptionWhenIUseNotExistsSymbols2() { // этот тест на всякий для личного успокоения, каюсь :)
        $this->Calculator->calculate('10300+1', '2');
    }
    
    /**
     * @expectedException InvalidArgumentException
     */
    public function testShouldExceptionWhenIUseNotExistsSymbols3() { // этот тест рабочий
        $this->Calculator->calculate('1+1A1', '4');
    }
[фикс]
class Calculator {    
    
    private $Base;
    private $Digits = "0123456789ABCDEFG";
    
    public function calculate($expression, $base) {
        $this->Base = $base;
        preg_match_all('/['.$this->Digits.']+/', $expression, $out);
        
        if (count($out[0]) < 2 || substr_count($expression, '+') != 1) {
            throw new RuntimeException('Invalid expression format');
        }
        
        if ($this->isInvalidNumber($out[0][0])) { // добавили новую проверку
            throw new RuntimeException('Invalid expression format');
        }
            
        $sum = $this->hexToInt($out[0][0]) + 
               $this->hexToInt($out[0][1]);    

        return $this->intToHex ($sum);
    }

    private function isInvalidNumber($hex) { // и метод проверки
        $is_invalid = false;
        for ($index = 0; $index < strlen($hex); $index++) {
            $is_invalid |= $this->toInt($hex[$index]) >= $this->Base;                
        }            
        return $is_invalid;        
    }
    
   ...

}
[коммит] Валидация первого слагаемого, чтобы каждая цифра не выходила за пределы основания

[тест]
    /**
     * @expectedException InvalidArgumentException
     */
    public function testShouldExceptionWhenIUseNotExistsSymbols() {
        $actual =  $this->Calculator->calculate('G+1', '16'); // буквы G быть не может
    }
[фикс]
class Calculator {    
    
    private $Base;
    private $Digits = "0123456789ABCDEFG";
    
    public function calculate($expression, $base) {
        $this->Base = $base;
        preg_match_all('/['.$this->Digits.']+/', $expression, $out);
        
        if (count($out[0]) < 2 || substr_count($expression, '+') != 1) {
            throw new RuntimeException('Invalid expression format');
        }
        
        if ($this->isInvalidNumber($out[0][0]) || $this->isInvalidNumber($out[0][1])) { // добавли еще одно условие
            throw new RuntimeException('Invalid expression format');
        }

        ...
[коммит] Валидация второго слагаемого чтобы не выходил за пределы основания

[тест]
    /**
     * @expectedException InvalidArgumentException
     */
    public function testShouldExceptionWhenIUseNotExistsSymbols4() {
        $this->Calculator->calculate('QWE+ASD', '17');
    }
[фикс]
class Calculator {    
    
    private $Base;
    private $Digits = "0123456789ABCDEFG";
    
    public function calculate($expression, $base) {
        $this->Base = $base;

        if ($this->isContainsInvalidNumber($expression)) { // проверку перенесли чуть выше и для всего эксепешена сразу
            throw new RuntimeException('Invalid number');
        }
        
        preg_match_all('/['.$this->Digits.']+/', $expression, $out);
        
        if (count($out[0]) < 2 || substr_count($expression, '+') != 1) {
            throw new RuntimeException('Invalid expression format');
        }        
            
        $sum = $this->hexToInt($out[0][0]) + 
               $this->hexToInt($out[0][1]);    

        return $this->intToHex ($sum);
    }

    private function isContainsInvalidNumber($expression) { // метод переименовали и чуть переписали
        $is_invalid = false;
        for ($index = 0; $index < strlen($expression); $index++) {
            if ($expression[$index] == '+') {
                continue;
            }
            
            $int = $this->toInt($expression[$index]);
            $is_invalid |= ($int === false) || $int >= $this->Base;                
        }            
        return $is_invalid;        
    }
    ...
[коммит] Теперь проводится валидация на всякие левые символы

[тест]
    /**
     * @expectedException InvalidArgumentException
     */
    public function testShouldExceptionWhenBaseIsMoreThan17() {
        $this->Calculator->calculate('1+1', '18');
    }
[фикс]
class Calculator {    
    
    private $Base;
    private $Digits = "0123456789ABCDEFG";
    
    public function calculate($expression, $base) {
        $this->Base = $base;

        if ($this->isContainsInvalidNumber($expression)) {
            throw new RuntimeException('Invalid number');
        }
        
        if ($base > strlen($this->Digits)) { // добавили еще один ифчик
            throw new RuntimeException('Invalid base');
        }
        ...
[коммит] Теперь и основание системы счисления валидаируется, не больше 17

[тест]
    /**
     * @expectedException InvalidArgumentException
     */
    public function testShouldExceptionWhenBaseIsLessThan2() {
        $this->Calculator->calculate('0+0', '1');
    }
[фикс]
        if ($base > strlen($this->Digits) || $base <= 1) { // добавили подусловие
            throw new RuntimeException('Invalid base');
        }
[коммит] Добавил проверку что основание не может быть меньше 2

[рефакторинг] немного упростил
    private function intToHex($int) {
        if ($int / $this->Base < 1) {
            return $this->toHex($int);
        }
                
        $high = (int)($int / $this->Base);
        return $this->intToHex($high).$this->toHex($int % $this->Base); // тут вызвал toHex, а то некошерно как-то intToHex для мелких чисел
    }
[коммит] Небольшой рефакторинг

[рефакторинг] избавился от рекурсии
    private function intToHex($int) {
        $result = '';
        $low = $int;
        do {
            $high = $low % $this->Base;
            $low = (int)$low / $this->Base;
            $result = $this->toHex($high).$result;
        } while ($low >= 1);
        
        return $result;
    }
[коммит] Вот оно преимущество хорошего покрытия тестами - рефакторинг в удовольствие.

среда, 25 июля 2012 г.

TDD на PHP используя Zend Studio - настройка GitHub

В прошлывй раз мы подружили Zend Studio n Zend Server, а так же написали hello world ипоставли себе задачу написать калькулятор шеснадатеричных чисел. Вот тест


Первый фикс простой как двери



Дальше я хотел бы закоммититься но куда? GitHub :) немног ораньше надо было об этом думать, а потому я создам новый проект опять :)


Тут же IDE спросит меня установить ли плагин - да!


После установит все сама и перезапустится - ай лайкит ит!

Теперь у меня есть возможность выбрать новую вьюшку



Чето ругнулся по поводу HOME переменной, но я пока проигнорю


Вот она вьюшка, много кнопочек :)


Можно зайти в настройки проекта


Там указать папочку с папкой в которой будет храниться дифолтовый репозиторий


 Теперь можно создать репозиторий

Тут я пошел не тем путем, но так как в процессе я не знал этого и делал скриншоты, то я их тут выложу, мало ли пригодятся. Можешь скипать все пометки [fail] аж до [OK]

[fail] Дам ему имя


[fail] Вот он на вьюшку репозитории


[fail] Теперь я могу завязать на него проект


[fail] Опять это дурацкое окошко - игнорим

[fail] Указываем новосозданнй репозиторий


[fail] Можно увидеть, каквозле проекта показалась пометка с именем репозитория


[fail] Теперь можно закоммититься


[fail] Указываем от имени кого коммит


[fail] Теперь выбираем что коммитим и с каким сообщением


[fail] Но, блин! Он взял мою папку проекта и переместил из apache рядом с локальным git репозиторием, в этом то и [fail]


[fail] Удалю репозиторий


[fail] Оставляя контент на месте


[fail] Перемещаю проект обратно в apache


[fail] Импортирую свой проект




[fail] Снова пытаюсь расшарить


[OK] Но на этот раз при виде окошка я поставлю снятую галочку


[OK] Проигнорирую все, что мне скажет умник плагин и создам репозиторий вместе проекта. Пока пусть будет так - все на кучу конечно (и апачи и настройки проекта и git), но потом разберемся.


[OK] Теперь можно жмакать finish


[OK] Папка осталась на месте, рядом создался git локальный репозиторий


[OK] Могу опять закоммититься :)


[OK] Тут вожно, что я не коммитил настройки проекта


[OK] И запаблишить изменения на сервак


[OK] На котором надо предвариетльно создать новый репозиторий




[OK] И указать его в настройках push операции


[OK] Далее я просто нажал все, что нажимается :)




[OK] И процесс пошел


[OK] Кажется успешно!


[OK] Таки да!


 Все, теперь у нас проект на git hub и мы можем продолжить tdd-ить. Об этом дальше...

TDD на PHP используя Zend Studio - настройка окружения

У меня задача - написать на php код используя TDD подход. Задача - сделать калькулятор суммирующий числа в разных системах счисления. Но для начала, пусть он это сделает в 16-ричной.

Как начать работу с Zend Studio смотрим тут
Качаем ИДЕху тут или тут (кому как совесть позволяет). Я качнул тут, но мне установщик сказал, что у меня триал закончился, хотя я ни разу не юзал его, потому пришлось шкодничать. Но я только на 30 дней, обедщаю!


Установка не вызваланикаких вопросов - next, next, next....

Потом мне потребовалось установить Zend Server. Наученный горьким опытом глюков, связанных с пробелом в папке Program Files для java я решил все поставить в папку C:/Zend и убрать все лишние пробелы.



Вот она установка.















Портом 80 решил не гневать TCP/IP Богов и выбрал 8088





 Пароль естественно admin


Лицензию взял триальную



И воть!



Дальше я посомтрел эту видяшку и все повторил :)


Не все конечно было гладко. Мой фейл был в том, что я не разместил воркспейс и сам проект в папке C:\Zend\Apache2\htdocs откуда оно бы запускалось. Да и вообще мне не особо надо было это все для простого калькулятора, но я на будущее - все же веб часть - это следующий шаг после того, как модель будет +/- готова к показу.

Итак пошагово. Переключаем воркспейс у только что запущенной Zend Studio


На папку куда у Apache деплоятся приложения...


Кстати да, как и обещано в видяшке Zend Studio сразу увидел Zend Server.

Создаем проект



Но перед финишом предложит проверитm есть ли связь



Ведь может быть и так.



Третья вкладка нам не особо нужна, но я на всякий выбрал вот это

 
Вот структура проекта, любопытненько

 Я написал судьбоносное и запустил




Ура! Пол таска позади.

Если интересно можно поколупаться в настроечках сервера (мало ли пригодится, хоть будешь знать что тут можно чето потыкать палочкой)



Ещея не нашел обещанного на видео описания сервера на на вью Server после добавления этой самой вью 



Воть пусто, даже добавление нового сервера не особо помогло


Хотя, напомню, Zend Server задетектился Zend Studio.

Ладно, пойдем дальше. Т.к. на php кодил давненько, и с тех пор много воды утекло, то пришлось методом google driven development и try error analyze добиться первой красной полосы.

 
Вот это сообщение меня просто достало. Я пару часов с ним мучился, а потом просто понял что стоит заглянуть в консоль и там будет информация чето не так :) И вот она желанная всеми дивелоперами зеленая полоса!


Дальше все пошло как часики, я подглядывал в google



 И написал такой вот тест.


Вернее он был сгенерирован, а я понял почему у меня не работало инстанциирование new Calculator(). 


Дело в том, что я запутался в том, где размещал классы и каких импортировал... Внимательнее надо быть!

Вот она красная полоса. Дальше можно как обычно!


Красный, зеленый, рефакторинг.

Задача - написать калькулятор, который умел бы суммировать числа в разных системах счисления.

Но перед тем надо наладить отношения c vcs. Об этом далее...