Dalam perjalanan ke cakupan kode 100% dengan tes di Go menggunakan sql-dumper sebagai contoh

gambar


Dalam posting ini saya akan berbicara tentang bagaimana saya menulis program konsol dalam bahasa Go untuk mengunggah data dari database ke file, mencoba untuk menutupi seluruh kode dengan tes 100%. Saya akan mulai dengan deskripsi mengapa saya membutuhkan program ini. Saya akan terus menggambarkan kesulitan pertama, beberapa di antaranya disebabkan oleh fitur bahasa Go. Selanjutnya, saya akan menyebutkan sedikit build di Travis CI, dan kemudian saya akan berbicara tentang bagaimana saya menulis tes, mencoba untuk menutupi kode 100%. Saya akan menyentuh sedikit pengujian pekerjaan dengan database dan sistem file. Sebagai kesimpulan, saya akan mengatakan apa yang menjadi keinginan untuk menutup kode dengan pengujian dan apa yang dikatakan indikator ini. Saya akan memberikan materi dengan tautan ke dokumentasi dan contoh komitmen dari proyek saya.


Tujuan Program


Program harus diluncurkan dari baris perintah dengan indikasi daftar tabel dan beberapa kolomnya, rentang data untuk kolom yang ditentukan pertama, penghitungan hubungan tabel yang dipilih satu sama lain, dengan kemampuan untuk menentukan file dengan pengaturan koneksi database. Hasil karya harus berupa file yang menjelaskan permintaan untuk membuat tabel yang ditentukan dengan kolom yang ditentukan dan menyisipkan ekspresi dari data yang dipilih. Diasumsikan bahwa penggunaan program semacam itu akan menyederhanakan skenario penggalian sebagian data dari basis data besar dan penggelaran bagian ini secara lokal. Selain itu, ini membongkar file sql yang seharusnya diproses oleh program lain, yang menggantikan bagian dari data sesuai dengan templat tertentu.


Hasil yang sama dapat dicapai dengan menggunakan salah satu klien populer ke database dan sejumlah besar pekerjaan manual. Aplikasi itu seharusnya menyederhanakan proses ini dan mengotomatisasi sebanyak mungkin.


Program ini seharusnya dikembangkan oleh pekerja magang saya untuk tujuan pelatihan dan penggunaan selanjutnya dalam pelatihan mereka selanjutnya. Tapi situasinya berubah sehingga mereka menolak gagasan ini. Tetapi saya memutuskan untuk mencoba menulis program semacam itu di waktu luang saya untuk tujuan praktik saya mengembangkan bahasa Go.


Solusinya tidak lengkap, ia memiliki sejumlah keterbatasan, yang dijelaskan dalam README. Bagaimanapun, ini bukan proyek pertempuran.


Contoh penggunaan dan kode sumber .


Kesulitan pertama


Daftar tabel dan kolomnya diteruskan ke program sebagai argumen dalam bentuk string, yaitu, tidak diketahui sebelumnya. Sebagian besar contoh bekerja dengan database di Go menyiratkan bahwa struktur basis data sudah diketahui sebelumnya, kami cukup membuat struct menunjukkan jenis-jenis setiap kolom. Tetapi dalam hal ini tidak berhasil seperti itu.


Solusi untuk ini adalah dengan menggunakan metode MapScan dari github.com/jmoiron/sqlx , yang menciptakan irisan antarmuka dalam ukuran yang sama dengan jumlah kolom sampel. Pertanyaan selanjutnya adalah bagaimana cara mendapatkan tipe data nyata dari antarmuka ini. Solusinya adalah saklar-kasus dengan jenis . Solusi semacam itu tidak terlihat sangat indah, karena akan perlu untuk membuang semua tipe ke string: bilangan bulat seperti apa adanya, string untuk melarikan diri dan melampirkan tanda kutip, tetapi pada saat yang sama menggambarkan semua tipe yang dapat berasal dari database. Saya tidak menemukan cara yang lebih elegan untuk menyelesaikan masalah ini.


Dengan tipe-tipe itu, fitur bahasa Go juga dimanifestasikan - variabel tipe string tidak dapat mengambil nilai nil , tetapi string kosong dan NULL dapat berasal dari database. Untuk mengatasi masalah ini, ada solusi dalam paket database/sql - gunakan strut khusus, yang menyimpan nilai dan tanda, apakah NULL atau tidak.


Perakitan dan perhitungan persentase cakupan kode dengan tes


Untuk perakitan saya menggunakan Travis CI, untuk mendapatkan persentase cakupan kode dengan tes - Baju. File .travis.yml untuk perakitan cukup sederhana:


 language: go go: - 1.9 script: - go get -t -v ./... - go get golang.org/x/tools/cmd/cover - go get github.com/mattn/goveralls - go test -v -covermode=count -coverprofile=coverage.out ./... - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN 

Dalam pengaturan Travis CI, Anda hanya perlu menentukan variabel lingkungan COVERALLS_TOKEN , yang nilainya harus diambil di situs .


