pada tahun 2003 . Untuk pertama kalinya dalam ekosistem PHP , data cakupan kode dapat dikumpulkan. Pada tahun 2004, Sebastian Bergmann merilis PHPUnit 2 , tempat ia pertama kali menggunakannya. Pengembang sekarang memiliki kemampuan untuk mengukur kinerja rangkaian pengujian mereka menggunakan laporan cakupan. Sejak itu, fungsinya telah dipindahkan ke komponen cakupan kode-php generik dan independen . PHPDBG dan PCOV telah muncul sebagai driver alternatif . Namun pada dasarnya, proses inti untuk pengembang tidak berubah selama 16 tahun terakhir.
Pada bulan Agustus 2020, dengan dirilisnya php-code-coverage 9.0 dan rilis terkait PHPUnit 9.3 dan behat-code-coverage 5.0 , cara baru untuk memperkirakan cakupan menjadi tersedia.
Hari ini kami akan mempertimbangkan
- Tur singkat tentang dasar-dasarnya
- Batasan
- Metrik alternatif
- Cakupan cabang
- Meliputi jalur
- Termasuk metrik baru
- Metrik mana yang akan digunakan?
- Apakah ada alasan untuk tidak menyertakan metrik baru?
- Hasil
Tur singkat tentang dasar-dasarnya
Sebagian besar pengembang PHP akrab dengan gagasan pengujian kode otomatis. Ide cakupan kode terkait erat dengan pengujian otomatis dan tentang mengukur persentase kode yang telah dijalankan atau, seperti yang mereka katakan, "dicakup" oleh pengujian. Misalnya, jika Anda memiliki kode berikut:
<?php
class PercentCalculator
{
public function __construct(int $numerator, int $denominator)
{
$this->numerator = $numerator;
$this->denominator = $denominator;
}
public function calculatePercent(): float
{
return round($this->numerator / $this->denominator * 100, 1);
}
}
Kemudian kita dapat menulis tes PHPUnit seperti yang ditunjukkan di bawah ini:
<?php
class PercentCalculatorTest extends PHPUnit\Framework\TestCase
{
public function testTwentyIntoForty(): void
{
$calculator = new PercentCalculator(20, 40);
self::assertEquals(50.0, $calculator->calculatePercent());
}
}
Setelah menjalankan pengujian, PHPUnit mengonfirmasi bahwa kami telah mencapai cakupan 100% dalam contoh sepele ini:
Batasan
Namun, dalam contoh di atas, ada potensi kesalahan kecil. Jika $ penyebut adalah 0 , maka kita mendapatkan kesalahan pembagian dengan nol. Mari kita perbaiki dan lihat apa yang terjadi:
<?php
class PercentCalculator
{
public function __construct(int $numerator, int $denominator)
{
$this->numerator = $numerator;
$this->denominator = $denominator;
}
public function calculatePercent(): float
{
// ,
//
//
return $this->denominator ? round($this->numerator / $this->denominator * 100, 1) : 0.0;
}
}
Meskipun baris 12 sekarang menggunakan pernyataan terner if / else (dan kami bahkan belum menulis tes untuk memverifikasi bahwa penanganan null kami benar), laporan tersebut memberi tahu kami bahwa kami masih memiliki cakupan kode 100%.
Jika sebagian dari garis tersebut dicakup oleh pengujian, maka seluruh garis tersebut ditandai sebagai tercakup . Ini bisa menyesatkan!
Dengan hanya menghitung apakah suatu baris dijalankan atau tidak, konstruksi kode lain sering kali memiliki masalah yang sama, misalnya:
if ($a || $b || $c) { // **
doSomething(); // 100%
}
public function pluralise(string $thing, int $count): string
{
$string = $count . ' ' . $thing;
if ($count > 1) { // $count >= 2, - 100%
$string .= 's'; // $count === 1,
} // ,
return $string;
}
Metrik alternatif
Dimulai dengan versi 2.3, Xdebug mampu mengumpulkan tidak hanya metrik baris demi baris yang sudah dikenal, tetapi juga metrik cabang dan cakupan jalur alternatif. Posting blog Derik yang berbicara tentang fitur ini diakhiri dengan pernyataan terkenal:
βTetap menunggu sampai Sebastian (atau orang lain) punya waktu untuk memperbarui PHP_CodeCoverage untuk menunjukkan cakupan cabang dan jalur. Selamat meretas!
Derik Retans, Januari 2015 "
Setelah 5 tahun menunggu "orang lain" yang misterius ini, saya memutuskan untuk mencoba menerapkan semuanya sendiri. Terima kasih banyak kepada Sebastian Bergman karena telah menerima permintaan penarikan saya .
Cakupan cabang
Di semua kecuali kode yang paling sederhana, ada tempat di mana jalur eksekusi bisa menyimpang menjadi dua atau lebih jalur. Ini terjadi di setiap titik keputusan, seperti setiap jika / lain atau sementara . Setiap "sisi" dari titik divergensi ini adalah cabang yang terpisah. Jika tidak ada titik keputusan, aliran hanya berisi satu cabang.
Perhatikan bahwa meskipun metafora pohon digunakan, cabang dalam konteks ini tidak sama dengan cabang kontrol versi, jangan membingungkan keduanya!
Ketika cakupan cabang dan jalur diaktifkan, laporan HTML dibuat dengan cakupan kode-php, di samping laporan cakupan jalur reguler, termasuk add-on untuk menampilkan cakupan cabang dan jalur. Seperti inilah tampilan cakupan cabang menggunakan contoh kode yang sama seperti sebelumnya:
Seperti yang Anda lihat, kotak pivot di bagian atas halaman segera menunjukkan bahwa meskipun kami memiliki cakupan baris demi baris, ini tidak berlaku untuk cakupan cabang dan jalur ( jalur dibahas secara rinci di bagian selanjutnya).
Selain itu, garis 12 disorot dengan warna kuning untuk menunjukkan bahwa cakupannya tidak lengkap (garis dengan cakupan 0% akan ditampilkan dalam warna merah seperti biasa).
Akhirnya, lebih banyak perhatian mungkin memperhatikan bahwa, tidak seperti liputan baris demi baris, lebih banyak garis yang disorot dalam warna. Ini karena cabang dihitung berdasarkan aliran eksekusi di dalam interpreter PHP. Cabang pertama dari setiap fungsi dimulai saat fungsi itu dimasukkan. Ini berbeda dengan cakupan berbasis string, di mana hanya badan fungsi yang dianggap berisi string yang dapat dieksekusi, dan deklarasi fungsi itu sendiri dianggap tidak dapat dieksekusi.
Menemukan cabang
Perbedaan antara apa yang dianggap juru bahasa PHP sebagai cabang kode yang terpisah secara logis dan model mental pengembang dapat membuat metrik sulit untuk dipahami. Misalnya, jika Anda bertanya kepada saya berapa banyak cabang di calcPercent () , saya akan menjawab 2 (kasus khusus untuk 0 dan kasus umum). Namun, melihat laporan cakupan kode-php di atas, fungsi satu baris ini sebenarnya berisi ... 4 cabang ?!
Untuk memahami apa yang dimaksud dengan interpreter PHP , ada laporan cakupan tambahan di bagian hulu. Ini menunjukkan versi tambahan dari tampilan setiap cabang, yang membantu mengidentifikasi secara lebih efisien apa yang tersembunyi dalam kode sumber. Ini terlihat seperti ini:
Judulnya berbunyi: "Di bawah ini adalah baris sumber yang mewakili setiap cabang kode yang ditemukan Xdebug . Perhatikan bahwa cabang tidak harus sama dengan string: string dapat berisi banyak cabang dan karenanya muncul lebih dari satu kali. Juga perlu diingat bahwa beberapa cabang dapat tersirat, misalnya, pernyataan if selalu memiliki yang lain dalam alur logis, bahkan jika Anda tidak menulisnya. "
Semua ini belum begitu jelas, tetapi Anda sudah dapat memahami cabang apa yang ada di dalam countPercent () :
- Cabang 1 dimulai pada entri fungsi dan menyertakan cek penyebut $ this->;
- Eksekusi kemudian dipecah menjadi cabang 2 dan 3 tergantung pada apakah kasus khusus ditangani atau tidak;
- Cabang 4 adalah tempat penggabungan cabang 2 dan 3 yang terdiri dari fungsi kembali dan keluar.
Mencocokkan cabang secara mental ke bagian individu dari kode sumber adalah keterampilan baru yang membutuhkan sedikit latihan. Tetapi melakukannya dengan kode yang mudah dibaca dan dimengerti pasti lebih mudah. Jika kode Anda penuh dengan satu baris cerdas yang menggabungkan beberapa bagian logika, seperti dalam contoh kami, maka perkirakan lebih banyak kerumitan dibandingkan dengan kode yang semuanya terstruktur dan ditulis dalam beberapa baris, yang sepenuhnya sesuai dengan cabangnya. Logika yang sama yang ditulis dengan gaya ini akan terlihat seperti ini:
Semanggi
Jika Anda mengekspor laporan cakupan kode-php dalam format Clover untuk mentransfernya ke sistem lain, maka dengan cakupan berbasis cabang diaktifkan, data akan ditulis ke kunci bersyarat dan syarat tercakup . Sebelumnya (atau jika cakupan cabang tidak diaktifkan) nilai yang diekspor selalu nol.
Meliputi jalur
Jalur adalah kombinasi cabang yang mungkin. Contoh countPercent () memiliki dua kemungkinan jalur, seperti yang ditunjukkan di atas:
- Cabang 1, lalu Cabang 2, lalu Cabang 4;
- Cabang 1, lalu cabang 3, lalu cabang 4.
Namun, seringkali jumlah jalur lebih besar daripada jumlah cabang, misalnya, dalam kode yang berisi banyak kondisional dan loop. Contoh berikut, diambil dari php-code-coverage , memiliki 23 cabang, tetapi sebenarnya ada 65 jalur berbeda untuk fungsi tersebut:
final class File extends AbstractNode
{
public function numberOfTestedMethods(): int
{
if ($this->numTestedMethods === null) {
$this->numTestedMethods = 0;
foreach ($this->classes as $class) {
foreach ($class['methods'] as $method) {
if ($method['executableLines'] > 0 &&
$method['coverage'] === 100) {
$this->numTestedMethods++;
}
}
}
foreach ($this->traits as $trait) {
foreach ($trait['methods'] as $method) {
if ($method['executableLines'] > 0 &&
$method['coverage'] === 100) {
$this->numTestedMethods++;
}
}
}
}
return $this->numTestedMethods;
}
}
Jika Anda tidak dapat menemukan 23 cabang, ingat bahwa foreach dapat menerima iterator kosong, dan jika selalu ada cabang lain yang tidak terlihat .
Ya, itu berarti 65 tes diperlukan untuk cakupan 100%. Laporan
HTML cakupan kode php , seperti cabang, menyertakan tampilan tambahan untuk setiap jalur. Ini menunjukkan mana yang tertutup adonan dan mana yang tidak.
SAMPAH
Mengaktifkan cakupan jalur lebih jauh mempengaruhi metrik yang ditampilkan, yaitu skor CRAP . Definisi yang diposting di crap4j.org menggunakan metrik cakupan jalur persentase yang tidak tersedia secara historis dalam PHP sebagai masukan untuk penghitungan . Padahal di PHP , cakupan baris demi baris selalu digunakan. Untuk fitur kecil dengan cakupan yang baik, skor CRAP kemungkinan besar akan tetap sama atau bahkan menurun. Tetapi untuk fungsi dengan banyak jalur eksekusi dan cakupan yang buruk, nilainya akan meningkat secara signifikan.
Termasuk metrik baru
Cakupan cabang dan jalur diaktifkan atau dinonaktifkan bersama-sama, karena keduanya merupakan representasi yang berbeda dari data eksekusi kode dasar yang sama.
PHPUnit
Untuk PHPUnit 9.3+, metrik tambahan dinonaktifkan secara default dan dapat diaktifkan melalui baris perintah atau melalui file konfigurasi phpunit.xml , tetapi hanya jika dijalankan di bawah Xdebug . Mencoba mengaktifkan fitur ini saat menggunakan PCOV atau PHPDBG akan mengakibatkan peringatan ketidakcocokan konfigurasi dan cakupan tidak akan dikumpulkan.
- Di konsol, gunakan opsi --path-coverage : vendor / bin / phpunit - path-coverage .
- Di phpunit.xml, setel atribut pathCoverage elemen cakupan ke true .
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<testsuites>
<testsuite name="default">
<directory>tests</directory>
</testsuite>
</testsuites>
<coverage pathCoverage="true" processUncoveredFiles="true" cacheDirectory="build/phpunit/cache">
<include>
<directory suffix=".php">src</directory>
</include>
<report>
<text outputFile="php://stdout"/>
<html outputDirectory="build/coverage"/>
</report>
</coverage>
</phpunit>
Di PHPUnit 9.3, format file konfigurasi telah diubah secara serius , jadi struktur di atas mungkin terlihat berbeda dari biasanya.
behat-code-coverage
Untuk behat-code-cover 5.0+, pengaturan dilakukan di behat.yml , atributnya disebut branchAndPathCoverage . Jika Anda mencoba mengaktifkannya dengan driver selain Xdebug , peringatan akan dikeluarkan, tetapi cakupan akan tetap dibuat. Ini untuk mempermudah penggunaan file konfigurasi yang sama di lingkungan yang berbeda. Jika tidak dikonfigurasi secara eksplisit, cakupan baru akan diaktifkan secara default saat berjalan di bawah Xdebug .
Metrik mana yang akan digunakan?
Secara pribadi, saya ( Doug Wright ) akan menggunakan metrik baru jika memungkinkan. Saya mengujinya pada berbagai kode untuk melihat apa yang "normal". Pada proyek saya, kemungkinan besar, saya akan menggunakan pendekatan hybrid, yang akan saya tunjukkan di bawah. Untuk proyek komersial, keputusan untuk beralih ke metrik baru, jelas, harus dibuat oleh seluruh tim, dan saya menantikan kesempatan untuk membandingkan temuan mereka dengan temuan saya.
Pendapat saya
Tidak diragukan lagi, cakupan jalur 100% adalah cawan suci, dan jika masuk akal untuk menerapkannya, itu adalah metrik yang baik untuk diperjuangkan bahkan jika Anda tidak melakukannya. Jika Anda menulis tes, Anda masih harus memikirkan hal-hal seperti kasus tepi. Cakupan berbasis jalur membantu Anda memastikan tidak apa-apa.
Namun, jika suatu metode berisi puluhan, ratusan, atau bahkan ribuan jalur (yang sebenarnya tidak jarang untuk hal-hal yang cukup rumit), saya tidak akan membuang waktu menulis ratusan pengujian. Bijaksana untuk berhenti pada pukul sepuluh. Pengujian bukanlah tujuan itu sendiri, tetapi alat mitigasi risiko dan investasi di masa depan. Tes harus membuahkan hasil, dan waktu yang dihabiskan untuk itutes tidak mungkin membuahkan hasil. Dalam situasi seperti ini, yang terbaik adalah menargetkan cakupan cabang yang baik, karena setidaknya memastikan bahwa Anda memikirkan apa yang terjadi di setiap titik keputusan.
Dalam kasus sejumlah besar jalur (mereka sekarang didefinisikan dengan baik dengan CRAP jujur), saya mengevaluasi jika kode yang dipermasalahkan tidak melakukan terlalu banyak, dan adakah cara yang masuk akal untuk memecahnya menjadi fungsi yang lebih kecil (yang sudah dapat diuraikan lebih detail)? Terkadang tidak, dan tidak apa-apa - kita tidak perlu benar-benar menghilangkan semua risiko proyek. Bahkan mengetahui tentang mereka itu indah. Penting juga untuk diingat bahwa batas fungsi dan pengujian unit terisolasinya adalah pemisahan logika buatan, bukan kompleksitas sebenarnya dari perangkat lunak Anda secara keseluruhan. Oleh karena itu, saya akan merekomendasikan untuk tidak merusak fungsi besar hanya karena jumlah jalur eksekusi yang menakutkan. Lakukan ini hanya jika pemisahan mengurangi beban kognitif dan membantu persepsi kode.
Apakah ada alasan untuk tidak menyertakan metrik baru?
Ya, kinerja. Bukan rahasia lagi bahwa kode Xdebug sangat lambat dibandingkan dengan kinerja PHP normal . Dan jika Anda mengaktifkan cakupan cabang dan jalur, maka semuanya diperparah dengan penambahan overhead untuk semua data eksekusi tambahan yang sekarang perlu dilacaknya.
Kabar baiknya adalah bahwa harus mengatasi masalah ini telah menginspirasi pengembang untuk membuat peningkatan kinerja umum dalam cakupan kode-php yang akan menguntungkan siapa pun yang menggunakan Xdebug . Kinerja rangkaian pengujian sangat bervariasi, sehingga sulit untuk menilai bagaimana hal ini akan memengaruhi setiap rangkaian pengujian, tetapi mengumpulkan cakupan berbasis string akan lebih cepat.
Masih sekitar 3-5 kali lebih lambat untuk membuat cakupan dari cabang dan jalur. Ini harus diperhitungkan. Pertimbangkan untuk mengaktifkan file pengujian individual secara selektif daripada keseluruhan paket pengujian, atau build malam dengan "cakupan yang lebih baik" daripada menjalankan setiap push.
Xdebug 3 akan jauh lebih cepat daripada versi saat ini karena pekerjaan yang dilakukan pada modularisasi dan kinerja, jadi peringatan ini harus dilihat sebagai khusus untuk Xdebug 2 saja . Dengan versi 3, bahkan mempertimbangkan overhead pengumpulan data tambahan, dimungkinkan untuk menghasilkan cakupan berbasis cabang dan berbasis jalur dalam waktu yang lebih singkat daripada yang dibutuhkan saat ini untuk mendapatkan cakupan baris demi baris!
Pengujian dilakukan oleh Sebastian Bergmann, grafik diplot oleh Derick Rethans
Hasil
Silakan uji fitur baru dan kirimkan surat kepada kami. Apakah mereka membantu? Ide untuk visualisasi alternatif (mungkin dari bahasa lain) sangat menarik.
Saya selalu tertarik dengan pendapat Anda tentang tingkat cakupan kode yang normal.
Di PHP Rusia pada tanggal 29 November, kami akan membahas semua pertanyaan paling penting tentang pengembangan PHP, tentang apa yang tidak ada dalam dokumentasi, tetapi apa yang akan memberi kode Anda level baru.
Bergabunglah dengan kami di konferensi: tidak hanya untuk mendengarkan laporan dan mengajukan pertanyaan kepada pembicara terbaik di jagat PHP, tetapi juga untuk komunikasi profesional (akhirnya offline!) Dalam suasana yang hangat. Komunitas kami: Telegram , Facebook , VKontakte , YouTube .