Home > Article > Backend Development > Web-side PHP code function coverage testing solution, php coverage_PHP tutorial
1. About code coverage
There are many levels of measuring code coverage, such as line coverage, function/method coverage, class coverage, branch coverage, etc. Code coverage is also an important criterion for measuring test quality. For black box testing, if you are not sure whether your test case has actually run through every line of code in the system, you will always have to discount the integrity of the test. . Therefore, the industry has its own set of code coverage solutions for almost every programming language. PHP, the most beautiful language in the world, is certainly no exception. PHPUnit and Spike PHPCoverage provide a set of code coverage testing solutions based on xdebug. In this article, I will describe my own solution for PHP code function coverage testing based on the specific business scenarios I encountered.
2. Business background
Suppose we develop a website online and hand it over to business test colleagues for functional testing. So how did they test it? Usually, it is nothing more than developers deploying the website, and then testers try out all the online functions, including some abnormal usage conditions. For business testing, as long as I test all function points and detect all abnormal usage conditions, it is done. But for development, what I'm more curious about is, did you run all the code I wrote? Is there some code that can only be triggered under very special circumstances, and you have never measured these circumstances? At this point, code coverage may be needed to help.
In fact, I first thought of xdebug to test coverage, which only requires two or three functions, as follows:
xdebug_start_code_coverage(); <span>//</span><span>开始收集代码行覆盖情况</span> <span> xdebug_get_code_coverage(); </span><span>//</span><span>获取截至目前所跑过的代码文件名和行号</span> <span> xdebug_stop_code_coverage(); </span><span>//</span><span>停止收集代码行覆盖情况</span>
The interface provided by xdebug can be used to test line coverage. Does this meet the requirements? In fact, the row coverage granularity is a bit fine. In actual projects, developers may fine-tune the code. For example, in this test, you ran through line 10 of the A.php file, but one day I fine-tuned A.php and added two more lines of code between lines 9 and 10 of A.php. . As a result, the original line 10 became line 12, and the line coverage information of xdebug only recorded the line number... Wouldn't the previous data be inaccurate? . . After careful consideration, I think function coverage is a good granularity. In relatively mature projects, there are rarely large-scale function changes. But the problem is that xdebug does not provide an interface for function coverage.
So, the scene we are encountering now is:
【1】I hope to measure a list of all functions covered in a certain test, know how many functions there are in this project, and calculate whether the coverage rate is high enough.
【2】After the test is completed, a coverage report should be generated to visualize the code coverage.
【3】The complete test process is as follows:
Instrumentation means some preparation work before test execution.
3. Function coverage solution
(1) Principle
xdebug inherently provides support for line coverage, and we need to calculate the function coverage ourselves. Function coverage requires two pieces of data, one is which functions are executed, and the other is the total number of functions in the file.
The total number of functions in the file. Since it is impossible for us to execute all functions, this part can only be achieved through static scanning of the code. If it is in C or Java, you may need a lexical analysis tool, but in front of the most beautiful language PHP, we don't need to be so complicated at all. Starting from PHP 4.3, PHP Zend Engine has a built-in tokenizer function to help developers perform source code lexical analysis. We only need to find the lexical rules corresponding to the functions defined in PHP, and we can easily get all the functions in the specified PHP file.
The interface defined by tokenizer is also very simple:
<span>array</span> <span>token_get_all</span> (<span>string</span> <span>$source</span>)
This function performs file parsing and splits the PHP source code into an array composed of tokens.
<span>string</span> <span>token_name</span> (int <span>$token</span>)
Convert the token in integer form to string form. Similar to the strerror function in C language. With tokenizer, you can design a finite state machine according to the rules and formats defined by PHP functions to complete the analysis of all functions. I wrote a relatively simple version of this part of the code, and took it out separately for your reference only: PHPFunctionParser
求函数覆盖率的另外一个难点在于获取被执行的函数列表。这地方让我们走了一些弯路。一开始一个最简单的办法,我们既然通过xdebug拿到被执的行,可以通过行号来反推此行属于哪一个函数。然而每一次的请求获取的行号信息量是非常大的,如果一个求情执行了1000行,那就要进行1000次判断,效率上会比较差。调研了一番之后,发现xdebug提供了function trace的功能,可以把一次请求中的函数调用关系获取到,只不过拿到了函数名字,却没办法得到它所在的文件。于是,再次调研一番,发现了Reflection,给定方法名和类名,可以反推出来它在哪个文件中定义。于是我们使用function trace把函数调用关系暂存在一个临时文件中,然后通过文件解析,拿到执行的函数名(如果是类方法,则是“类名::函数名”的形式),再通过reflection机制反推出定义这个函数的文件即可。再次体会到了世界上最美语言的强大之处。
(2)插桩
为了降低使用门槛,我们尽可能少地改变PHP源代码为好。xdebug收集信息的原理是分别调用xdebug_start_code_coverage和xdebug_stop_code_coverage来控制覆盖率信息收集的开始和结束,因此不可避免地要改变源代码。此处我们的解决办法是,将xdebug_stop_code_coverage通过register_shutdown_function注册为php程序结束前必须要跑的一段程序(类似C语言的atexit函数),将其封装到一个文件中,然后在源代码第一行require这个文件即可。如果你的PHP框架是CodeIgniter这种所有请求都有一个统一入口index.php的框架,那就只需要改变这一个文件即可,对源代码只有一行的改动!实际上,目前基本上所有的PHP框架,都是以一个index.php文件作为所有请求的入口。
我们对源代码的改动只有入口文件index.php的第一行加入了一句话:
<?php <span>require_once</span> "/file/path/to/phpcoverage.php"; ?>
而phpcoverage.php核心代码逻辑大致如下:
<?<span>php ……</span> <span>function</span><span> xdebugPhpcoverageBeforeShutdown(){ ……</span> <span>$lineCovData</span> =<span> xdebug_get_code_coverage(); xdebug_stop_code_coverage(); ……</span><span> xdebug_stop_trace(); ……</span><span> } </span><span>register_shutdown_function</span><span>(‘xdebugPhpcoverageBeforeShutdown’); </span><span>…… xdebug_start_trace(……</span><span>); xdebug_start_code_coverage();<br />//备注:上面省略号表示非关键代码,这里就不展示了</span>
(3)信息存储
我们的函数覆盖率测试有了思路,使用xdebug的function trace获取一次请求中所有函数的调用关系,得到执行过的所有函数,输出到文件中,通过文件解析和reflection获得被执行的函数名和该函数所在文件。将这些信息存入数据库或文件即可。
之前试用Spike的时候,我们发现这些信息以xml格式存入文件,数据冗余度很高,导致几个测试下来,文件已经非常大了。这显然不是我们想看到的。因此在数据存储的时候,我们直接将数据做json格式的序列化,字符串形式存在文件中,大大减少了文件大小。与此同时,我们再通过请求来源的IP和日期作为分隔,分别存储不同的文件。这样,来自每个机器每天的请求数据都能一目了然,向着“精准”的方向又迈进了一步,可以对测试人员的每个请求做精确的监控。下图是我们在业务实践中搜集的部分数据文件截图:
这样,来自任何一个IP的每一次Web请求,它所覆盖的行和函数信息,都会被记录到文件中。对于一般的项目测试中,也就只有几个测试人员在使用,所以不需要考虑一些性能问题。
4. 报告生成
上面讲了生成覆盖率数据的原理,不过我们至此获得的只是一份份的数据文件,如何汇总成一份完整的报告呢?这就需要我们自己来写一段脚本解析刚才生成的数据文件了。我们的做法是借鉴了开源工具spike phpcoverage的模版,并加入自己的代码逻辑,特别是加入了该工具所不具有的函数覆盖率统计数据。我们自己测试的web页面生成的报告如下:
图中可以看到每个文件的行覆盖率,函数覆盖率,还有总的覆盖率统计数据。如果需要更精确的数据,可以点进文件连接,查看到底覆盖的是哪些代码行(蓝色为覆盖,红色为未覆盖):
5. 总结
When doing web testing in business testing, code coverage is an important indicator to measure test quality. We hope to use this method to be as "accurate" as possible. After the test is executed, we can accurately see which lines of code have been executed and which lines have not been executed. Analyze the reasons why the test cases were not executed to improve the test cases. The process of using the tool is also very simple. Instrumentation=>Testing=>Collecting data=>Producing a report. And this solution minimizes the impact on business code and only requires changing one line of code. Even if something goes wrong in the middle, you can quickly restore the code to its original state. Let testing be at ease and development can also be at ease.
However, the last thing that needs to be emphasized is that covering all the code does not mean that the test is complete. But if it is not covered, it must be incomplete. Therefore, the greatest significance of this solution is to be able to discover some missing codes in the test and find some problems. In fact, it can also help new employees understand the entire project code structure. We can clearly know which code on the server is running for each of our browser requests.