Coveralls memungkinkan Anda untuk dengan mudah mengetahui berapa persen dari keseluruhan proyek, untuk setiap file, menyoroti satu baris kode sumber yang ternyata merupakan tes yang tidak tertutup. Sebagai contoh, pada build pertama jelas bahwa saya tidak menulis tes untuk beberapa kasus kesalahan saat mem-parsing permintaan pengguna.


Cakupan kode 100% berarti bahwa tes ditulis, antara lain, jalankan kode untuk setiap cabang di if . Ini adalah pekerjaan yang paling banyak ketika menulis tes, dan, secara umum, ketika mengembangkan aplikasi.


Anda dapat menghitung cakupan dengan tes secara lokal, misalnya, dengan go test -v -covermode=count -coverprofile=coverage.out ./... sama go test -v -covermode=count -coverprofile=coverage.out ./... , tetapi Anda dapat melakukannya dengan lebih baik di CI, Anda dapat menempatkan piring di Github.


Karena kita berbicara tentang dadu, maka saya menemukan dadu dari https://goreportcard.com berguna, yang menganalisis indikator berikut:


  • gofmt - pemformatan kode, termasuk penyederhanaan konstruksi
  • go_vet - memeriksa konstruksi yang mencurigakan
  • gocyclo - menunjukkan masalah dalam kompleksitas siklomatik
  • golint - bagi saya itu memeriksa ketersediaan semua komentar yang diperlukan
  • lisensi - proyek harus memiliki lisensi
  • ineffassign - memeriksa penugasan yang tidak efektif
  • salah mengeja - memeriksa kesalahan ketik

Kesulitan menutupi kode dengan tes 100%


Jika parsing permintaan pengguna kecil untuk komponen terutama bekerja dengan mengubah string ke beberapa struktur dari string dan cukup mudah dicakup oleh tes, maka untuk pengujian kode yang bekerja dengan database, solusinya tidak begitu jelas.


Atau, sambungkan ke server database nyata, pra-isi dengan data di setiap tes, buat pilihan, dan hapus. Tetapi ini adalah solusi yang sulit, jauh dari pengujian unit dan memaksakan persyaratannya pada lingkungan, termasuk pada server CI.


Pilihan lain bisa menggunakan database dalam memori, misalnya, sqlite ( sqlx.Open("sqlite3", ":memory:") ), tetapi ini menyiratkan bahwa kode tersebut harus terikat dengan lemah ke mesin database mungkin, dan ini sangat menyulitkan proyek tetapi untuk tes integrasi cukup baik.


Untuk pengujian unit, menggunakan tiruan untuk basis data cocok. Saya menemukan ini . Dengan menggunakan paket ini, Anda dapat menguji perilaku baik dalam hal hasil normal, dan dalam hal kesalahan, menunjukkan permintaan mana yang harus mengembalikan kesalahan mana.


Tes menulis menunjukkan bahwa fungsi yang menghubungkan ke database nyata perlu dipindahkan ke main.go, sehingga dapat didefinisikan ulang dalam tes untuk salah satu yang akan mengembalikan contoh tiruan.


Selain bekerja dengan database, perlu untuk membuat pekerjaan dengan sistem file ketergantungan terpisah. Ini akan memungkinkan mengganti rekaman file nyata dengan menulis ke memori untuk kemudahan pengujian dan mengurangi kopling. Ini adalah bagaimana antarmuka FileWriter muncul, dan dengan itu antarmuka file itu kembali. Untuk menguji skenario kesalahan, implementasi tambahan dari antarmuka ini dibuat dan ditempatkan di file filewriter_test.go , sehingga tidak termasuk dalam build umum, tetapi dapat digunakan dalam pengujian.


Setelah beberapa waktu, saya punya pertanyaan bagaimana cara menutupi main() tes. Saat itu, saya punya cukup kode di sana. Seperti yang ditunjukkan hasil pencarian, ini tidak dilakukan di Go . Sebagai gantinya, semua kode yang dapat ditarik dari main() perlu ditarik. Dalam kode saya, saya hanya menyisakan opsi parsing dan argumen baris perintah (paket flag ), menghubungkan ke database, membuat instance objek yang akan menulis file, dan memanggil metode yang akan melakukan sisa pekerjaan. Tetapi jalur ini tidak memungkinkan Anda untuk mendapatkan cakupan persis 100%.


