test
Every time you write an extra line of code, you are adding potential new bugs. In order to build better, more reliable programs, you need to use functional and unit tests on your code.
PHPUnit Test Framework ¶
Symfony integrates an independent class library - named PHPUnit - to bring you a rich testing framework. This chapter does not cover PHPUnit itself, but it has its own Excellent documentation.
It is recommended to use the latest stable version of PHPUnit, is installed with PHAR.
Every test - whether it is a unit test or a functional test - is a PHP class, stored in your Bundle's Tests/
subdirectory. If you follow this principle, then when running all program-level tests, just run the following command:
1 | $ phpunit |
PHPunit is configured through the phpunit.xml.dist
file in the Symfony root directory.
Code coverage can be generated with the —coverage-*
option. To view help information, use —help
to display more content.
Unit testing ¶
Unit testing is a test for a single PHP class. This class is also called "unit (unit )". If you need to test the overall behavior of your application hierarchy, see Functional Tests (#catalog2).
Writing Symfony unit tests is no different than writing standard PHPUnit unit tests. Suppose, you have an extremely simple class named Calculator
located in the Util/
directory of the app bundle:
// src/AppBundle/Util/Calculator.phpnamespace AppBundle\Util; class Calculator{ public function add($a, $b) { return $a + $b; }}
To test it, create a CalculatorTest
File to the tests/AppBundle/Util
directory of your bundle:
// tests/AppBundle/Util/CalculatorTest.phpnamespace Tests\AppBundle\Util; use AppBundle\Util\Calculator; class CalculatorTest extends \PHPUnit_Framework_TestCase{ public function testAdd() { $calc = new Calculator(); $result = $calc->add(30, 12); // assert that your calculator added the numbers correctly! $this->assertEquals(42, $result); }}
According to the agreement , Tests/AppBundle
directory should be copied to the directory where the unit test files under your bundle are located. Therefore, if the class you want to test is located in the src/AppBundle/Util
directory, then put the test code in the tests/AppBundle/Util/
directory.
Just like your real program – autoloading is automatically enabled and completed through the bootstrap.php.cache
file (this part of the configuration The default is in the app/phpunit.xml.dist file)
The test for the specified file or directory is also very simple:
# run all tests of the application# 运行程序中的所有测试$ phpunit # run all tests in the Util directory# 运行指定目录下的所有测试$ phpunit tests/AppBundle/Util # run tests for the Calculator class# 仅运行Calculator类的测试$ phpunit tests/AppBundle/Util/CalculatorTest.php # run all tests for the entire Bundle# 运行整个bundle的测试$ phpunit tests/AppBundle/
Functional test ¶
Functional tests check the integration of different levels of the program (from routing to views). These levels themselves do not change due to the intervention of PHPUnit, but they have unique workflows:
Make a request;
-
Test response (response);
Click a link or submit a form;
Test response;
Clear and repeat.
Your first functional test ¶
The functional test is stored in the Test/AppBundle/Controller
directory A normal PHP file. If you want to test pages handled by your PostController
, first create a new PostControllerTest.php file and inherit a special WebTestCase
class.
As a routine, this test might look like the following code:
// tests/AppBundle/Controller/PostControllerTest.phpnamespace Tests\AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class PostControllerTest extends WebTestCase{ public function testShowPost() { $client = static::createClient(); $crawler = $client->request('GET', '/post/hello-world'); $this->assertGreaterThan( 0, $crawler->filter('html:contains("Hello World")')->count() ); }}
WebTestCase class starts the program kernel. In most cases, this is done automatically. But if the kernel is located in a non-standard directory, you need to adjust the
phpunit.xml.dist file and reset the
KERNEL_DIR environment variable to your kernel directory:
<?xml version="1.0" charset="utf-8" ?><phpunit> <php> <server name="KERNEL_DIR" value="/path/to/your/app/" /> </php> <!-- ... --></phpunit>
createClient() The method returns a client, which can be used to simulate a browser so that you can crawl website pages:
1 | $crawler = $client->request('GET', '/post/hello-world'); |
##1 | $crawler = $client->request('GET', '/post/hello-world'); |
##1 | $client->insulate(); |
1 | $container = $client->getContainer(); |
Note that if you run the client in isolation or use an HTTP layer, the above function will not work . You can view the list of available services in the program through the debug:container
command line tool.
Before Symfony2.6, this command was container:debug
.
If the information you need to check is available in a profiler, profiler should be used instead of container.
Using analysis data ¶
For each request, you can use the Symfony profiler to collect information about how the request is processed internally. information processed. For example, analyzers are often used to verify whether the number of database queries during the loading process of a specified page is less than a given value.
In order to get the Profiler of the latest request, follow the example:
// enable the profiler for the very next request// 为接下来的请求开启profiler$client->enableProfiler(); $crawler = $client->request('GET', '/profiler'); // get the profile 得到分析器$profile = $client->getProfile();
For some details on using the profiler in the test, please refer to How to use it in functional testing Using the analyzer article.
Redirect ¶
When a request returns a redirect response, the client does not automatically follow up. You can detect the response message and then force follow through the followRedirect()
method:
1 | $crawler = $client->followRedirect(); |
If you need the client to automatically follow all redirects, you can force it to do so by using followRedirects()
method:
1 | $client->followRedirects(); |
will false
Passed to the followRedirects()
method, the redirect will no longer be followed:
1 | $client->followRedirects(false); |
Crawler ¶
Every time you make a request through the client, a Crawler instance is returned. Scrapers allow you to traverse HTML documents, pick up DOM nodes, and find links and forms.
Traversing ¶
Just like JQuery, the crawler has various methods to traverse the DOM of an HTML/XML document. For example, the following method finds all input[type=submit]
elements, selects the last one on the page, and then selects its adjacent parent element:
$newCrawler = $crawler->filter('input[type=submit]')
->last()
->parents()
->first();
There are many other methods You can use:
filter('h1.title')
- Matches all nodes of the CSS picker.
filterXpath('h1')
- All nodes matching the XPath expression (expression).
eq(1)
- The node specifying the index.
first()
- The first node.
last()
- The last node.
siblings()
- Siblings.
nextAll()
- All subsequent siblings.
previousAll()
- All previous siblings.
parents()
- Returns all parent nodes.
children()
- Returns all child nodes.
reduce($lambda)
- All nodes when the anonymous function $lambda does not return false.
Since each of the above methods returns a Crawler
grabber object, you can reduce the code of the node picking process by chaining calls to each method:
$crawler
->filter('h1')
->reduce(function ($node, $i) {
if (!$node->getAttribute('class')) {
return false;
}
})
->first();
You can use the count()
function to get the number of nodes stored in a Crawler: count($crawler)
Extract information ¶
The crawler can extract information from nodes:
// 返回第一个节点的属性值$crawler->attr('class'); // 返回第一个节点的节点文本$crawler->text(); // Extracts an array of attributes for all nodes// (_text returns the node value)// returns an array for each element in crawler,// each with the value and href// 提取一个由所有节点的属性组成的数组// (_text返回节点值)// 返回一个由抓取器中的每一个元素所组成的数组// 每个元素有value和href$info = $crawler->extract(array('_text', 'href')); // Executes a lambda for each node and return an array of results// 对每个节点执行一次lambda函数(即匿名函数),最后返回结果数组$data = $crawler->each(function ($node, $i) {
return $node->attr('href');});
Links ¶
To select a link, you can use the above traversal method, or use the more convenient selectLink()
shortcut method:
1 | $crawler->selectLink('Click here'); |
This will select all links that contain the given text, or clickable images whose alt
attributes contain the given text. Similar to other filtering methods, it also returns a Crawler
object.
Once you select a link (link), you can use a special link object (link
object), which is a method specifically designed to handle links (similar to getMethod()
or getUri()
are all helper methods/helpful methods). To click a link, use the Client's click()
method and pass the link object in:
$link = $crawler->selectLink('Click here')->link(); $client->click($link);
Form ¶
Form Can be selected via their buttons, which can be selected using the selectButton() method, just like links:
##1 | $buttonCrawlerNode = $crawler->selectButton('submit'); |
method can select the button
(button) label and the label where submit (submit) is located (i.e. input
Label). It uses several parts of the button to find them:
- value
The value attribute value
The id or alt attribute value for images - id
##button
The id or name attribute value for button tags When you have a After the crawler containing the button, call the - form()
method to get the form instance that owns the button node.
##1
$form = $buttonCrawlerNode->form();
When calling the form() method, you can also pass in an array containing the form field values to replace the default: | When you want to simulate a specific HTTP form submission method, pass it to the second parameter: |
1
$form = $buttonCrawlerNode->form(array(), 'DELETE');
##1 | $client->submit($form); |