LINUX.ORG.RU

Тестирование «C» кода.

 , , , ,


4

4

Всем привет ::) Господа, вы наверное будете в голос ржать, но я в жизни не занимался тестированием кода, вот прям сосем никогда, от того прошу советов как тестировать чем тестировать и что по теме почитать/поучить как это дело автоматизируется и прочее. Да есть google, но я как всегда иду туда в последнюю очередь. Ну и смею себе позволить позвать людей (кого с ходу вспомню) плотно работающий с «С». Cast DELIRIUM, i-rinat, beastie, ncrmnt, theNamelessOne, Murg, Iron_Bug. Всем добра и улыбашек ::)

По просьбе i-rinat пример кода (просто рандомный код из закромов откопанный/заброшенный/недописанный, в общем то что нужно)

#include <stddef.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

struct $struct_neuron
{
    size_t  id;
    struct  $struct_neuron ** sinapses_connections;
    size_t  sinapses_connections_size;
    float * sinapses_weight;
    struct  $struct_neuron ** acsons_connections;
    size_t  acsons_connections_size;
    float   acsons_result_signals;
    void   (*finp)();
    void   (*fmid)();
    void   (*fout)();
};typedef struct $struct_neuron struct_neuron;

struct $struct_network
{
    size_t id;
    size_t network_size;
    struct_neuron * neurons;
    struct $struct_network ** network_connections;
    size_t network_connections_size;;
};typedef struct $struct_network struct_network;


struct $struct_net_union
{
    size_t id;
    struct_network ** networks;
    size_t net_union_size;

};typedef struct $struct_net_union struct_net_union;


struct_neuron neuro_neuron_new(void);
void neuro_neuron_connect(struct_neuron* acson, struct_neuron * sinaps);

struct_network neuro_network_new(size_t neurons);
void neuro_network_connect(struct_network* net1, struct_network* net2);



#include "neuro.h"




struct_neuron neuro_neuron_new(void)
{
    static size_t id=0;
    struct_neuron neuron;
    neuron.id=id++;
    neuron.sinapses_connections      = NULL;
    neuron.sinapses_connections_size = 0;
    neuron.sinapses_weight           = NULL;
    neuron.acsons_connections        = NULL;
    neuron.acsons_connections_size   = 0;
    neuron.acsons_result_signals     = 0.0;
    return neuron;
}

void neuro_neuron_connect(struct_neuron * acson, struct_neuron * sinaps)
{
    sinaps->sinapses_weight = realloc(sinaps->sinapses_weight,sizeof(float)*sinaps->sinapses_connections_size+1);
    srand(clock());
    sinaps->sinapses_weight[sinaps->sinapses_connections_size] = (float)(rand()/100000009);
    sinaps->sinapses_connections = realloc(sinaps->sinapses_connections,sizeof(struct_neuron)*(sinaps->sinapses_connections_size+1));
    sinaps->sinapses_connections[sinaps->sinapses_connections_size++] = acson;
    acson->acsons_connections = realloc(acson->acsons_connections,sizeof(struct_neuron)*(acson->acsons_connections_size+1));
    acson->acsons_connections[acson->acsons_connections_size++] = sinaps;
}



struct_network neuro_network_new(size_t neurons)
{
    static size_t id = 0;
    struct_network network;
    network.id = id++;
    network.network_size = neurons;
    network.neurons = NULL;
    network.network_connections = NULL;
    network.network_connections_size = 0;
    network.neurons = malloc(sizeof(struct_neuron)*neurons);
    for(size_t i = 0; i < neurons; i++)
    {
        network.neurons[i] = neuro_neuron_new();
    };
    return network;
};

void neuro_network_connect(struct_network * net1, struct_network *net2)
{
    net1->network_connections = realloc(net1->network_connections,sizeof(struct_network)*(net1->network_connections_size+1));
    net1->network_connections[net1->network_connections_size++] = net2;
    net2->network_connections = realloc(net2->network_connections,sizeof(struct_network)*(net2->network_connections_size+1));
    net2->network_connections[net2->network_connections_size++] = net1;
    for(size_t i = 0; i < net1->network_size; i++)
    {
        for(size_t p = 0; p < net2->network_size; p++)
        {
            neuro_neuron_connect(&net1->neurons[i],&net2->neurons[p]);
        };
    };
};


struct_net_union neuro_net_union_new()
{
    static size_t id=0;
    struct_net_union net_union;
    net_union.id=id++;
    net_union.networks=NULL;
    net_union.net_union_size=0;
    return net_union;
}