Dalam pengujian Go, ada yang namanya " Contoh Function ". Ini adalah fungsi tes yang membandingkan output dengan apa yang dijelaskan dalam komentar di dalam fungsi tersebut. Contoh tes tersebut dapat ditemukan dalam kode sumber untuk paket go . Jika file tersebut tidak mengandung tes dan tolok ukur, maka mereka dinamai dengan awalan example_ dan diakhiri dengan _test.go . Nama masing-masing fungsi tes harus dimulai dengan Example . Pada ini, saya menulis tes untuk objek yang menulis sql ke file, mengganti catatan nyata dalam file dengan tiruan, dari mana Anda bisa mendapatkan konten dan menampilkannya. Kesimpulan ini dibandingkan dengan standar. Dengan mudah, Anda tidak perlu menulis perbandingan dengan tangan Anda, dan mudah untuk menulis beberapa baris dalam komentar. Tetapi ketika datang ke tes untuk objek yang menulis data ke file csv, kesulitan muncul. Menurut RFC4180, baris dalam CSV harus dipisahkan oleh CRLF, dan go fmt mengganti semua baris dengan LF, yang mengarah pada fakta bahwa standar dari komentar tidak cocok dengan output aktual karena pemisah baris yang berbeda. Saya harus menulis tes reguler untuk objek ini, sementara juga mengganti nama file dengan menghapus example_ dari itu.


Pertanyaannya tetap, jika file, misalnya, query.go diuji menggunakan contoh dan tes konvensional, haruskah ada dua file example_query_test.go dan query_test.go ? Di sini, misalnya, hanya ada satu example_test.go . Gunakan pencarian untuk "contoh uji coba" masih menyenangkan.


Saya belajar menulis tes di Go menurut panduan yang diberikan Google untuk "tes menulis go". Sebagian besar yang saya temui ( 1 , 2 , 3 , 4 ) menyarankan membandingkan hasilnya dengan desain bentuk yang diharapkan


 if v != 1.5 { t.Error("Expected 1.5, got ", v) } 

Tetapi ketika datang untuk membandingkan jenis, konstruksi yang akrab berkembang secara evolusioner menjadi tumpukan menggunakan "mencerminkan" atau jenis ketegasan. Atau contoh lain, ketika Anda perlu memeriksa bahwa slice atau peta memiliki nilai yang diperlukan. Kode menjadi rumit. Jadi saya ingin menulis fungsi bantu saya untuk ujian. Meskipun solusi yang baik di sini adalah menggunakan perpustakaan untuk pengujian. Saya menemukan https://github.com/stretchr/testify . Ini memungkinkan Anda untuk membuat perbandingan dalam satu baris . Solusi ini mengurangi jumlah kode dan menyederhanakan pembacaan dan dukungan tes.


Fragmentasi dan pengujian kode


Menulis tes untuk fungsi tingkat tinggi yang bekerja dengan beberapa objek memungkinkan Anda untuk secara signifikan meningkatkan nilai cakupan kode dengan tes sekaligus, karena selama tes ini banyak baris kode objek individual dieksekusi. Jika Anda menetapkan sendiri target hanya 100% cakupan, maka motivasi untuk menulis unit test pada komponen kecil sistem menghilang, karena ini tidak mempengaruhi nilai cakupan kode.


Selain itu, jika Anda tidak memeriksa hasil dalam fungsi tes, ini juga tidak akan mempengaruhi nilai cakupan kode. Anda bisa mendapatkan nilai cakupan tinggi, tetapi Anda tidak dapat mendeteksi kesalahan serius dalam aplikasi.


Di sisi lain, jika Anda memiliki kode dengan banyak cabang , setelah fungsi tebal dipanggil, maka akan sulit untuk menutupinya dengan tes. Dan di sini Anda memiliki insentif untuk meningkatkan kode ini, misalnya, untuk mengambil semua cabang menjadi fungsi terpisah dan menulis tes terpisah di atasnya. Ini secara positif akan mempengaruhi keterbacaan kode.


Jika kode memiliki kopling yang kuat, maka kemungkinan besar Anda tidak akan dapat menulis tes di atasnya, yang berarti Anda harus membuat perubahan padanya, yang secara positif akan mempengaruhi kualitas kode.


Kesimpulan


Sebelum proyek ini, saya tidak perlu menetapkan target cakupan 100% dari kode dengan tes. Saya bisa mendapatkan aplikasi yang berfungsi dalam 10 jam pengembangan, tetapi butuh 20-30 jam untuk mencapai cakupan 95%. Dengan menggunakan contoh kecil, saya mendapat ide tentang bagaimana nilai cakupan kode mempengaruhi kualitasnya, dan berapa banyak upaya yang diperlukan untuk mempertahankannya.


Kesimpulan saya adalah bahwa jika Anda melihat dasbor dengan nilai cakupan kode tinggi untuk seseorang, hampir tidak ada yang mengatakan tentang seberapa baik aplikasi ini telah diuji. Bagaimanapun, Anda harus menonton tes sendiri. Tetapi jika Anda sendiri telah menetapkan kursus untuk 100% jujur, maka ini akan membantu Anda menulis aplikasi dengan lebih baik.


Anda dapat membaca lebih lanjut tentang ini di bahan-bahan berikut dan mengomentarinya:



Spoiler

Kata "coating" digunakan sekitar 20 kali. Maaf

Source: https://habr.com/ru/post/id418565/


All Articles