Apakah masuk akal untuk mengukur cakupan bersyarat untuk kode Java 8?

19

Saya bertanya-tanya apakah mengukur cakupan kode bersyarat oleh alat saat ini untuk Java tidak usang sejak Java 8 muncul. Dengan Java 8 Optionaldan Streamkita sering dapat menghindari kode cabang / loop, yang membuatnya mudah untuk mendapatkan cakupan bersyarat sangat tinggi tanpa menguji semua jalur eksekusi yang mungkin. Mari kita bandingkan kode Java lama dengan kode Java 8:

Sebelum Java 8:

public String getName(User user) {
    if (user != null) {
        if (user.getName() != null) {
            return user.getName();
        }
    }
    return "unknown";
}

Ada 3 jalur eksekusi yang mungkin dalam metode di atas. Untuk mendapatkan 100% cakupan bersyarat, kita perlu membuat 3 unit tes.

Java 8:

public String getName(User user) {
    return Optional.ofNullable(user)
                   .map(User::getName)
                   .orElse("unknown");
}

Dalam hal ini, cabang disembunyikan dan kami hanya perlu 1 tes untuk mendapatkan cakupan 100% dan tidak masalah yang akan kami uji. Meskipun masih ada 3 cabang logis yang sama yang harus dibahas saya percaya. Saya pikir itu membuat statistik cakupan bersyarat sepenuhnya tidak dipercaya hari ini.

Apakah masuk akal untuk mengukur cakupan bersyarat untuk kode Java 8? Apakah ada alat lain yang melihat kode yang dimasukkan?

Karol Lewandowski
sumber
5
Metrik cakupan tidak pernah menjadi cara yang baik untuk menentukan apakah kode Anda diuji dengan baik, hanya cara untuk menentukan apa yang belum diuji. Pengembang yang baik akan memikirkan berbagai kasus dalam benaknya, dan menyusun tes untuk semuanya - atau setidaknya semua yang menurutnya penting.
kdgregory
3
Tentu saja cakupan bersyarat yang tinggi tidak berarti bahwa kami memiliki tes yang baik, tetapi saya pikir itu adalah keuntungan BESAR untuk mengetahui jalur eksekusi mana yang terbuka dan inilah pertanyaan yang paling banyak ditanyakan. Tanpa cakupan bersyarat, jauh lebih sulit untuk menemukan skenario yang belum diuji. Mengenai jalur: [pengguna: null], [pengguna: notnull, user.name:null], [pengguna: notnull, user.name:notnull]. Apa yang saya lewatkan?
Karol Lewandowski
6
Apa kontraknya getName? Tampaknya bahwa jika usernol, itu harus mengembalikan "tidak dikenal". Jika userbukan nol dan user.getName()nol, itu harus mengembalikan "tidak dikenal". Jika userbukan nol dan user.getName()bukan nol, itu harus mengembalikannya. Jadi, Anda akan menguji tiga kasus karena itulah kontraknya getName. Anda tampaknya melakukannya mundur. Anda tidak ingin melihat cabang dan menulis tes sesuai dengan itu, Anda ingin menulis tes Anda sesuai dengan kontrak Anda, dan memastikan kontrak terpenuhi. Saat itulah Anda memiliki cakupan yang baik.
Vincent Savard
1
Sekali lagi, saya tidak mengatakan bahwa cakupan membuktikan kode saya diuji dengan sempurna, tapi itu alat yang sangat berharga yang menunjukkan kepada saya apa yang belum saya uji. Saya pikir kontrak pengujian tidak dapat dipisahkan dari pengujian jalur eksekusi (contoh Anda luar biasa karena melibatkan mekanisme bahasa implisit). Jika Anda belum menguji jalur, maka Anda belum sepenuhnya menguji kontrak atau kontrak tidak sepenuhnya ditentukan.
Karol Lewandowski
2
Saya akan mengulangi poin saya sebelumnya: itu yang selalu terjadi, kecuali Anda membatasi diri hanya untuk fitur bahasa dasar dan tidak pernah memanggil fungsi apa pun yang belum diinstrumentasi. Yang berarti tidak ada perpustakaan pihak ketiga, dan tidak ada penggunaan SDK.
kdgregory

