Unit testing from beginner to beginner

Hello. This post

prompted me to write an article . It provides a description of the tools and some theoretical information. I myself am just starting to understand unit testing and testing in general, so I decided to share some information regarding this matter. And also organize your knowledge and skills. Next, I will try to explain the testing process step by step in a simple philistine language, since I have not found a chewed description anywhere on the Internet, step by step so to speak. Whoever is interested and who wants to try to figure it out all the same, welcome. What I mean, automated testing and unit testing, I will not write, there is a Wikipedia for this.





For our tests we will use, probably the most popular framework - PHPUnit. First we need to install it. This is easiest to do through PEAR. How to do this is written in the documentation. Two commands are used ( from the documentation ):

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


Naturally, the path to PEAR should be spelled out in PATH. When the necessary files are loaded, our PHPUnit will be completely ready to test our code.

Let's rock


So, let's begin. Let us have some kind of data model. It has two attributes: a string and a number. There is a setter method and methods for saving and loading values ​​(to a file).

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;}
}

We have defined the basic methods and attributes of the classes. Since we don’t read and write anything yet, by condition we return false.

We introduce some artificial restrictions for the attributes:
  • The string cannot be empty
  • The number must be greater than 10 but less than 20
  • Naturally, the data must be correctly entered into the file and read from there.

Of course, in real projects there are more restrictions, but for starters we have enough :)

Now we will postpone our model for a while and take up the test. The test is a regular class inherited from the base class (in our case, PHPUnit_Framework_TestCase). Methods of this class are tests. Create a unit folder for our test.

unit / TestModelTest.php:
require_once 'PHPUnit/Autoload.php';
class TestModelTest extends PHPUnit_Framework_TestCase {
	function testTrue() {
		$this->assertTrue(true);
	}
}


TestModelTest is our test class for the TestModel class.
testTrue () is the test itself. In it, we define scenarios for specific cases. In this test, we will simply verify that true is true :) This is done using the assertTrue method (assert-assert). Those. we claim that true is true.
Run our test. PHPUnit just specify the folder in which all our tests lie.
phpunit unit

We get:
PHPUnit 3.6.10 by Sebastian Bergmann.
.
Time: 0 seconds, Memory: 2.75Mb
OK (1 test, 1 assertion) 

Hooray, our test works! We go further.

TDD

TDD - Test Driven Development - an approach in which, roughly speaking, tests are first written, and then gradually, based on them, the main class is written. More on wikipedia. Let's go this way. We already have a module framework. Requirements too. Now we will write test cases based on our requirements.

unit / TestModelTest.php:
setAttributes(15,'');	
		$this->assertFalse($model->saveData());	//мы утверждаем, что на выходе должна быть ложь!
		$model->setAttributes(15,'aaaa');
		$this->assertTrue($model->saveData());	//а теперь истина
	}
	//проверяем условие 10setAttributes(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);
	}
}


We described all three cases in three methods. For each his own. Now run the tests:

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! Well, nothing, it should be so :) Now add some code to our model.

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;
	}
}

I think nothing in the code should be difficult.

Run the tests:
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.


Already better. Already passes twice as many checks. Let's go in order:
1. testStringCannotBeEmpty. The string cannot be empty. Add a check:
	public function saveData() {
		if (!strlen($this->str)) return false;
		......
	}

2.testIntMustBeGreaterThanTenAdnSmallerThanTwenty. Condition 10
	public function saveData() {
		if (!strlen($this->str)) return false;
		if ($this->num<10 || $this->num>20) return false;
		......
	}

3. testSaveLoad. Yeah! Another mistake, at first glance it is difficult to notice. The line written is not equal to the line read. It's all at the end of the line. We go to the documentation and read or learn about the flag FILE_IGNORE_NEW_LINES.
We are correcting.
	public function loadData() {
		$arr=file($this->fname, FILE_IGNORE_NEW_LINES);
		....
}

(spoiler: condition 2 is not specifically met)

Run:
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.

We look at the 46th line (for me): $ model-> setAttributes (20, 'test3'); We did not consider the extreme case! We fix:
	public function saveData() {
		if (!strlen($this->str)) return false;
		if ($this->num<=10 || $this->num>=20) return false;
		......
	}


We run our tests:
Time: 0 seconds, Memory: 2.75Mb
OK (3 tests, 11 assertions)

Hooray, all three tests have passed. Our model meets the requirements. Which was required :)

Conclusion


This article in no way claims to be a complete unit testing guide, much less a TDD manual. The purpose of this article is primarily to systematize my (beginner) knowledge in this area. And I really hope that it will help someone as an initial aid to immerse yourself in the deep world of automatic testing.
Thanks for attention.

Also popular now: