Basics of Unit testing in PHP using PHPUnit. Testing modules in PHP Testing database queries

Do you guys do unit testing in PHP? I'm not sure I've ever done this...what is it?

assertEquals(0, count($stack)); array_push($stack, "foo"); $this->assertEquals("foo", $stack); $this->assertEquals(1, count($stack)); $this->assertEquals("foo", array_pop($stack)); $this->assertEquals(0, count($stack)); ) ) ?>

As more complex example I'd like to point you to the my snippet on github.

PHPUnit with code coverage

I like to practice something called TDD using a unit testing system (in PHP, which is phpunit).

What I also really like about phpunit is that it also offers code coverage via xdebug. As you can see from the image below, my class has 100% testing coverage. This means that every line from my Authentication class has been verified, which gives me confidence that the code is doing what it's supposed to. Keep in mind that coverage doesn't always mean your code is well tested. You can have 100% coverage without testing a single line of production code.

Netbeans

Personally, I like to test my code inside Netbeans (for PHP). with a simple mouse click (alt+f6) I can test all my code. This means I don't have to leave the IDE, which I really like, and helps save time switching between sessions.

Good day everyone! Today I would like to talk to you about what it is unit testing in PHP.

When writing even the simplest programs, you periodically have to stop and refactor in order to understand whether the program is written correctly. A code refactoring in PHP I already talked about it in one of the publications on the site, which you can read.

In general, of course, this approach is not bad, but it has significant drawbacks. So, for example, when writing some fairly large project, the code will gradually become clogged with commented out debugging functions, such as print or print_r.In the case of working on your own project, the code of which no one, or almost no one, is going to read, it will, to some extent, be justified.

However, let's imagine this situation: You write websites for customers using your own content management system. Customers are happy, you feel great, but one day you realize that the system you have developed no longer meets the requirements for it, it needs changes. And you start rewriting one part of the system after another.

There comes a moment when one new class, method, condition or cycle destroys the entire system. Changes in one place lead to errors in another. And now they are already climbing endlessly, as if from a cornucopia, and it becomes clear that this is no longer possible. And everything would be much better if first, among other things, they had written PHP Unit tests. It’s not for nothing that Martin Fowler says, that whenever you try to print something through print for debugging or refactoring purposes, it's better to write it as Unit test.

So, we seem to have become familiar with the theory, now let’s move directly to the code. Important notes must be made here; all operations are carried out on a PC running Windows 7 with installed PHP 7 versions. Further it will be point by point.

2) Move the downloaded file to a folder C:\bin. Create a file in the same folder phpunit.bat, write the following contents into it: @php C:\bin\phpunit-6.3.0.phar %*

Please note that the path C:\bin must be defined in a system variable PATH, otherwise when you try to execute the command in the console phpunit You will get an error!

3) Open the console and run the command phpunit, and if everything is correct, then help should be displayed in the console.

Of course there are other ways PHPUnit installations, however, I found this method the most acceptable. For additional information You can always go to the official website of the project PHPUnit. So, the installation is complete, now let's move directly to the code.

// file StackTest.php, located in the directory C:/Projects/php/tests
// connect main class TestCase from the PHPUnit\Framework namespace
use PHPUnit\Framework\TestCase;

// define the class under test as a descendant of the TestCase class
class StackTest extends TestCase
{
// the functions being tested are public and begin with the word test
public function testPushAndPop()
{
$stack = ; // created an array
// and checked the assert statement to ensure that the number of elements in the array is zero
$this->

$this->assertEquals("foo", array_pop($stack));
$this->assertEquals(0, count($stack));
}
}
?>

The code is well commented, but I’ll clarify a couple of points. cornerstone Unit testing is the statement ( assertion). An assertion is your assumption about the expected value, or in other words you assert that the value of a variable, an array element, the result of executing a method, etc. will be equal to such and such a value. In the example above, when the array is initially created, the expected value of its length is 0. This is actually the case in our example.

IN in this case we use only one statement assertEquals although in class TestCase libraries PHPUnit There are several dozen of them, for all occasions, so to speak.

So we wrote the test, what next? And then it needs to be launched. To do this, open the console, go to the folder with our test ( PHP Unit tests usually located in a separate folder tests) and run the command phpunit, passing it the current directory as an argument (indicated by a single dot).

cd C:/Projects/php/tests && phpunit .

This command will automatically go through all PHP tests, which are in this catalog. Once the execution is complete, it will output information about how many tests passed and possibly failed.

Thus, today we found out what is Unit testing in PHP that using it is not only useful, but also necessary. And if you know PHP bad, or don’t know it at all, then especially for you I have an excellent video course, in which, in particular, I discuss the topic in detail Unit testing in PHP.

This tutorial script in PHP shows how a test can be written as a single array of questions and answers. Since there can be several types of questions (choosing “yes” - “no”, choosing one of several options, entering a number or string as an answer), we will need not just an array, but array of arrays, each element of which will describe everything that is needed to display and check the next question. These will be records with the following keys:

  • "q" - displayed question text;
  • "t" - the type of question corresponding to the desired HTML tag: "checkbox" for "yes/no" checkboxes, "text" for a string or number as an answer, "select" - for a list in which you need to select one value from several. We implement the selection of more than one value with the “multiselect” element we invented, which is a group of checkboxes processed together. In fact, a standard list "; break; case "text": $len = strlen ($val["a"]); echo $val["q"]." "; break; case "select": echo $val["q"]." "; break; case "multiselect": $i = explode ("|",$val["i"]); echo $val["q"].": "; foreach ($i as $number=>$ item) echo $item." "; break; ) echo "
    "; ) echo " "; ) function error_check ($q) ( $question_types = array ("checkbox", "text", "select", "multiselect"); $error = ""; if (!isset($q["q"] ) or empty($q["q"])) $error="There is no question text or it is empty"; else if (!isset($q["t"]) or empty($q["t"]) ) $error="Question type not specified or empty"; else if (!in_array($q["t"],$question_types)) $error="Invalid question type specified"; else if (!isset($q[ "a"]) or empty($q["a"]) and $q["a"]!="0") $error="There is no response text or it is empty"; else ( if ($q[" t"]=="checkbox" and !($q["a"]=="0" or $q["a"]=="1")) $error = "Responses 0 or 1 are allowed for the radio button" ; else if ($q["t"]=="select" || $q["t"]=="multiselect") ( if (!isset($q["i"]) or empty($q ["i"])) $error="No list elements specified"; else ( $i = explode ("|",$q["i"]); if (count($i)<2) $error="Нет хотя бы 2 элементов списка вариантов ответа с разделителем |"; foreach ($i as $s) if (strlen($s)<1) { $error = "Вариант ответа короче 1 символа"; break; } else { if ($q["t"]=="select" and !array_key_exists($q["a"],$i)) $error="Ответ не является номером элемента списка"; if ($q["t"]=="multiselect") { $a = explode ("|",$q["a"]); if (count($i)!=count($a)) $error="Число утверждений и ответов не совпадает"; foreach ($a as $s) if ($s!="0" and $s!="1") { $error = "Утверждение не отмечено как верное или неверное"; break; } } } } } } if (!empty($error)) { echo "

    Test error found: ".$error."

    Debug information:

    "; print_r ($q); exit; ) ) function strlwr_($s) yuya"; $len = strlen ($s); $d=""; for ( $i=0;<$len; $i++) { $c = substr($s,$i,1); $n = strpos($c,$hi); if ($n!==FALSE) $c = substr ($lo,$n,1); $d .= $c; } return $d; } ?>



    It's a familiar situation: you're developing an application, solving problems, and sometimes it feels like you're going in circles. Fix one bug and another one immediately appears. Sometimes it's the one you fixed 30 minutes ago, sometimes it's a new one. Debugging becomes very difficult, but there is a good and simple way out of this situation. Unit tests can not only reduce the pain of development, but also help you write code that is easier to maintain and easier to change.

    To understand what unit testing is, it is necessary to define the concept of a “module”. Module(or unit) is a part of the application functionality, the result of which we can check (or test). Unit testing- this is actually a check that a given module works exactly as expected. Having written the tests once, every time you make changes to the code, all you have to do is run the tests to check that everything is correct. This way you will always be sure that your changes will not break the system.

    Myths about unit testing

    Despite all the benefits of unit testing, not all developers use it. Why? There are several answers to this question, but they are all not very good excuses. Let's look at common reasons and try to figure out why they are not justified.

    Writing tests takes too long

    The most common reason: writing tests takes a long time. Of course, some development environments will generate a set of simple tests for you, but writing high-quality, non-trivial tests for your code takes time. It's normal practice to spend some time writing unit tests, which will ultimately save you a lot more time while maintaining the project. If you developed a website, then for sure, when adding new functionality, you tested it simply by clicking on all sorts of links on the site. Simply running a set of tests will be much faster than manually testing all the functions.

    No tests needed - the code already works!

    Another excuse for developers: the application works - there is no need for testing. They know the application and its weak points, and can fix everything that needs to be done, sometimes in a few seconds. But imagine hiring a new developer to develop an application who has no idea how the code works. A beginner can make some changes that can break anything. Unit tests will help avoid such situations.

    And one more reason why developers don’t like tests - they’re not interesting. Developers by nature love to solve problems. Writing code is like creating something out of emptiness, creating order out of chaos, creating something useful. As a result, writing tests becomes a boring task, something that can be done after the main job. And testing fades into the background. But look at it from the other side: catching some unpleasant bug for hours is also no fun.

    Example

    Let's start practicing. In our examples we will use the PHPUnit library. The easiest way to install PHPUnit is to pull from the PEAR channel.

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

    If everything goes well, all the necessary tools will be installed. If you want to install PHPUnit manually, you will find instructions here.

    First test

    Using PHPUnit, you will write test classes containing test methods, all of which must satisfy the following conventions:

    • In most cases, you will extend the PHPUnit_Framework_TestCase class, which will give you access to built-in methods such as setUp() and tearDown() .
    • The name of the testing class is formed by adding the word Test to the name of the class being tested. For example, you are testing the RemoteConnect class, so the name of the tester is RemoteConnectTest.
    • Test method names should always start with “test” (for example, testDoesLikeWaffles()). Methods must be public. You can use private methods in your tests, but they will not be run as tests via PHPUnit.
    • Testing methods do not accept parameters. You should write testing methods that are as independent and self-contained as possible. sometimes it's inconvenient, but you'll get cleaner, more efficient tests.

    Let's write a small class to test RemoteConnect.php:

    If we want to test the functionality for connecting to a remote server, then we must write a similar test:

    assertTrue($connObj->connectToServer($serverName) !== false); ) ) ?>

    The testing class inherits the base PHPUnit class, and therefore all the necessary functionality. The first two methods, setUp and tearDown, are examples of this built-in functionality. These are helper functions that are part of every test. They are executed before and after running all tests, respectively. But now we are interested in the testConnectionIsValid method. This method creates an object of type RemoteConnect, and calls the connectToServer method.

    We call another helper function, assertTrue, in our test. This function defines the simplest assertion: it checks whether the passed value is true. Other helper functions perform checks for properties of objects, the existence of files, the presence of keys in an array, or matching a regular expression. In our case, we want to make sure that the connection to the remote server is correct, i.e. is that the connectToServer function returns true .

    Running tests

    Tests are launched by simply calling the phpunit command indicating your php file with tests:

    Phpunit /path/to/tests/RemoteConnectTest.php

    PHPUnit runs all the tests and collects some statistics: whether the test completed successfully or not, the number of tests and assertions and displays all this. Example output:

    PHPUnit 3.4 by Sebastian Bergmann. Time: 1 second Tests: 1, Assertions: 1, Failures 0

    For each test performed, the result will be displayed: "." if the test completed successfully, “F” if the test failed, “I” if the test could not be completed, and “S” if the test was skipped.

    In our example, the test completed successfully, which means the function under test works as expected. But checking a function only for correct operation is not enough; it is also necessary to check the function’s operation if used incorrectly.

    PHPUnit provides a set of basic checks that cover most possible situations. Of course, sometimes you have to write tricky tests that test non-trivial functionality of your application. But mostly the basic functions of PHPUnit are used:

    AssertTrue / AssertFalse Checking the passed values ​​for equality true/false
    AssertEquals Checking passed values ​​for equality
    AssertGreaterThan Compares two variables (there are also LessThan, GreaterThanOrEqual, and LessThanOrEqual)
    AssertContains Does the passed variable contain the specified value?
    AssertType Checking the type of a variable
    AssertNull Testing for null equality
    AssertFileExists Checking file existence
    AssertRegExp Testing with regular expression

    For example, there is a function that returns an object (returnSampleObject) and we want to make sure that the returned object is of the type we need:

    returnSampleObject(); $this->assertType("remoteConnect", $returnedObject); ) ?>

    One test - one assertion (assert)

    As with all areas of software development, there are best practices in testing. One of them is “one test, one assertion”. This rule will help you write small and easy to read tests. But sometimes thoughts arise: “Since we’re checking this here, we’ll check something else at the same time!” For example:

    assertGreaterThan(0,strlen($string)); $this->assertContains(“42”,$string); ) ?>

    Our testIsMyString performs two different tests. First, test for an empty string (length must be > 0), then test for the substring “42” contained in the string. But this test can fail in both the first and second cases, and the error message in both cases will be the same. Therefore, it is worth adhering to the principle of “one test - one statement”.

    Test-driven Development (test-driven development)

    It would be bad to talk about testing without mentioning a common development technique - test-driven development ( test driven development). TDD is a technique used in software development. The main idea of ​​this technique is that tests are written first, and only after the tests are written, application code is written that will pass these tests.

    This is the first part of the "PHPUnit for Beginners" series. In this guide, we will explain why to cover your code with unit tests and the full power of the PHPUnit tool. At the end we will write a simple test using PHPUnit.
    • PHPUnit for beginners. Part 1: Start using it.

    Types of tests

    Before we dive into PHPUnit, let's understand the different types of tests. Depending on how you want to categorize them, PHPUnit applies all types of software development tests.

    Let's categorize tests based on their level of specificity. According to Wikipedia. In general, there are 4 recognized test levels:

    • Unit testing: This level tests the smallest unit of functionality. From a developer's point of view, the developer's job is to ensure that the function being tested does exactly what it is designed to do. Thus, it must be minimally or completely independent of another function or class. It must be written in such a way that it is executed entirely in memory, i.e. it should not connect to the database, should not access the network or use the FS, etc. Unit testing should be as simple as possible.
    • Integration testing: This level “connects” different units of code and tests whether their combinations work correctly. It is built on top of unit testing and is able to catch bugs that cannot be detected using unit testing, because Integration testing checks whether class A works with class B.
    • System testing: it is designed to reproduce the operation of scenarios in conditions close to combat. This, in turn, is built on top of integration testing. While integration testing ensures that different parts of the system work smoothly. System testing is responsible for making sure the system works as the user expects before sending it to the next level.
    • Acceptance testing: While the above tests are intended for developers at the development stage, acceptance testing is actually performed by users of the software. Users are not interested in the internal features of the software, they are only interested in how the software works.

    If we put test types in a pyramid, it would look like this:

    What is PHPUnit

    Looking at the pyramid at the top, we can say that unit testing is the building block for all other types of testing. When we build a strong foundation, we are able to build a sustainable application. However, writing tests manually and running tests every time you make changes is a labor-intensive process. If there was a tool to automate this process, writing tests would become a much more enjoyable experience.

    This is where PHPUnit comes into the picture. Currently, PHPUnit is the most popular unit testing framework in PHP. In addition to having features such as mocking objects, it can also analyze code coverage, logging, and provide thousands of other features.

    Let's install PHPUnit on our system:

    1. Download it: PHPUnit is distributed in a PHAR(PHp ARhive) file. You can download it.
    2. Add its path to the $PATH system variable: after downloading the PHAR file, make sure that it is executable and the path where it is located is registered in the $PATH system variable. That. you can run it from anywhere.

    If you are working on a Unix-like system, then you can do this with the following commands:

    $ wget https://phar.phpunit.de/phpunit.phar $ chmod +x phpunit.phar $ sudo mv phpunit.phar /usr/local/bin/phpunit

    If you did everything correctly, you can see the version of PHPUnit installed by typing the command in your terminal:

    $phpunit --version

    Your first unit test

    It's time to write your first unit test! First, we need some class that we will test. Let's write a simple class called Calculator. And let's write a test for it.

    Create a file "Calculator.php" and copy the code below into it. This Calculator class has only one add method.

    Class Calculator ( public function add($a, $b) ( return $a + $b; ) )

    Now create a test file "CalculatorTest.php" and copy the following code into it. We will look at each method in more detail.

    Require "Calculator.php"; class CalculatorTests extends PHPUnit_Framework_TestCase ( private $calculator; protected function setUp() ( $this->calculator = new Calculator(); ) protected function tearDown() ( $this->calculator = NULL; ) public function testAdd() ( $result = $this->calculator->add(1, 2); $this->assertEquals(3, $result) )

    • Line 2: connect the file of the tested class Calculator.php. Since this is the class we are going to test in this file, make sure it is included.
    • Line 8: setUp() is a method that is called before each test. Remember he called before each test, which means that if you add another test method to this class, it will be called before it too.
    • Line 13: Similar to the setUp() method, tearDown() is called after each test.
    • Line 18: testAdd() is a test method for the add() method. PHPUnit will recognize every method starting with test as a test method and run it automatically. This method is actually very simple: first we call the Calculator::add() method to calculate the value of 1 plus 2, and then we check that this method returned the correct value using assertEquals() from PHPUnit.

    The final part of the work done is to run PHPUnit and check that all tests pass (run without errors). In your terminal, go to the directory where you created the test file and run the following command:

    $phpunit CalculatorTest.php

    If you did everything correctly, you should see something like this:

    PHPUnit 3.7.32 by Sebastian Bergmann. . Time: 31ms, Memory: 2.25Mb OK (1 test, 1 assertion)

    Conclusion

    We have completed the first tutorial in the "PHPUnit for Beginners" series. In the next article we are going to show you how to use a Data Provider in your tests.

    We hope this simple guide will help you in your development and help you get started using unit testing.

    If you liked the translation on this topic, read us at



Publications on the topic