Jawaban:

4

Apakah ada alat yang mengukur cabang logis yang bisa dibuat di Java 8?

Saya tidak tahu. Saya mencoba menjalankan kode yang Anda miliki melalui JaCoCo (alias EclEmma) hanya untuk memastikan, tetapi itu menunjukkan 0 cabang dalam Optionalversi. Saya tidak tahu metode konfigurasi untuk mengatakan sebaliknya. Jika Anda mengonfigurasinya untuk juga menyertakan file JDK, secara teoritis akan menampilkan cabang di Optional, tapi saya pikir akan konyol untuk mulai memverifikasi kode JDK. Anda hanya harus menganggap itu benar.

Saya pikir masalah intinya adalah menyadari bahwa cabang tambahan yang Anda miliki sebelum Java 8 adalah, dalam arti tertentu, cabang yang dibuat secara artifisial. Bahwa mereka tidak lagi ada di Java 8 berarti Anda sekarang memiliki alat yang tepat untuk pekerjaan itu (dalam hal ini, Optional). Dalam kode pra-Jawa 8 Anda harus menulis tes unit tambahan sehingga Anda dapat memiliki keyakinan bahwa setiap cabang kode berperilaku dengan cara yang dapat diterima - dan ini menjadi sedikit lebih penting di bagian kode yang tidak sepele seperti User/ getNamecontoh.

Dalam kode Java 8, Anda malah menaruh kepercayaan pada JDK bahwa kode tersebut berfungsi dengan baik. Seperti, Anda harus memperlakukan Optionalbaris itu sama seperti alat cakupan kode memperlakukannya: 3 baris dengan 0 cabang. Bahwa ada baris dan cabang lain dalam kode di bawah ini adalah sesuatu yang belum Anda perhatikan sebelumnya, tetapi telah ada setiap kali Anda menggunakan sesuatu seperti ArrayListatau HashMap.

Shaz
sumber
2
"Bahwa mereka tidak lagi ada di Jawa 8 ..." - saya tidak setuju dengan hal itu, Java 8 adalah kompatibel dan ifdan nullmasih bagian dari bahasa ;-) Ini masih mungkin untuk menulis kode dengan cara lama dan untuk lulus nullpengguna atau pengguna dengan nullnama. Tes Anda harus membuktikan bahwa kontrak terpenuhi terlepas dari bagaimana metode diterapkan. Intinya adalah bahwa tidak ada alat untuk memberi tahu Anda jika Anda telah menguji kontrak sepenuhnya.
Karol Lewandowski
1
@ KarolLewandowski Saya pikir apa yang Shaz katakan adalah bahwa jika Anda memercayai cara kerja Optional(dan metode terkait), Anda tidak perlu lagi mengujinya. Tidak dengan cara yang sama Anda menguji if-else: setiap ifadalah ladang ranjau yang potensial. Optionaldan idiom fungsional serupa sudah dikodekan dan dijamin tidak akan membuat Anda tersandung, jadi pada dasarnya ada "cabang" yang hilang.
Andres F.
1
@AndresF. Saya tidak berpikir Karol menyarankan agar kami menguji Optional. Seperti katanya, secara logis kita harus tetap menguji yang getName()menangani berbagai kemungkinan input dengan cara yang kita inginkan, terlepas dari implementasinya. Lebih sulit untuk menentukan ini tanpa bantuan cakupan kode yang membantu dalam cara pra-JDK8.
Mike Partridge
1
@ MikePartridge Ya, tetapi intinya adalah bahwa ini tidak dilakukan melalui jangkauan cabang. Cakupan cabang diperlukan saat menulis if-elsekarena masing-masing konstruk tersebut sepenuhnya ad-hoc. Sebaliknya, Optional, orElse, map, dll, semua sudah diuji. Cabang-cabang, pada dasarnya, "menghilang" ketika Anda menggunakan idiom yang lebih kuat.
Andres F.