LINUX.ORG.RU

Тестирование в С++, как проверить правильность указанных свойств?

 ,


0

1

Допустим есть такой код с ошибкой:

struct Cmd {
	Operation op = Operation::...;
	double subtractor = 0.;
	double multipler = 0.;
};

void randomModifSubtractor(Cmd& cmd) {
	double& v = cmd.multipler; // <- ЭТО ОШИБКА, д.б. subtractor
	v = static_cast<double>(rand()) / RAND_MAX; // для демо сойдет
}

void randomModifCmd(Cmd& cmd) {
	if (rand() % 2 == 0)
		randomModifSubtractor(cmd);
	else
		randomModifMultipler(cmd);
}

В данном коде ошибка в подмене названия. Значения полей уже инициализированно рандомно, и здесь оно меняется на новое. У каждого из полей свои правила модификации, и в реальности сложней. Эта ошибка отчасти ухудшает работу программы, но не ломает. А какова д.б. нормальная работа заранее не известно.

Как может тестироваться такая ошибка? Или может быть можно по другому написать этот код, что бы ошибка тестировалась или минимизировалась ее вероятность?

В тесте для randomModifSubtractor проверять неизменность других полей.

RussianWarShip
()

Как может тестироваться такая ошибка?

Никак. Всё что тебе предложат - тупо продублировать этот код ещё раз, но уже в качестве типа «теста» и сравнивать результаты. Мероприятие, очевидно, бесполезное.

минимизировалась ее вероятность?

Не давать писать код нубам. После написания кода спустя день/неделю/месяц внимательно всё пересматривать и трассировать в уме.

firkax ★★★★★
()

У каждого из полей свои правила модификации, и в реальности сложней.

Если есть случаи, которые можно посчитать вручную, то в тесте ставишь старый subtractor, запускаешь randomModifSubtractor, сверяешь новый subtractor c ожидаемым.

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

monk ★★★★★
()

Замокай в тесте рандом, сравни результаты с известными значениями функции.

theNamelessOne ★★★★★
()

заменяешь реальную Random монаду на тестовую, которая поставляет известные значения. то же самое с другими нечистыми монадами, поставляющими данные. в итоге останется проверять только чистую логику

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

caryoscelus
()

написали жеж. типизация на то и нужна чтобы в частности проверять присваивания. это ее прямая обязанность.

делаешь типы навроде AddOp, MulOp, DivOp и так далее по фантазии и степени детализации. тогда ты просто не сможешь переменной типа AddOp приcвоить значение типа MulOp.

alysnix ★★★
()

Никак. Если действительно

Эта ошибка отчасти ухудшает работу программы, но не ломает. А какова д.б. нормальная работа заранее не известно.

то это классический случай деградации. Ошибки такого типа невозможно выявить с помощью какого то одного суперкрутого теста, для этого нужно чтобы у модуля была хорошо спроектированная система unit testing с возможностью проведения тестов на регрессию. Например (это реальный случай) вот есть код управления графом, 50+ тыс строк реализации математических формул (которые довольно трудно держать в уме), и где то он дергает функцию сравнения unsigned long long int, в котором кто то случайно год назад сделал опечатку и заменил > на >=. Все тесты проходят нормально, так как ошибок нет, код идет в продакшн, но при достижении определенного порога числа вершин графа в очень определенных случаях код начинает немного ошибаться, и подхватывать те вершины графа, которые не нужны. В итоге система не падает, но начинает со временем работать туповато, но тесты все проходит , и причину проблемы невозможно найти. Сам Эйнштейн не написал бы один тест для такого случая. Такие ошибки вытряхиваются только при комплексных тестах, написанных квалифицированными специалистами. Либо надо пользоваться другими методами, как предложено выше @firkax

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

В итоге система не падает, но начинает со временем работать туповато, но тесты все проходит , и причину проблемы невозможно найти.

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

firkax: >Не давать писать код нубам.

Самому себе не запретишь, как ни старайся. У меня по крайней мере не получается.

victor79
() автор топика

Раздели cmd на две разные структуры. И функции будут принимать два разных типа. Это ошибка головы и рук, либо ты этого не делаешь, либо делаешь так что-бы это тупо не работало.

А так, «я печатаю в stderr вместо stdout как мне исправить эту «ошибку»?» =))

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

Раздели cmd на две разные структуры. И функции будут принимать два разных типа.

Это то же самое, что strong types.

Я например такое иногда использую (или вариации):

template <class TT, class Tag>
struct UniqValT {

    explicit UniqValT() {}

    explicit UniqValT(TT val) : pValue(val + 1) {
        assert( val < std::numeric_limits<TT>::max() );
    }

    bool operator < (const UniqValT& o) const { return pValue < o.pValue; }
    bool operator == (const UniqValT& o) const { return pValue == o.pValue; }

    explicit operator bool() const { return has(); }

    TT value() const { return pValue-1; }

    bool has() const { return !empty(); }
    bool empty() const { return *this == UniqValT(); }

private:
    TT pValue = 0;
};

а после пишется:

struct Size : public UniqValT<uint, struct Tag_Size> {};

и использование:

void doSomething2(Size sz) {}
void doSomething1(Size sz) { doSomething2(sz); }

...
doSomething1(Size(5));

Числовые значения тогда уже не туда не отправить. Но попытка приделать это к каждой переменной приведет к бесконечной доводке и оптимизации тестирования.

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

Ну, да. Но я в своих скромных поделках на сишке так как постоянно то там то тут указатели и подобная «проблема» может выскочить где угодно ибо void* дохрена привык если что сеть на жопку и методично просканировть всё глазками. Хотя в случае ТС конечно сложно, не прям в этом конкретном, а вообще ибо может же быть просто функции принимающие double напрямую и если он не те переменные не тем функциям будет давать то будут расчёты, но как у той секретарши печатающей 1500 знаков в минуту. А если подобным стронг туподефом :D всё фигачить то этих уникальных типов будет столько что запутаешься нахрен. Всё же подобные логические ошибки это тупо баги их надо в первую очередь не дупускать руками и головой, а то на пустом почти месте городится переусложнение кода ИМХО конечно.

Но тут тред про плюсы, а я в них никакой, да есть у вас поболе вариантов обхода таких полупроблем.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от victor79

Да, это только со временем приходит, никто не имеет врожденных способностей к тестированию, их можно только постепенно развивать. Для данного конкретного случая посмотри на C++ template traits и там еще есть Concepts, они помогают проверять типы на этапе компиляции, например

template <typename CMD,
          typename = std::enable_if_t<std::is_base_of_v<ISubstractor, CMD>> >
void  randomModifSubtractor(CMD const & m)
{
   ...
}   

при таком подходе код даже тестировать не надо - он просто не соберется если CMD не будет унаследован от ISubstractor. Данный пример примитивный, там очень много других вариантов задания условий, можно в литературе посмотреть.

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

spring
()
Последнее исправление: spring (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.