void neuro_net_union_add(struct_net_union * net_union, struct_network * network)
{
    net_union->networks = realloc(net_union->networks,sizeof(struct_network)*(net_union->net_union_size+1));
    net_union->networks[net_union->net_union_size] = network;
    net_union->net_union_size++;
}




#include "neuro.h"


int main(int argc, char const *argv[])
{

	struct_network  net1 = neuro_network_new(100);

	struct_network  net2 = neuro_network_new(100);

	neuro_network_connect(&net1,&net2);

	
	return 0;
}
gcc -g neuro.c main.c -o neuro
★★★★★

Какой код ты тестировать собрался?

Вот простой метод, предусмотри в программе параметр --test Если программа будет его получать, то вместо выполнения основного алгоритма, она будет по очереди тестировать все функции, передавая в них тестовые параметры и сравнивая с эталонными результатами.

PeKar ()

Тестировал Си код С++сным. Catch. В принципе можно и всяким gtest обмазаться и даже gmock

anonymous ()

Тестирование - это просто вызов фрагментов кода с проверкой возвращаемых значений. В самом простом случае это банальный набор assert'ов:

assert(mysquare(0) == 0);
assert(mysquare(1) == 1);
assert(mysquare(2) == 4);

Если хочется красивостей, бери любую библиотеку для тестирования (можно плюсовую, gtest, catch, наверняка есть и сишные) - вместо assert у тебя будут какие-нибудь EXPECT_FOO() которые по сути то же самое, только будут красиво печатать что происходит. А можешь просто сам написать набор простейших макросов вида

#define EXPECT_TRUE(val) do { \
  int ret = !!(val); \
  if (ret) { \
    printf("[PASSED] %s\n", #val); \
  } else { \
    printf("[FAILED] %s\n", #val); \
    exit(1); \
  } \
} while(0)

...

EXPECT_TRUE(mysquare(0) == 0);
EXPECT_TRUE(mysquare(1) == 2);
EXPECT_TRUE(mysquare(2) == 4);

и получить то же самое.

slovazap ★★★★★ ()
Последнее исправление: slovazap (всего исправлений: 1)
Ответ на: комментарий от slovazap

Тестирование - это просто вызов фрагментов кода с проверкой возвращаемых значений.

Нет. Есть юнит-тестирование, тестирование регрессий, фаззинг, mock, etc.

RazrFalcon ★★★★★ ()
Последнее исправление: RazrFalcon (всего исправлений: 1)

Посмотрите в сторону Unity, который не игровой движок.

DarkAmateur ★★★ ()

Ну кстати у меня своя точка зрения на тестирование, которая отличается от общепринятой, если интересно стукнись в жаббер.

DELIRIUM ☆☆☆☆☆ ()
Ответ на: комментарий от RazrFalcon

Проверка утечек, санитайзеры, coverage, мутации применимы к любым тестам. Фаззинг - всего лишь способ генерации входных значений.

slovazap ★★★★★ ()

Это бессмысленный вопрос. Покажи пример кода. И тогда, может быть, будут какие-то ответы близкие к тому, что ты ищешь.

В подобной общей формулировке это просто вброс для флейма.

i-rinat ★★★★★ ()

Многие фрэймфорки для тестирования C не умеют авто-регистрацию тестов, поэтому нужно сначала определиться будет ли использоваться C++, внешний скрипт для генерации кода вызова списка тестов, другие извращения или руками это будет писаться (это просто муторно и переделывать тоже неудобно, поэтому стоит определиться сразу). А на wiki есть их список.

xaizek ★★★★★ ()
Ответ на: комментарий от xaizek

Как-то сложно у вас все. Я всегда использовал и использую printf в качестве теста. Пока что-то пишешь, определяешь макрос EBUG и вставляешь везде, где нужно, макросы DBG(...), а как все заработало, отключаешь EBUG и выкладываешь на гитхаб.

anonymous ()

я использую XCTest в персональном проекте (но это макос-онли), и UnitTest++ на работе (кроссплатформ).

тесты, понятно, пишутся на ObjC и C++ соотв, но ничто не мешает тестировать сишный код ими.

waker ★★★★★ ()

Тестировал C код gtest'om брат жив.

Почитать про основы тестирования, типа не тестировать уже протестированное, что такое fixture, setup/teardown, test case, mockup и fake. Как выбирать данные для тестирования. В принципе, годная вводная есть в тех же gtest, только читать лучше от корки до корки, может advanced часть по диагонали.

Для C есть ещё check, но там много ручных телодвижений.

pon4ik ★★★★★ ()
Ответ на: комментарий от pon4ik

Только при использовании gtest, придётся держать в голове что есть разница небольшая между C подмножеством крестов и C (всякие sizeof('a') и что там ещё было). С другой стороны, если твоё api (а в результате юнит тестов обычно получается api как ни старайся:)) нельзя будет использовать из плюсов - кому оно надо.

