Dobre przykłady testów jednostkowych dla wbudowanych programistów C [zamknięte]

20

W przyszłym tygodniu będę rozmawiać z moim działem na temat testów jednostkowych i rozwoju opartego na testach. W ramach tego zamierzam pokazać przykłady z rzeczywistego świata z kodu, który napisałem niedawno, ale chciałbym też pokazać kilka bardzo prostych przykładów, które napiszę w wykładzie.

Szukałem dobrych przykładów w Internecie, ale starałem się znaleźć takie, które szczególnie pasują do naszego obszaru rozwoju. Prawie całe oprogramowanie, które piszemy, to głęboko osadzone systemy sterowania działające na małych mikrokontrolerach. Jest dużo kodu C, który można łatwo zastosować do testowania jednostkowego (będę mówił o testowaniu jednostkowym na PC, a nie na samym celu), o ile trzymasz się z dala od „dolnej” warstwy: rzeczy, które mówią bezpośrednio do urządzeń peryferyjnych mikrokontrolera. Jednak większość przykładów, które znalazłem, zwykle opiera się na przetwarzaniu ciągów (np. Doskonały przykład cyfr rzymskich Dive Into Python), a ponieważ rzadko kiedy używamy ciągów, nie jest to tak naprawdę odpowiednie (o jedynych funkcjach bibliotecznych, których zwykle używa nasz kod są memcpy, memcmpi memset,strcat lub wyrażenia regularne nie są całkiem właściwe).

Tak więc, na pytanie: czy ktoś może zaoferować dobre przykłady funkcji, których mogę użyć do zademonstrowania testów jednostkowych podczas sesji na żywo? Dobrą odpowiedzią w mojej (z zastrzeżeniem zmiany) opinii byłoby prawdopodobnie:

  • Funkcja, która jest na tyle prosta, że ​​każdy (nawet ci, którzy piszą kod tylko od czasu do czasu) może zrozumieć;
  • Funkcja, która nie wydaje się bezcelowa (tj. Wypracowanie parzystości lub CRC jest prawdopodobnie lepsza niż funkcja, która mnoży dwie liczby razem i dodaje losową stałą);
  • Funkcja, która jest wystarczająco krótka, aby pisać przed pokojem ludzi (mogę skorzystać z wielu schowków Vima, aby zmniejszyć liczbę błędów ...);
  • Funkcja, która przyjmuje liczby, tablice, wskaźniki lub struktury jako parametry i zwraca coś podobnego, zamiast obsługi ciągów;
  • Funkcja z prostym błędem (np. >Zamiast >=), łatwa do wprowadzenia, która nadal działałaby w większości przypadków, ale zepsułaby się w przypadku określonego przypadku krawędzi: łatwa do zidentyfikowania i naprawienia za pomocą testu jednostkowego.

jakieś pomysły?

Chociaż prawdopodobnie nie jest to istotne, same testy prawdopodobnie zostaną napisane w C ++ przy użyciu Google Test Framework: wszystkie nasze nagłówki mają już #ifdef __cplusplus extern "C" {wokół siebie opakowanie; działało to dobrze z testami, które przeprowadziłem do tej pory.

DrAl
źródło
Biorąc pod uwagę ten „problem” jako przedstawienie prezentacji sprzedaży TDD kierownictwu, wydaje mi się, że dość dobrze pasuje do pożądanego formatu. Wydaje się, że PO domaga się istniejących rozwiązań tego problemu.
Technophile

Odpowiedzi:

15

Oto prosta funkcja, która ma generować sumę kontrolną na bajtach len .

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;
}

Ma błąd płotu: w instrukcji for powinien być test i < len.

Zabawne jest to, że jeśli zastosujesz go do takiego ciągu tekstowego ...

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

dostaniesz „właściwą odpowiedź”! Jest tak, ponieważ dodatkowy bajt, który został sprawdzony, był terminatorem zerowego ciągu. Możesz więc skończyć z dodawaniem funkcji sumy kontrolnej do kodu, a może nawet wysyłaniem jej razem i nigdy nie zauważać problemu - to znaczy, dopóki nie zaczniesz stosować jej do czegoś innego niż ciągi tekstowe.

Oto prosty test jednostkowy, który oznaczy ten błąd (przez większość czasu ... :-)

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
źródło
Bardzo dobre! To właśnie taka odpowiedź, na którą liczyłem: dziękuję.
DrAl
Czy podczas tworzenia bufora masz już śmieci w tej części pamięci, czy naprawdę konieczne jest zainicjowanie go losowymi liczbami?
Snake Sanders,
@ SnakeSanders Powiedziałbym, że tak, ponieważ chcesz, aby testy jednostkowe były jak najbardziej deterministyczne. Jeśli kompilator, którego używasz, umieści tam 0 na komputerze programisty i 10 na komputerze testowym, będziesz miał straszny czas na znalezienie błędu. Myślę, że uzależnianie tego od czasu zamiast stałego ziarna jest złym pomysłem z tego samego powodu.
Andrew mówi Przywróć Monikę
Poleganie na niedeterministycznych zachowaniach w teście jednostkowym jest złym pomysłem. Tłuszczowy test prędzej czy później sprawi ci ból głowy ...
sigy,
2

Co z implementacją funkcji sortowania, takiej jak bąbelkowa ? Po uruchomieniu funkcji sortowania możesz kontynuować wyszukiwanie binarne, które jest równie dobre do wprowadzenia testów jednostkowych i TDD.

Sortowanie i wyszukiwanie zależy od porównań, które łatwo się pomylić. Obejmuje to także zamianę wskaźników, które należy wykonać ostrożnie. Oba są podatne na błędy, więc możesz się popsuć :)

Jeszcze kilka pomysłów:

  • Testy jednostkowe bardzo pomagają podczas refaktoryzacji. Więc kiedy twój sortowanie bąbelkowe zadziała, możesz zmienić go na bardziej wydajny, taki jak qsort, a testy powinny się jeszcze udać, co potwierdza, że ​​twoja nowa funkcja sortowania również działa.
  • Sortowanie jest łatwe do przetestowania, wynik jest albo sortowany, albo nie, co czyni go dobrym kandydatem.
  • To samo dotyczy wyszukiwania; albo istnieje, albo nie.
  • Pisanie testów do sortowania otwiera dyskusje, takie jak typ danych wejściowych używanych w teście (zero elementów, losowe dane wejściowe, zduplikowane wpisy, ogromne tablice itp.).
Martin Wickman
źródło
Czy masz jakieś konkretne sugestie dotyczące prostego błędu, który pokazałby, w jaki sposób testowanie ułatwia życie?
DrAl
@DrAl: Zaktualizowałem moją odpowiedź o to.
Martin Wickman