Contoh pengujian unit yang bagus untuk pengembang C tertanam [ditutup]

20

Saya akan memberikan ceramah kepada departemen saya minggu depan tentang pengujian unit dan pengembangan yang digerakkan oleh pengujian. Sebagai bagian dari ini, saya akan menunjukkan beberapa contoh dunia nyata dari beberapa kode yang saya tulis baru-baru ini, tetapi saya juga ingin menunjukkan beberapa contoh yang sangat sederhana yang akan saya tulis dalam ceramah.

Saya telah mencari di web untuk contoh-contoh yang baik, tetapi saya telah berjuang untuk menemukan yang cocok untuk bidang pengembangan kami. Hampir semua perangkat lunak yang kami tulis adalah sistem kontrol tertanam dalam yang dijalankan pada mikrokontroler kecil. Ada banyak kode C yang mudah diterapkan untuk pengujian unit (saya akan berbicara tentang pengujian unit pada PC daripada pada target itu sendiri) selama Anda menghindari lapisan 'bawah': hal-hal yang berbicara langsung ke peripheral mikrokontroler. Namun, sebagian besar contoh yang saya temukan cenderung didasarkan pada pemrosesan string (mis. Contoh angka Romawi Dive Into Python yang sangat baik) dan karena kita jarang menggunakan string, ini tidak benar-benar cocok (tentang satu-satunya fungsi perpustakaan yang biasanya digunakan kode kita adalah memcpy, memcmpdan memset,strcat atau ekspresi reguler kurang tepat).

Jadi, pada pertanyaan: bisakah ada yang menawarkan beberapa contoh fungsi yang bagus yang dapat saya gunakan untuk menunjukkan pengujian unit dalam sesi langsung? Jawaban yang bagus menurut pendapat saya (dapat berubah) mungkin adalah:

  • Sebuah fungsi yang cukup sederhana sehingga siapa pun (bahkan mereka yang hanya menulis kode sesekali) dapat mengerti;
  • Fungsi yang tidak tampak sia-sia (yaitu mengerjakan paritas atau CRC mungkin lebih baik daripada fungsi yang mengalikan dua angka bersamaan dan menambahkan konstanta acak);
  • Sebuah fungsi yang cukup pendek untuk ditulis di depan ruangan orang (saya dapat memanfaatkan banyak clipboard Vim untuk mengurangi kesalahan ...);
  • Fungsi yang mengambil angka, array, pointer atau struktur sebagai parameter dan mengembalikan sesuatu yang serupa, daripada menangani string;
  • Fungsi yang memiliki kesalahan sederhana (misalnya, >bukan >=) yang mudah dimasukkan ke dalam yang akan tetap bekerja dalam kebanyakan kasus tetapi akan pecah dengan beberapa kasus tepi tertentu: mudah diidentifikasi dan diperbaiki dengan uji unit.

Adakah pikiran?

Meskipun mungkin tidak relevan, tes itu sendiri mungkin akan ditulis dalam C ++ menggunakan Google Test Framework: semua header kami sudah memiliki #ifdef __cplusplus extern "C" {pembungkus di sekitarnya; ini telah bekerja dengan baik dengan tes yang saya lakukan sejauh ini.

DrAl
sumber
Mengambil "masalah" di sini sebagai datang dengan presentasi untuk menjual TDD kepada manajemen, ini menurut saya cocok dengan format yang diinginkan dengan cukup baik. OP tampaknya meminta solusi yang ada untuk masalah ini.
Technophile

Jawaban:

15

Berikut adalah fungsi sederhana yang seharusnya menghasilkan checksum lebih dari len byte.

int checksum(void *p, int len)
{
    int accum = 0;
    unsigned char* pp = (unsigned char*)p;
    int i;
    for (i = 0; i <= len; i++)
    {
        accum += *pp++;
    }
    return accum;
}

Ini memiliki bug fencepost: dalam pernyataan for, tes seharusnya i < len.

