Poczytaj o TDD: https://en.wikipedia.org/wiki/Test-driven_development
Tu masz biblioteki które wspierają testy (oczywiście nie wszystkie): https://stackoverflow.com/questions/65820/unit-testing-c-code
Ja używam tego: https://github.com/catchorg/Catch2
oraz: https://github.com/google/googletest
Początek, etap RED. To oznacza na początku "że się kompiluje" i "po uruchomieniu testu nie przechodzi".
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// Funkcja oblicza wartość kwadratu dla zakresu argumentów [10, 30].
// Przedział jest zamknięty czyli "włącznie z...".
int calculate(int val) {
return 0;
}
int main(void) {
assert(calculate(10) == 100);
return EXIT_SUCCESS;
}
Kompilujesz, uruchamiasz i ...
$ ./calculate
calculate: calculate.c:12: int main(void): Assertion `calculate(10) == 100' failed.
Przerwane (zrzut pamięci)
Wydzielam test (dalej jestem w RED), będzie porządniej. Brak w specyfikacji jak się zachować poza zakresem, coś przyjmuję i dokumentuję w teście.
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// Funkcja oblicza wartość kwadratu dla zakresu argumentów [10, 30].
// Przedział jest zamknięty czyli "włącznie z...".
int calculate(int val) {
return 0;
}
void test_range(void) {
assert(100 == calculate(10));
assert(900 == calculate(30));
assert(121 == calculate(11));
assert(841 == calculate(29));
// Przyjmuję że w przypadku błędu, funkcja zwraca 0
assert(0 == calculate(9));
assert(0 == calculate(31));
}
int main(void) {
test_range();
return EXIT_SUCCESS;
}
Czas na implementację. Przechodzę do GREEN. Niektórzy (np. ja) wyróżniają tu etap GREY. To oznacza że kod "jakoś działa" ale wygląda słabo.. zasymuluję to.
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// Funkcja oblicza wartość kwadratu dla zakresu argumentów [10, 30].
// Przedział jest zamknięty czyli "włącznie z...".
int calculate(int val) {
if(val > 30) {
return 0;
}
if(val < 10) {
return 0;
}
return val * val;
}
void test_range(void) {
assert(100 == calculate(10));
assert(900 == calculate(30));
assert(121 == calculate(11));
assert(841 == calculate(29));
// Przyjmuję że w przypadku błędu, funkcja zwraca 0
assert(0 == calculate(9));
assert(0 == calculate(31));
}
int main(void) {
test_range();
return EXIT_SUCCESS;
}
Na pierwszy rzut oka widać że warunek można połączyć. Drugie pytanie to co oznacza 0 zwracane z funkcji? Poprawiam TYLKO TO bo pisanie kodu TDD polega także na trzymaniu się etapów. Test jako zobowiązanie i stan GREEN jako rozwiązanie.
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// Funkcja oblicza wartość kwadratu dla zakresu argumentów [10, 30].
// Przedział jest zamknięty czyli "włącznie z...".
int calculate(int val) {
static const int OUT_OF_RANGE = 0;
if((val > 30) || (val < 10)) {
return OUT_OF_RANGE;
}
return val * val;
}
void test_range(void) {
assert(100 == calculate(10));
assert(900 == calculate(30));
assert(121 == calculate(11));
assert(841 == calculate(29));
// Przyjmuję że w przypadku błędu, funkcja zwraca 0
assert(0 == calculate(9));
assert(0 == calculate(31));
}
int main(void) {
test_range();
return EXIT_SUCCESS;
}
Kod jest w stanie GREEN, może zostać wypchnięty do repozytorium.
Teraz następna pętla.... Hmm.. co przetestować? Przecież nie będę testował elementów (wszystkich) po kolei! Wybiorę 1, może 2... tak będę miał nadzieję że przetestowałem cały zakres.
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// Funkcja oblicza wartość kwadratu dla zakresu argumentów [10, 30].
// Przedział jest zamknięty czyli "włącznie z...".
int calculate(int val) {
static const int OUT_OF_RANGE = 0;
if((val > 30) || (val < 10)) {
return OUT_OF_RANGE;
}
return val * val;
}
void test_range(void) {
assert(100 == calculate(10));
assert(900 == calculate(30));
assert(121 == calculate(11));
assert(841 == calculate(29));
// Przyjmuję że w przypadku błędu, funkcja zwraca 0
assert(0 == calculate(9));
assert(0 == calculate(31));
}
void test_value(void) {
assert(484 == calculate(22));
assert(225 == calculate(15));
}
int main(void) {
test_range();
test_value();
return EXIT_SUCCESS;
}
O! Kod przeszedł przez RED i wskoczył do GREY! Czy można coś jeszcze poprawić by był czytelny? Np można tak:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// Funkcja oblicza wartość kwadratu dla zakresu argumentów [10, 30].
// Przedział jest zamknięty czyli "włącznie z...".
int calculate(int val) {
static const int OUT_OF_RANGE = 0;
static const int LOWER_BOUND = 10;
static const int HIGH_BOUND = 30;
if((val > HIGH_BOUND) || (val < LOWER_BOUND)) {
return OUT_OF_RANGE;
}
return val * val;
}
void test_range(void) {
assert(100 == calculate(10));
assert(900 == calculate(30));
assert(121 == calculate(11));
assert(841 == calculate(29));
// Przyjmuję że w przypadku błędu, funkcja zwraca 0
assert(0 == calculate(9));
assert(0 == calculate(31));
}
void test_value(void) {
assert(484 == calculate(22));
assert(225 == calculate(15));
}
int main(void) {
test_range();
test_value();
return EXIT_SUCCESS;
}
Teraz możesz spokojnie się zastanawiać czy zmienić te stałe na makra, zmienić implementację mnożenia coś poprawiać. Kod masz broniony przez testy. Oczywiście należy je wydzielić do oddzielnych plików oraz tak naprawdę użyć biblioteki do testowania. Ja chciałem pokazać przykład więc użyłem prymitywnego assert(..)