立博APP

<code id="choey"><ol id="choey"><span id="choey"></span></ol></code>
    1. <var id="choey"></var>

        <code id="choey"></code>
      1. <output id="choey"><legend id="choey"></legend></output>

          <label id="choey"><legend id="choey"></legend></label>
        1. <code id="choey"></code>
          <var id="choey"><ol id="choey"><big id="choey"></big></ol></var>
          全國免費咨詢電話: 010-59418061
          關注尚腦
          Web端PHP代碼函數覆蓋率測試解決方案
            1. 關于代碼覆蓋率

            衡量代碼覆蓋率有很多種層次,比如行覆蓋率,函數/方法覆蓋率,類覆蓋率,分支覆蓋率等等。代碼覆蓋率也是衡量測試質量的一個重要標準,對于黑盒測試來說,如果你不確定自己的測試用例是否真正跑過了系統里面的每一行代碼,在測試的完整性上總要打些折扣。因此,業界幾乎對各種編程語言都有自己的一套代碼覆蓋率解決方案。世界上最美的語言PHP當然也不例外。PHPUnit和Spike PHPCoverage提供了一套基于xdebug的代碼覆蓋率測試方案。在本文中,我將針對自己碰到的特定業務場景,講述一下自己進行PHP代碼函數覆蓋率測試的解決方案。

            2. 業務背景

            假設我們在線開發了一個網站,交給業務測試的同事去進行功能測試。那他們是怎么測試的呢?通常情況下,無非是開發人員把網站部署好了,然后測試人員把網上所有功能都試用一遍,包括一些異常使用情況。對于業務測試來說,只要我把所有的功能點都測了,把所有異常使用情況也測到了,那就完成了。但是對于開發來說,我比較好奇的是,你是否把我寫的所有代碼都跑到了?會不會存在一些代碼,只有在很特殊的情況下才能觸發,而你從來沒有測到過這些情況?這時,可能就需要代碼覆蓋率來出馬了。

            其實我首先想到了xdebug來測試覆蓋率,只需要兩三個函數即可,如下:

            xdebug_start_code_coverage(); //開始收集代碼行覆蓋情況

            xdebug_get_code_coverage(); //獲取截至目前所跑過的代碼文件名和行號

            xdebug_stop_code_coverage(); //停止收集代碼行覆蓋情況

            xdebug提供的接口可以用于測試行覆蓋率,這是否能滿足要求呢?其實,行覆蓋率顆粒度有點細,實際項目中,開發人員可能會對代碼進行微調。比如,這次測試,你跑過了A.php文件的第10行,但是我有一天對A.php進行了微調,在A.php第9行和第10行之間又加了兩行代碼。于是,原來的第10行變為了第12行,而xdebug的行覆蓋信息只記錄了行號……這樣之前的數據豈不是不準確了么。。??紤]再三,我覺得函數覆蓋是個不錯的顆粒度。在相對成熟的項目中,很少有大規模函數變動的情況。不過問題是,xdebug并沒有提供函數覆蓋的接口。

            于是,我們現在碰到的場景是:

            【1】希望測到某次測試中所覆蓋的所有函數列表,知道這個項目總共有多少個函數,計算一下覆蓋率是否足夠高。

            【2】測試完成之后,要生成一份覆蓋率報告,將代碼的覆蓋情況可視化。

            【3】完整測試的流程如下:

           

            其中插樁的意思是在測試執行之前的一些準備工作。

            3. 函數覆蓋率解決方案

           ?。?)原理

            xdebug天生提供了對行覆蓋率的支持,我們要自己計算出函數覆蓋率。函數覆蓋率需要兩點數據,一個是哪些函數被執行,一個是文件中總共有多少個函數。

            文件中總共的函數量,由于我們不可能把所有函數都執行一遍,因此這部分只能通過代碼靜態掃描來實現。如果是在C++或者Java中,可能就需要詞法分析工具了,然而在最美的語言PHP面前,我們完全不需要那么復雜。從PHP4.3開始,PHP Zend Engine中內置了tokenizer功能,幫助開發者做源碼詞法分析。我們只需要找到PHP中定義函數時所對應的詞法規律,就可以輕松得到指定PHP文件中的全部函數了。

            tokenizer定義的接口也十分簡單:

            array token_get_all (string $source)

            該函數進行文件解析,將php源代碼拆成由token組成的數組。

            string token_name (int $token)

            將整數形式的token轉變為字符串形式。類似于C語言中的strerror函數。有了tokenizer,自己再根據php函數定義的規律和格式設計一個有限狀態機,即可完成全量函數的解析。這部分代碼,本人寫了個比較簡陋的,把它單獨拿出來,僅供大家參考:PHPFunctionParser

            求函數覆蓋率的另外一個難點在于獲取被執行的函數列表。這地方讓我們走了一些彎路。一開始一個最簡單的辦法,我們既然通過xdebug拿到被執的行,可以通過行號來反推此行屬于哪一個函數。然而每一次的請求獲取的行號信息量是非常大的,如果一個求情執行了1000行,那就要進行1000次判斷,效率上會比較差。調研了一番之后,發現xdebug提供了function trace的功能,可以把一次請求中的函數調用關系獲取到,只不過拿到了函數名字,卻沒辦法得到它所在的文件。于是,再次調研一番,發現了Reflection,給定方法名和類名,可以反推出來它在哪個文件中定義。于是我們使用function trace把函數調用關系暫存在一個臨時文件中,然后通過文件解析,拿到執行的函數名(如果是類方法,則是“類名::函數名”的形式),再通過reflection機制反推出定義這個函數的文件即可。再次體會到了世界上最美語言的強大之處。

           ?。?)插樁

            為了降低使用門檻,我們盡可能少地改變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的第一行加入了一句話:

            

            而phpcoverage.php核心代碼邏輯大致如下:

           

          ……

          function xdebugPhpcoverageBeforeShutdown(){

          ……

          $lineCovData = xdebug_get_code_coverage();

          xdebug_stop_code_coverage();

          ……

          xdebug_stop_trace();

          ……

          }

          register_shutdown_function(‘xdebugPhpcoverageBeforeShutdown’);

          ……

          xdebug_start_trace(……);

          xdebug_start_code_coverage();

           

            //備注:上面省略號表示非關鍵代碼,這里就不展示了


          ?
          尚腦教育隸屬于(北京尚腦互聯科技有限公司)    版權所有       京ICP備15029150號-2
          友情鏈接: 北京APP開發 | xp純凈版系統下載 | 廣州網站建設 | 廣州拓展訓練 | 數字圖書館系統 | 醫廢監管系統平臺 | 北京網站建設 | 騰訊企業郵箱 | 微信刷粉絲 | 信陽網站建設 | VR外包 | 展會互動 | 深圳網絡營銷 | 微信恢復 |
          梅县| 神农架| 宁强| 磁县| 永仁| 灌云| 延安| 丹阳| 梨树| 吉木萨尔| 会泽| 宜宾县| 拜城| 明光| 永川| 泰来| 剑阁| 田阳| 定安| 盘山| 拜泉| 怀宁| 宁陕| 河源| 襄阳| 万全| 许昌| 南雄| 沙河| 鸡公山| 沛县| 九寨沟| 胡尔勒| 繁峙| 通辽钱家店| 魏县| 八达岭| 蓬溪| 芜湖| 临县| 安化| 岐山| 东安| 平远| 丰都| 淖毛湖| 五道梁| 阿瓦提| 屯留| 和龙| 兴仁| 石棉| 夏河| 阳泉| 梁河| 巨野| 惠阳| 科尔沁左翼中旗| 台北县| 六合| 百色| 潮连岛| 古县| 聂拉木| 宁晋| 邹平| 拐子湖| 上思| 郯城| 斋堂| 铜陵| 林州| 察尔汉| 庆安| 绩溪| 索伦| 葫芦岛| 泸州| 扎鲁特旗| 永修| 镇康| 青神| 奉化| 剑河| 咸丰| 十堰| 五台山| 大田| 乐亭| 德庆| 景县| 仙桃| 福泉| 容城| 无锡| 十堰| 双峰| 饶河| 宁蒗| 平远| 鄱阳| 康乐| 新郑| 莘县| 九寨沟| 阳曲| 井冈山| 斗门| 石拐| 漳县| 嵊泗| 琼海| 乌斯太| 梧州| 集宁| 乌审召| 双城| 玉山| 巴南| 普格| 分宜| 南漳| 青冈| 惠农| 邗江| 改则| 马龙| 莱芜| 张家港| 西乌珠穆沁旗| 若羌| 聊城| 蒲城| 阿克苏| 石渠| 简阳| 隆回| 晋江| 尼木| 德宏| 兰州| 广汉| 阿巴嘎旗| 德庆| 馆陶| 盂县| 柳林| 永清| 北宁| 朱日和| 南安| 渭源| 镇坪| 易门| 红安| 武川| 高安| 铜川| 枝江| 博罗| 晋城| 合水| 黟县| 桃园| 伊金霍洛旗| 加查| 八宿| 宜城| 霸州| 南涧| 翁源| 新密| 延安| 平塘| 乌拉特中旗| 瑞金| 头道湖| 安平| 安乡| 兰西| 延寿| 即墨| 吉县| 淄博| 依安| 连南| 遵义| 额敏| 新乐| 汉源| 昭苏| 新宁| 满城| 灵宝| 阿尔山| 阜城| 商城| 宁南| 马尔康| 进贤| 远安| 郴州| 宁波| 宁化| 尖扎| 达拉特旗| 大连| 彭县| 福海| 达州| 河口| 罗源| 施甸| 邹平| 无锡| 崇仁| 玉屏| 东莞| 龙门| 塘头| 华家岭| 大兴| 昭觉| 康乐| 江津| 尤溪| 临漳| 赫章| 阿巴嘎旗| 武强| 燕尾港| 荆门| 金佛山| 毕节| 昌宁| 化德| 五原| 平山| 武川| 德保| 将乐| 库尔勒| 金沙| 定西| 依安| 海盐| 乌海| 镇平| 威远| 新城子| 辽阳| 奇台| 同德| 东台| 舒城| 洪家| 枝江| 阜康| 故城| 东莞| 常熟| 黎城| 高要| 裕民| 阳春| 景县| 平湖| 盈江| 海力素| 仁怀| 肇东| 希拉穆仁| 河南| 睢县| 新丰| 阜阳| 贡嘎| 离石| 内邱| 龙门| 海阳| 晋宁| 榆中| 佛山| 镶黄旗| 呼玛| 环县| 郎溪| 黄茅洲| 治多| 全椒| 红安| 平安| 博兴| 五台山| 涟水| 武功| 鲁甸| 保靖| 韩城| 仁寿| 宁阳| 白玉| 铜川| 盐都| 乌当| 阿木尔| 福清| 弥勒| 集安| 深州| 延边| 吴川| 祁东| 宜黄| 察布查尔| 清涧| 舍伯吐| 达拉特旗| 松江| 英山| 商都| 冷湖| 普格| 青龙山| 万源| 南沙岛| 乌兰乌苏| 黔西| 云龙| 黔西| 绍兴| 安定| 安国| 青川| 若羌| 靖西| 策勒| 炉山| 阿巴嘎旗| 柳江| 当雄| 宿迁| 都昌| 南川| 罗子沟| 鄂州| 吴川| 安县| 东乡| 寿宁| 帕里| 庆安| 长丰| 通辽钱家店| 尼勒克| 兴隆| 建宁| 肇州| 西峰| 六安| 资中| 临城| 莫索湾| 龙里| 金平| 崇明| 阳春| 简阳| 邳州| 巴音布鲁克| 邗江| 迁西| 温江| 渭源| 石屏| 蒲城| 汉川| 东兴| 曲周| 崇阳| 双峰| 东兰| 琼中| 桂平| 九龙| 阳江| 樟树| 吴忠| 牟平| 建平县| 漠河| 彭县| 子长| 小金| 壶关| 江浦| 衡东| 永福| 阿巴嘎旗