pon4ik ★★★★★ ()
Ответ на: комментарий от PeKar

Не, тесты должны быть отдельной сущностью

Dron ★★★★★ ()
Ответ на: комментарий от RazrFalcon

Ну, это не моя профессия, я так, тык тык в кнопки и сё ))

Dron ★★★★★ ()
Ответ на: комментарий от MyTrooName

Я должен хорошенько удариться головой для этого, не в плохом смысле, есть на то причины )) Пока что нет.

Dron ★★★★★ ()

Ну и смею себе позволить позвать людей (кого с ходу вспомню) плотно работающий с «С»

Меня с ходу можешь вычёркивать )

theNamelessOne ★★★★★ ()
Ответ на: комментарий от theNamelessOne

Хм, а я тебя пометил когда то ::) Ну ладно

theNamelessOne ★★★★★ (21.11.2017 20:33:26) сишник, вроде толковый.

Dron ★★★★★ ()
Последнее исправление: Dron (всего исправлений: 1)
Ответ на: комментарий от DELIRIUM

у нас есть тааакие приборы, но мы вам о них не расскажем, угу.

давай уже, позорься.

anonymous ()
Ответ на: комментарий от pon4ik

Ну основная идея в том, чтобы не зацикливаться на юнит-тестах, а больше делать упор на автоматизированные интеграционные тесты. Например, я часто «покрываю» C++ код автотестами на perl (Test::More и тд), а на юнит-тесты в их классическом понимании кладу болт.

DELIRIUM ☆☆☆☆☆ ()
Ответ на: комментарий от DELIRIUM

ну как и предполагалось. о том, что количество тестов для достижения приемлемого уровня покрытия в случае юнит-тестов - растет линейно, а в случае интеграционных - экспотенциально, мы умолчим, да? а увеличение количества тестов - это, на секундочку, не только красивые циферки в репорте, но и увеличение времени выполнения (существенное) и времени, затрачиваемого на их поддержку (более, чем существенное)

интеграционные тесты, как и любое другое усложнение системы, должны применяться только там, где без них не обойтись. а не тяпляпаться «патамушта так удобно»

anonymous ()
Ответ на: комментарий от DELIRIUM

А чего такого особенного тут? Как минимум на одном проекте в моей практике сиё очень широко практиковалось(ибо состояло процентов на 80 из i/o и мэппинга). А учитывая что можно мёржить коверэйдж после прогона всех тестов что есть в наличии...

Имхо главное что бы код был покрыт тестами, а какими это уже третье дело, пусть хоть вручную 99% коверейдж на регрессионном выбивают товарищи :)

Рекомендую ещё попробовать test.py(a.k.a. pytest, на деле оказалась очень хорошая штука в паре со всякими pexpect).

pon4ik ★★★★★ ()
Ответ на: комментарий от pon4ik

особенного ничего, каждый первый думает, что он открыл этим что-то новое, но по сути - это разбазаривание ресурсов (в лучшем случае) или замена качества ресурсов количеством - плодить 100500 интеграционных тестов могут и обезьяны, а для написания качественных юнит-тестов думать надо

anonymous ()
Ответ на: комментарий от anonymous

Одно не заменяет другое. В идеальном мире на планете формы шара, надо иметь и то и другое.

А в нашей убогой бытности надо делать продукт, а то денег не дадут. Есть вещи которые проще и быстрее покрыть в юнит, есть вещи которые в интеграционных.

Ну сиё тоже есть банальщина 100500 раз повторенная в интернетах.

pon4ik ★★★★★ ()
Последнее исправление: pon4ik (всего исправлений: 1)
Ответ на: комментарий от pon4ik

простота покрытия интеграционными тестами - либо от того, что интеграционные тесты на самом деле не интеграционные, а просто плохо написанные с использованием более удобного фреймворка юнит-тесты, либо от того, что наверху пищевой цепочки безграмотный самодур, с которым проще согласиться и сделать дурость, чем доказать что-то. так себе причины для выбора подхода к тестированию

anonymous ()

