程式碼覆蓋率測試:從誤傳到現實

edithfang發表於2014-12-24
有一段時間,程式設計師獲得的報酬直接與他們編寫的程式碼行數掛鉤。他們被視為工作在小隔間裡的原始碼生產機器,這導致他們僅將程式設計視為一天八小時的工作,做完之後一段時間就將其遺忘。

但時代變了。大部分的小隔間工作場所消失了,程式設計師開始愛上他們的工作。隨著敏捷技術和軟體工藝運動的出現,許多對程式設計師和程式設計有用的新工具隨之出現。

TDD正逐漸成為程式碼編寫的實際方式,甚至向隔間世界最幽暗角落的程式設計師揭露了SCRUM和Kanban的祕密。

自動測試和測試驅動開發(TDD)是敏捷給我們程式設計師提供了一些關鍵技術。本文的主題是使用實現這些技術的工具產生測試程式碼覆蓋。

定義

“在電腦科學中,程式碼覆蓋是一種度量,用來描述程式原始碼經過特定測試套件測試的程度。”~維基百科

上述定義,摘自維基百科,是描述程式碼覆蓋含義的一種最簡單方式。從根本上說,你的工程中包含大量產品程式碼,也有很多測試程式碼。測試程式碼執行產品程式碼,測試覆蓋意味著你的產品程式碼有多少經過測試。

資訊可以通過各種方式呈現,從簡單的百分比到直觀的圖表,甚至在你最喜歡的整合開發環境中實時高亮顯示。

讓我們實際檢查一下

我們使用PHP語言來闡明程式碼。此外,我們需要PHPUnit和XDebug來進行程式碼測試和覆蓋資料收集。

原始碼

下面是我們使用的原始碼。你可以在附件文件中找到。

class WordWrap {

      public function wrap($string = '', $cols) {
            $string = trim($string);
            if (strlen($string) > $cols) {
                $lastSpaceIndex = strrpos(substr($string, 0, $cols), ' ');
                if ($lastSpaceIndex !== false && substr($string, $cols, 1) != ' ') {
                   return substr($string, 0, $lastSpaceIndex) . "n" . $this->wrap(substr($string, $lastSpaceIndex), $cols);
                } else {
                   return substr($string, 0, $cols) . "n" . $this->wrap(substr($string, $cols), $cols);
                }
             }

             return $string;
       }
}

上述程式碼包含了一個簡單函式,將文字封裝成每行指定數量字元。

測試程式碼

我們使用測試驅動開發(TDD)編寫了下面的程式碼,程式碼的覆蓋率為100%。這意味著執行測試,我們執行了每一行原始碼。

require_once __DIR__ . '/../WordWrap.php';

class WordWrapTest extends PHPUnit_Framework_TestCase {

    function testItCanWrap() {
        $w = new WordWrap();

        $this->assertEquals('', $w->wrap(null, 0));
        $this->assertEquals('', $w->wrap('', 0));
        $this->assertEquals('a', $w->wrap('a', 1));
        $this->assertEquals("anb", $w->wrap('a b', 1));
        $this->assertEquals("a bnc", $w->wrap('a b c', 3));
        $this->assertEquals("anbcnd", $w->wrap('a bc d', 3));
    }
}

在命令列介面(CLI)中使用文字覆蓋執行測試

獲取覆蓋資料的一種方法是在CLI(命令列介面)中執行測試,分析輸出。對於這個例子,假設我們使用的是類UNIX系統(Linux,MacOS、FreeBSD等)。Windows使用者需要稍微調整路徑和可執行檔案的名稱,但應該相當類似。

讓我們開啟控制檯程式,切換到測試資料夾目錄下。然後,選擇生成純文字覆蓋資料選項,執行phpunit程式。

phpunit --coverage-text=./coverage.txt ./WordWrapTest.php

大部分安裝了XDebug系統應該能工作,但在某些情況下,可能會遇到與時區相關的錯誤。

PHP Warning: date(): It is not safe to rely on the system's timezone settings.
You are *required* to use the date.timezone setting or the date_default_timezone_set() function.
In case you used any of those methods and you are still getting this warning, you most likely
misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set
date.timezone to select your timezone. in phar:///usr/share/php/phpunit/phpunit.phar/
PHP_CodeCoverage-1.2.10/PHP/CodeCoverage/Report/Text.php on line 124

在php.ini檔案中按要求設定很容易解決這個問題。你可以在這張列表中找到你應該指定的時區。我來自羅馬尼亞,所以我使用以下設定:

date.timezone = Europe/Bucharest

現在,如果你再一次執行phpunit命令,你應該不會看到錯誤訊息。取而代之的是顯示測試結果。

PHPUnit 3.7.20 by Sebastian Bergmann.
..
Time: 0 seconds, Memory: 5.00Mb
OK (2 tests, 7 assertions)

覆蓋資料將輸出到指定文字檔案中。

$ cat ./coverage.txt

Code Coverage Report
2014-03-02 13:48:11

Summary:
Classes: 100.00% (1/1)
Methods: 100.00% (1/1)
Lines: 2.68% (14/522)

WordWrap
Methods: 100.00% ( 1/ 1) Lines: 100.00% ( 7/ 7)

