Pull to refresh

Unit-тестирование от начинающего начинающим

Reading time 5 min
Views 78K
Здравствуйте.

На написание статьи меня сподвигнул этот пост. В нём приведено описание инструментов и некоторая теоретическая информация.

Сам я только начинаю разбираться в unit-тестировании и тестировании вообще, поэтому решил поделиться некоторой информацией касательно этого дела. А также систематизировать свои знания и навыки. Далее постараюсь объяснить процесс тестирования по шагам простым обывательским языком, так как нигде в интернете не нашёл разжёванного описания, по шагам так сказать. Кому интересно и кто хочет попробовать всё-таки разобраться, добро пожаловать.

Что такое автоматизированное тестирование и unit-тестирование я писать не буду, для этого есть википедия.

Для наших тестов будем использовать, наверное самый популярный фрэймворк – PHPUnit. Для начала нам необходимо его утановить. Делать это проще всего через PEAR. Как это сделать, написано в документации. Используется две команды(из документации):

pear config-set auto_discover 1
pear install pear.phpunit.de/PHPUnit


Естественно, путь к PEAR должен быть прописан в PATH. Когда загрузятся необходимые файлы, наш PHPUnit будет полностью готов к тестированию нашего кода.

Let's Rock


Итак, начнём. Пусть у нас будет какая-то модель данных. В ней два атрибута: строка и число. Есть метод-сеттер и методы для сохранения и загрузки значений (в файл).

TestModel.php
class TestModel {
	public $num;
	public $str;

	public function setAttributes($i, $s) {}
	/*
	@return: true, если данные сохранены
			false, в обратном случае
	*/
	public function saveData() {return false;}
	/*
	@return: true, если данные успешно прочитаны из файла
			false, в обратном случае
	*/
	public function loadData() {return false;}
}

Мы определили базовые методы и атрибуты классов. Так как у нас пока ничего не читается и не пишется, по условию возвращаем false.

Введём некоторые искуственные ограничения для аттрибутов:
  • Строка не может быть пустой
  • Число должно быть больше 10, но меньше 20
  • Естественно, данные должны правильно заноситься и в файл и читаться оттуда

Конечно, в реальных проектах ограничений больше, но для начала нам хватит :)

Теперь отложим на время нашу модель и займёмся тестом. Тест представляет собой обычный класс, унаследованный от базового класса (в нашем случае PHPUnit_Framework_TestCase). Методы этого класса, и есть тесты. Создадим папку unit для нашего теста.

unit/TestModelTest.php:
require_once 'PHPUnit/Autoload.php';

class TestModelTest extends PHPUnit_Framework_TestCase {
	function testTrue() {
		$this->assertTrue(true);
	}
}


TestModelTest — наш тест-класс для класса TestModel.
testTrue() — непосредственно тест. В нём мы определяем сценарии для конкретных случаев. В данном тесте мы просто проверим, что true является true :) Это делается при помощи метода assertTrue (assert-англ-утверждать). Т.е. мы утверждаем, что true является истинной.
Запустим наш тест. PHPUnit достаточно указать папку, в которой лежат все наши тесты.
phpunit unit

Получаем:
PHPUnit 3.6.10 by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 2.75Mb

OK (1 test, 1 assertion) 

Ура, наш тест работает! Идём далее.

TDD

TDD – Test Driven Development – подход, при котором, грубо говоря, сначала пишутся тесты, а потом постепенно, исходя из них, пишется основной класс. Подробнее в википедии. Пойдём этим путём. Каркас модуля у нас уже есть. Требования тоже. Теперь напишем тестовые случаи, исходя из наших требований.

unit/TestModelTest.php:
<?php

require_once 'PHPUnit/Autoload.php';
require_once dirname(__FILE__).'/../TestModel.php';


class TestModelTest extends PHPUnit_Framework_TestCase {
	
	//проверяем условие, что строка не может быть пустой
	function testStringCannotBeEmpty() {
		$model=new TestModel;
		$model->setAttributes(15,'');	
		$this->assertFalse($model->saveData());	//мы утверждаем, что на выходе должна быть ложь!
		
		$model->setAttributes(15,'aaaa');
		$this->assertTrue($model->saveData());	//а теперь истина
	}
	
	//проверяем условие 10<i<20
	function testIntMustBeGreaterThanTenAdnSmallerThanTwenty() {
		$model=new TestModel;
		/* Условия ложны */
		$model->setAttributes(2,'test1');	
		$this->assertFalse($model->saveData());	
		
		$model->setAttributes(10,'test2');	
		$this->assertFalse($model->saveData());	
		
		$model->setAttributes(20,'test3');	
		$this->assertFalse($model->saveData());	
		
		$model->setAttributes(25,'test4');	
		$this->assertFalse($model->saveData());	
		
		/* Условие истинно */
		$model->setAttributes(15,'test5');	
		$this->assertTrue($model->saveData());	
	}
	