Valgrind/Sanitizers для С - обязательно (бегло глядя на код, не видно парных free к malloc'ам, как-то неаккуратненько)

Об остальном уже написали. Добавлю, для unit-тестирования на чистом С есть интересный и простой header-only https://github.com/mity/acutest

Deleted ()

Я то тут причем? Я админа. Ц трогала очень давно, лет так 6 назад, если не больше.

По автоматизации тоже ничего сказать не могу. Автоматизировать серваки и тестирование кода - вещи разные.

Murg ★★★ ()
Ответ на: комментарий от Dron

то было ооооочень давно. Сейчас только питон да ансибл.

Murg ★★★ ()
Ответ на: комментарий от DELIRIUM

На самом деле во многих серьезных проектах (например, LLVM, Clang, WebKit) так и делают - все стоит на интеграционных и регрессионных тестах, а юнит-тестов относительно мало

annulen ★★★★★ ()
Ответ на: комментарий от Dron

брусок кода

У тебя там результат rand() делится целочисленно, а потом преобразуется во float.

Для начала, нужно убрать из кода все «случайности» или данные извне, которые могут меняться от запуска к запуску. Вместо них нужно подсунуть функции, которые всегда дают одинаковые результаты. Проще всего такой хак провернуть через макросы и прямое включение тестируемого исходника в программу-тест. То есть, в коде будет #include "neuro.c". А дальше — функция main, в которой ты зовёшь свои функции на заранее определённых значениях и проверяешь, что они возвращают ожидаемые результаты.

Запустить функцию и посмотреть в отладчике её результат это неправильный способ получения данных для теста.

i-rinat ★★★★★ ()
Ответ на: комментарий от Deleted

Valgrind/Sanitizers для С - обязательно

Не надо так резко! 99.99% софта без всяких валгриндов отдают на растерзание, и он выходит в stable репы дистрибутивов.

Вон, я в ЖЖшке писал, как намучился, исправляя косяки RTS2: авторы даже не подумали сделать тестовую компиляцию с -Wall -Werr -Wextra. В итоге достаточно много мелких косяков наделали, которые спокойно могли привести к UB. А еще там был жирный баг с выделением оперативы в размере нескольких экзабайт.

Если ты не знаешь, что такое RTS2, скажу: на этой штуке уже несколько десятков телескопов-роботов по всему миру работает! И ХЗ, как на них народ с сегфолтами справляется (возможно, просто не слишком часто эти сегфолты возникают, а возможно, «телескоп-робот» неусыпно контролируется «китайцем-роботом»).

anonymous ()
Ответ на: комментарий от annulen

это не от правильности такого подхода, а от безысходности. в большом проекте, еще и опенсорсном, т.е. таком, в который коммитит всякая пионерия, ревьюить юнит-тесты - получается слишком долго, а бить по результатам ревью по рукам - не в духе опенсорса. проще иметь хоть как-то отревьюенные интеграционные тесты, чем кучу непонятно что делающих юнит-тестов. в нормально организованном процессе, а не опенсорсном бардаке, количество интеграционных тестов стараются свести к минимуму.

anonymous ()

Не по теме, но. Представлять каждый нейрон и синапс как отдельный объект - громадный (громаднейший) оверхед для современных топологий, более того никак не оптимизируемый. Всё прекрасно ложится на матрицы и выражается ими.

yoghurt ★★★★★ ()

Теперь по теме. Классика - это Test-driven development by Example, от автора самой методики. Язык тут не очень важен, ибо сама по себе методика переносится прекрасно с джавы и хаскеля на си с емакс лиспом.

Опять же, TDD - не совсем то, что тебе нужно, это скорее некий экстремальный максимум. Важно понять, _как_ писать код, чтобы он был тестируем, чтобы отдельные элементы проекта были слабо связаны друг с другом и т.о. могли бы быть протестированы раздельно.

Разбираешься с методикой, вкуриваешь выбранный тестовый движок (GTest уже советовали, сам в довесок использовал CUnit, Boost.Test, самопалы) - и вперёд!

yoghurt ★★★★★ ()
Последнее исправление: yoghurt (всего исправлений: 1)
Ответ на: комментарий от yoghurt

во-первых чото ты чутка смешал теплое с мягким. во-вторых, даже я не до конца понял, каким образом ты смешал выбор размера модуля для юнит-тестирования и матрицы, а ТС, признавшийся в отсутствии и опыта и знаний - скорее всего не понял вообще ничего.

anonymous ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.