讓我們一點點分析。

  • 類:指多少類經過測試,有多少被覆蓋。WordWrap是我們唯一的類。
  • 方法:和類一樣。我們只有wrap()函式,沒有其他函式。
  • 行:和上面相同,但是指程式碼行數。這裡我們有很多行,因為概要部分包含PHPUnit自身的所有行。
  • 然後是每個類都有一個部分。在我們的例子中,只有唯一的WordWrap。每一部分都包含自身的函式和行數細節。

基於這些觀察,我們可以得出結論,程式碼100%通過測試覆蓋。和我們之前預期分析的覆蓋率資料吻合。

生成HTML覆蓋輸出

只要改變PHPUnit的一個簡單引數,可以生成工整的HTML輸出。

$ mkdir ./coverage
$ phpunit --coverage-html ./coverage ./WordWrapTest.php

如果你檢視coverage目錄,你會發現裡面有很多檔案。因為檔案太多,這裡沒有貼出檔案列表。取而代之,我將在web瀏覽器中給你展示。


這和上述文字版本概要部分等同。我們可以通過連結放大並檢視細節。


在IDE內覆蓋

如果你的程式碼只能通過SHH或者網路訪問遠端伺服器並在上面編譯,前面的例子很有趣,也非常有用。但是,如果所有這些資訊都在你的整合開發環境中是不是更棒?

如果你使用PHPStorm,一切都會在單擊的過程中完成!選擇執行覆蓋測試,所有資訊神奇般地立刻呈現。


覆蓋資訊將在你的整合開發環境的許多地方以不同的方式呈現:



  • 測試覆蓋百分比會在每一個目錄和檔案周圍顯示。
  • 在編輯器中編寫程式碼時,行號的左邊的綠色或紅色方塊會標記每一行。綠色代表經過測試的行,紅色代表未經測試的行。無實際程式碼的行(空行,只有花括號或圓括號,類或函式宣告)不會有任何標記。
  • 右邊欄是檔案瀏覽器,你可以按覆蓋率快速瀏覽和排序檔案。
  • 在測試輸出欄中,你會看到一行文字告訴你程式碼覆蓋率已產生。
程式碼覆蓋的虛構

這個強大的工具同時到達程式設計師和管理者手中,不可避免會出現一些誤傳。在程式設計師拒絕按照編寫的程式碼行數獲得酬勞或者管理者意識到實現一個系統非常簡單之後,一些人開始按照程式碼覆蓋率給程式設計師發放酬勞。程式碼覆蓋越高意味著程式設計師越小心,對嗎?這是一個誤傳。程式碼覆蓋不是編寫程式碼質量的度量。

有時程式設計師傾向於認為100%覆蓋的程式碼沒有bug。這是另一個誤傳。程式碼覆蓋僅僅是告訴你你已經測試過每一行程式碼。程式碼覆蓋是已執行程式碼行數的度量。它不是正確實現程式碼行數的度量。例如,寫了一半的演算法,使用定義一半的測試將會得到100%覆蓋率。但這並不意味著該演算法已完成或是能正常工作。

最後,實現系統非常容易。當然,如果你使用TDD,你自然會有很高的程式碼覆蓋值。對整個工程而言,100%覆蓋是不可能的。但是對於小的模組或類,獲取100%的覆蓋非常容易。就拿我們的原始碼來說,假設你沒有進行任何測試。執行所有程式碼的最簡單測試是什麼?

function testItCanWrap() {
    $w = new WordWrap();
    $this->assertEquals("a bnc", $w->wrap('a b c', 3));
    $this->assertEquals("anbcnd", $w->wrap('a bc d', 3));
}

就是這樣。兩句斷言就可以實現完全覆蓋。這不是我們想要的。這樣的測試偏離描述性和完整性,太荒謬了。

程式碼覆蓋的實現

程式碼覆蓋是一種狀態指示器,而不是衡量效能或正確性的單元。

程式碼覆蓋率是給程式設計師參考的,而不是給管理者參考的。它是我們發現程式碼中問題的方法。該方法可以發現過時的,未測試的類。還可以發現未經測試執行可能導致問題的路徑。

在實際專案中,程式碼覆蓋率總是低於100%。取得完全覆蓋是不可能的,如果取得,那也是非常罕見的。然而,為了獲取98%的覆蓋率,你必須將目標定在100%。其它任何目標都沒有意義。

下面是Syneto的StorageOS配置程式的程式碼覆蓋率。



整體覆蓋率大約只有35%,但結果需要說明一下。大部分模組都處於綠色狀態,超過70%的覆蓋率。然而只有一個資料夾,Vmware,降低了整體平均值。這模組中有很多的類,類中只定義了通訊API。那些類沒有測試的必要。它們是由可靠程式碼自動生成。程式設計師知道這些程式碼,他們也知道如何解釋結果。管理者可能會堅持測試,因為程式碼顯示為紅色狀態,這對於不瞭解程式內部細節的人來說很可疑。測試這部分程式碼有意義嗎?一點也沒有!這將是一個無用的測試,除了佔用幾十秒寶貴的編譯時間沒有任何的優勢。

結語

這就是我們所說的程式碼覆蓋:對程式設計師來說,它是一個很棒的工具,可以高亮顯示可能出現問題的資訊源,對大部分管理者來說說卻是被曲解的事實,成為限制和度量程式設計師行為的工具。正如使用的其他工具一樣,程式碼覆蓋正確使用很簡單,但也容易誤用。
來自:程式師
相關閱讀
評論(1)

相關文章