	//проверяем корректность чтения/записи
	function testSaveLoad() {
		$i=13;
		$str='test';
		$model=new TestModel;
		$model->setAttributes($i,$str);	
		$this->assertTrue($model->saveData());	//записали данные

		$fetchModel=new TestModel;
		$this->assertTrue($fetchModel->loadData());	//прочитали данные
		//сравниваем прочитанные данные и исходные
		$this->assertEquals($fetchModel->num,$i);
		$this->assertEquals($fetchModel->str,$str);
	}
}


Мы описали все три случая в трёх методах. Для каждого свой. Теперь запустим тесты:

PHPUnit 3.6.10 by Sebastian Bergmann.

FFF

Time: 0 seconds, Memory: 2.75Mb

There were 3 failures:

1) TestModelTest::testStringCannotBeEmpty
Failed asserting that null is false.
...
2) TestModelTest::testIntMustBeGreaterThanTenAdnSmallerThanTwenty
Failed asserting that null is false.
...
3) TestModelTest::testSaveLoad
Failed asserting that null is true.
...
FAILURES!
Tests: 3, Assertions: 3, Failures: 3.


Damn! Ну ничего, так и должно быть :) Теперь добавим немного кода в нашу модель.

unit/TestModelTest.php:
class TestModel {
	public $num;
	public $str;
	
	public $fname="file.txt";
	
	public function setAttributes($i, $s) {
		$this->num=(int)$i;
		$this->str=$s;
	}
	
	public function saveData() {
		$h=fopen($this->fname,'w+');
		$res=fputs($h, $this->str."\r\n".$this->num);
		fclose($h);
		return (bool)$res;
	}
	
	public function loadData() {
		$arr=file($this->fname);
		if ($arr==false) return false;
		list($this->str,$this->num)=$arr;
		return (bool)$arr;
	}
}

Думаю, в коде ничего не должно вызывать затруднений.

Запускаем тесты:
There were 3 failures:

1) TestModelTest::testStringCannotBeEmpty
Failed asserting that true is false.
...
2) TestModelTest::testIntMustBeGreaterThanTenAdnSmallerThanTwenty
Failed asserting that true is false.
...
3) TestModelTest::testSaveLoad
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'test
-
-'
+'test'

FAILURES!
Tests: 3, Assertions: 6, Failures: 3.


Уже лучше. Уже проходит в два раза больше проверок. Идём по порядку:
1. testStringCannotBeEmpty. Строка не может быть пустой. Добавляем проверку:
	public function saveData() {
		if (!strlen($this->str)) return false;
		......
	}

2. testIntMustBeGreaterThanTenAdnSmallerThanTwenty. Условие 10<x<20. Проверка:
	public function saveData() {
		if (!strlen($this->str)) return false;
		if ($this->num<10 || $this->num>20) return false;
		......
	}

3. testSaveLoad. Ага! ещё одна ошибка, на первый взгляд её сложно заметить. Строка записанная не равна строке прочитанной. Всё дело в конце строки. Идём в документацию и читаем или узнаём про флаг FILE_IGNORE_NEW_LINES.
Исправляем.
	public function loadData() {
		$arr=file($this->fname, FILE_IGNORE_NEW_LINES);
		....
}

(spoiler: условие 2 специально не соблюдено)

Запустим:
There was 1 failure:

1) TestModelTest::testIntMustBeGreaterThanTenAdnSmallerThanTwenty
Failed asserting that true is false.

TestModelTest.php:30
C:\Program Files\php\phpunit:46

FAILURES!
Tests: 3, Assertions: 8, Failures: 1.

Смотрим на 46 строчку (у меня): $model->setAttributes(20,'test3'); Мы не рассмотрели крайний случай! Исправляем:
	public function saveData() {
		if (!strlen($this->str)) return false;
		if ($this->num<=10 || $this->num>=20) return false;
		......
	}


Запускаем наши тесты:
Time: 0 seconds, Memory: 2.75Mb

OK (3 tests, 11 assertions)

Ура, все три теста прошли. Наша модель удовлетворяет поставленным требованиям. Что и требовалось :)

Заключение


Эта статья ни в коей мере не претендует на полное руководство по unit-тестированию, ни тем более на руководство по TDD. Цель данной статьи — в первую очередь систематизировать мои(начинающего) познания в данной сфере. И я очень надеюсь, что она поможет кому-нибудь в качестве начального подспорья для погружения в глубокий мир автоматического тестирования.
Спасибо за внимание.
Tags:
Hubs:
+33
Comments 46
Comments Comments 46

Articles