Apa yang menyenangkan adalah, jika Anda menerapkannya ke string teks seperti ini ...

char *myString = "foo";
int testval = checksum(myString, strlen(myString));

Anda akan mendapatkan "jawaban yang benar"! Itu karena byte tambahan yang checksummed adalah terminator string nol. Jadi Anda bisa berakhir dengan menempatkan fungsi checksum ini ke dalam kode, dan mungkin bahkan mengirim dengannya, dan tidak pernah melihat masalah - yaitu, sampai Anda mulai menerapkannya pada sesuatu selain string teks.

Berikut ini adalah unit test sederhana yang akan menandai bug ini (sebagian besar waktu ... :-)

void main()
{
    // Seed the random number generator
    srand(time(NULL));

    // Fill an array with junk bytes
    char buf[1024];
    int i;
    for (i = 0; i < 1024; i++)
    {
        buf[i] = (char)rand();
    }

    // Put the numbers 0-9 in the first ten bytes
    for (i = 0; i <= 9; i++)
    {
        buf[i] = i;
    }

    // Now, the unit test. The sum of 0 to 9 should
    // be 45. But if buf[10] isn't 0 - which it won't be,
    // 255/256 of the time - this will fail.
    int testval = checksum(buf, 10);
    if (testval == 45)
    {
        printf("Passed!\n");
    }
    else
    {
        printf("Failed! Expected 45, got %d\n", testval);
    }
}
Bob Murphy
sumber
Sangat bagus! Ini hanya semacam jawaban yang saya harapkan: terima kasih.
DrAl
Ketika Anda membuat buffer yang sudah Anda miliki dengan sampah di memori itu, apakah benar-benar perlu menginisialisasi dengan angka acak?
Snake Sanders
@SnakeSanders Saya akan mengatakan ya, karena Anda ingin tes unit menjadi deterministik mungkin. Jika kompiler yang Anda gunakan kebetulan menempatkan 0 di sana pada mesin pengembang Anda dan 10 pada mesin uji Anda, Anda akan kesulitan menemukan bug. Saya pikir membuatnya tergantung pada waktu dan bukannya benih yang tetap adalah ide yang buruk, untuk alasan yang sama.
Andrew mengatakan Reinstate Monica
Mengandalkan perilaku non-deterministik dalam unit test adalah ide yang buruk. Tes yang
rapuh
2

Bagaimana dengan mengimplementasikan fungsi sortir seperti bubble sort ? Setelah fungsi sortir berfungsi, Anda dapat melanjutkan dengan pencarian biner yang sama baiknya untuk memperkenalkan pengujian unit dan TDD.

Penyortiran dan pencarian tergantung pada perbandingan yang mudah salah. Ini juga melibatkan penukaran pointer di sekitar yang harus dilakukan dengan hati-hati. Keduanya rentan terhadap kesalahan, jadi silakan mengacau :)

Beberapa ide lagi:

  • Tes unit sangat membantu ketika melakukan refactoring. Jadi begitu jenis gelembung Anda bekerja, Anda bisa mengubahnya menjadi jenis yang lebih kuat seperti qsort, dan tes masih harus berlalu, membuktikan fungsi sortir baru Anda juga berfungsi.
  • Penyortiran mudah untuk diuji, hasilnya disortir, atau tidak, yang membuatnya menjadi kandidat yang baik.
  • Hal yang sama untuk pencarian; itu ada atau tidak.
  • Menulis tes untuk menyortir membuka diskusi seperti apa jenis input yang akan digunakan untuk tes (nol elemen, input acak, entri duplikat, array besar dll).
Martin Wickman
sumber
Apakah Anda memiliki saran khusus untuk kesalahan sederhana yang akan menunjukkan bagaimana pengujian membuat hidup lebih mudah?
DrAl
@DrAl: Memperbarui jawaban saya dengan itu.
Martin Wickman