Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
«Проваленные» тесты совсем не означают, что какая-то функциональность не работает
Как-то тема не особо раскрыта.Согласен, но мне показалось, что делать пост еще длиннее уже не прилично.
почему программисты не любят юнит-тестыпотому, что далеко не всегда умеют их писать, и как следствие — не видят в них смысла (просто из личного опыта, никого не хочу обидеть)
и что с этим делатьУчиться, учиться, еще раз учиться.
Писать тесты на основе поведения и открытого апи? И все?Поверьте, это не мало.
«Проваленные» тесты совсем не означают, что какая-то функциональность не работаетЭто вот о чем. Если тесты основаны на подробном знании внутренностей модуля, то банальное переименование list_head в listHead «провалит» нам все тесты. Но модуль как был работоспособным, так и остался.
Как это обычно бывает
Полезность и необходимость юнит-тестов демонстрируется на синтетических примерах в вакууме, вроде коллекций и калькуляторов.
Что мы делаем не так?Ммм, смешиваете в одну кучу интеграционные тесты и TDD?
А если писать сначала тесты, а потом код. Это поможет?
К сожалению нет. Или далеко не всегда.
extern void * list_head;
Если честно, то хуже реализацию списков придумать сложно…
Это типовое решение. Никакой необходимости изобретать велосипед нет.
достаточно добавить в функции параметр node_t *&list_head.
Тщательно проектировать API и мокать всю периферию.В этой фразе нет ни одного лишнего слова.
struct NewAlloc
{
static void* malloc(int size)
{
return ::malloc(size);
}
static void free(void* item)
{
::free(item);
}
};
template <typename Alloc = NewAlloc>
class LList
{
typedef struct node
{
int val = 0;
struct node* next = nullptr;
} node_t;
node_t* list_head = nullptr;
public:
void list_push(int val)
{
node_t* current = list_head;
if (list_head == nullptr) {
list_head = (node_t*)Alloc::malloc(sizeof(node_t));
list_head->val = val;
list_head->next = NULL;
}
else {
while (current->next != NULL) {
current = current->next;
}
current->next = (node_t*)Alloc::malloc(sizeof(node_t));
current->next->val = val;
current->next->next = NULL;
}
}
int list_pop(void)
{
if (list_head == nullptr) {
return -1;
}
node_t* next_node = list_head->next;
int retval = list_head->val;
Alloc::free(list_head);
list_head = next_node;
return retval;
}
};
struct CountingAlloc
{
static int mallocCounter;
static int freeCounter;
static void reset()
{
mallocCounter = 0;
freeCounter = 0;
}
static void* malloc(int size)
{
mallocCounter ++;
return ::malloc(size);
}
static void free(void* item)
{
freeCounter --;
::free(item);
}
};
int CountingAlloc::mallocCounter = 0;
int CountingAlloc::freeCounter = 0;
namespace UnitTest1
{
TEST_CLASS(UnitTest1)
{
public:
TEST_METHOD(test_push)
{
CountingAlloc::reset();
LList<CountingAlloc> l;
l.list_push(1);
Assert::AreEqual(1, CountingAlloc::mallocCounter);
Assert::AreEqual(0, CountingAlloc::freeCounter);
}
TEST_METHOD(test_pop)
{
CountingAlloc::reset();
LList<CountingAlloc> l;
l.list_push(1);
l.list_pop();
Assert::AreEqual(1, CountingAlloc::mallocCounter);
Assert::AreEqual(1, CountingAlloc::freeCounter);
}
};
}
А сделать mock-и на функции стандартной библиотеки не самая простая задача.man 3 malloc_hook (если GNU-расширения в данном проекте допустимы).
Проще и чище, чем подгонять тестируемый код под тесты.позволю себе не согласится. Это только кажется, что код «подгоняется» под тесты. А по-факту, получается очень качественный и легко поддерживаемый код (это я не про #ifdef UNIT_TEST конечно же).
Почему разработчики не любят Юнит Тесты