On Github RickyChien / research-slide-tcse
Created by Ricky Chien
For better software quality, we need to write tests
Coverage tool can give us a statistics report after testing
Blanket.js - JavaScript test coverage tool
Nowadays, web is going to become more complicated
Browser can take more jobs than server
Before
After
首先,code coverage統計程式碼覆蓋率的背後原理主要是透過source instrumentation,我們簡稱instrumentation。 instrumentation是一種在不影響原程式行為的狀況下對原始碼進行增修,然後達成記錄程式走訪行為的目的。 直接看範例圖,原本的程式碼如上圖,經過instrumentation之後,下圖的程式碼新增了額外的變數,用來記錄原始碼的走訪過程。Browser instrumentation / Server instrumentation
接下來讓我們分析一下在Web上的code coverage實作方式,主要可以分為兩種方式 - Browser instrumentation 與 Server instrumentation。Sometimes we need to interact with a real server
看起來server instrumentation似乎很完美,但有時測試環境情況會更加複雜。 例如:有時候我們會與後端的web server做互動,需要web server回傳的真實的資料來做測試。 這時後,單純為了code coverage而多加入了一個instrumentation server會使得整合發生困難。 不過若使用Browser instrumentation的方式時可以避開這種問題,而且在安裝與設定上, Browser instrumentation的方式相較起來也會比較容易。In browser instrumentation
剛剛我們已經說明了在Browser instrumentation方式上會有無法覆蓋動態載入原始碼的問題。 本研究將這個問題稱之為Zero Coverage。 然後接下來用兩個實際的範例來展示一下什麼是Zero coverage。Firefox OS email app should be covered 21 modules
Make it possible to cover dynamic lazy loading scripts
那本研究目的就是在於改進使用browser insturmentation,修正zero coverage問題。 在JavaScript coverage tools中,較著名且是使用browser insturmentation的就是blanket。 所以我們想要直接改進blanket,使得dynamic lazy loading可以被偵測。Analyze web loading approachs
首先我們先來分析web上各種載入方式,找出方法來偵測動態載入的code coverage。<script src="path-to/script.js"></script>HTML script方法並非動態載入。 一般JavaScript載入方式都會透過這種在HTML加入script tag的方式來載入原始碼,
var xhr = new XMLHttpRequest(); xhr.open('GET', 'path-to/script.js'); // Assign script url xhr.onload = function (script) { eval(script); // Execute script }; xhr.send();接下來的方式皆為動態載入方法。 首先,XHR是browser提供的Web API可以讓你透過JavaScript來下載原始碼,他也是Ajax技術的核心。 透過XMLHttpRequest物件並且指定要下載的路徑,接下來就能在取得原始碼後執行原始碼,達到動態載入。
document.write('<script src="path-to/script.js"></script>');document.write一樣是browser提供的API,它能夠重新改寫原始的HTML, 像是圖中重新寫入script tag來動態下載原始碼。 不過此種方式有performance與security問題,目前較不推薦使用。
appendChild / insertBefore / replaceChild
var script = document.createElement("script"); script.src = url; // Assign script url document.head.appendChild(script); parentNode.insertBefore(script, node); parentNode.replaceChild(script, oldNode);這邊所說的DOM modification API一樣是Web API。 主要有appendChild, insertBefore, replaceChild這三種能對於DOM做新增元素的方法。 我們可以動態的新建一個script元素,然後指定其source路徑, 最後使用DOM modification API新增到HTML上面來達成下載。
Famous module loader library such as RequireJS using syntax :
require(["path-to/script.js"], function() { // This function is called after path-to/script.js has loaded. });JavaScript有許多不同的module loader libraries。像是requireJS就是滿著名的library。 這種library會使用固定形式的API來做動態載入,像圖中會定義一個require function call來載入原始碼。 我們將這種方式稱為function wrapping。
Overwrite native appendChild / insertBefore / replaceChild
var originalAppendChild = Element.prototype.appendChild; Element.prototype.appendChild = function(newElement) { // Do our hack here return originalAppendChild.apply(this, args); // invoke native method };就Overwrite DOM modification API裏面的appendChild為例子來說。 首先,我們必須先了解Browser Web API的prototype繼承結構, 然後找到了Element.prototype.appendChild這個function來進行overwrite。 JavaScript可以透過像C++中的function pointer形式將function存在變數中, 我們透過這個方法先將Element.prototype.appendChild暫存起來,然後直接overwrite成我們的邏輯。 在我們的邏輯中就會將下載來的script進行source instrumentation,依照情況invoke原始appendChild function call。 這種overwirte native API在開發中很罕見,所以能安全的並不影響原始程式行為需要做些驗證。
Overwrite native open method in XHR object
var originalXHROpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url) { // Do our hack here return originalXHROpen.apply(this, args); // invoke native method };那XHR API的overwrite方式也如同上面的方式,所以這邊就不再多做解釋了。
We demonstrated the zero coverage issue
Analyzed source instrumentation mechanism and dynamic lazy loading schemes
Proposed a solution to overwrite native Web APIs to detect dynamic lazy loading
Solution was succssful and safe that even integrated into FFOS testing framework
New feature has proposed to Blanket