попробуйте поймать блоки с помощью boost.Test

У меня есть функция (член класса), которую я хочу избежать сбоя приложения из-за неоднозначности. Для этой цели я добавил блок try catch, как показано ниже:

void getGene(unsigned int position){
     T val;
     try {
        val =  _genome.at(_isCircular ? position % _genome.size() : position);
     }
     catch (std::exception& e) {
         std::cerr << "Error in [" << __PRETTY_FUNCTION__ << "]: " 
                   << e.what()  << std::endl;
         exit(1);
    }
    return val;
}

Теперь я хочу добавить модульный тест Boost, который я думал сделать что-то вроде

BOOST_AUTO_TEST_CASE(nonCircularGenome_test){

   // set size to 10
   test.setSize(10);
   // set non circular
   test.setNonCircular();    

   // gene at site # 12 does not exist in a 10-site long genome, must throw an exception
   BOOST_CHECK_THROW(test.getGene(12), std::out_of_range);

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

Каков наилучший способ заставить обе эти вещи работать, чтобы пользователю было предложено исправить ошибку на ходу, а тесты явно проверяли при отладке? Одним из способов является использование блоков #ifdef/#endif DEBUG, но я хочу избежать макросов препроцессора.

Заранее спасибо,

Нихил


person Nikhil J Joshi    schedule 17.06.2013    source источник


Ответы (1)


Вы, кажется, неправильно понимаете объем и цель исключений - и, возможно, обработки ошибок в целом.

Прежде всего, вы должны определить, каковы предварительные условия вашей функции: всегда ли getGene() ожидает, что position будет допустимым? Ожидает ли он, что его клиенты никогда не будут предоставлять недействительные позиции?

Если это так, то клиент, предоставляющий недопустимую позицию (даже если клиент является тестовой подпрограммой), нарушает контракт с getGene() (в частности, он нарушает его предварительное условие), а нарушение контракта — это неопределенное поведение. по определению. Вы не можете протестировать неопределенное поведение, поэтому вам следует удалить свой тест.

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

Одна из возможностей — повторно выдать исключение после регистрации диагностики:

T getGene(unsigned int position){
    T val;
    try {
       val =  _genome.at(_isCircular ? position % _genome.size() : position);
    }
    catch (std::exception& e) {
        std::cerr << "Error in [" << __PRETTY_FUNCTION__ << "]: " 
                  << e.what()  << std::endl;

        throw;
//      ^^^^^
    }
    return val;
}

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

T getGene(unsigned int position){
    return _genome.at(_isCircular ? position % _genome.size() : position);
}
person Andy Prowl    schedule 17.06.2013
comment
+1, но некоторые подробности... вы можете иногда тестировать неопределенное поведение, это зависит от того, какое неопределенное поведение. В терминах Lakos вы можете тестировать мягкое неопределенное поведение (т. е. внеконтрактные обращения к вашей библиотеке), но не жесткое неопределенное поведение (т. е. что-то, что уже вызвало неопределенное поведение в библиотека и/или язык). На самом деле есть предложение (n3604), чтобы добавить некоторую поддержку этого на уровне языка, и BSL имеет библиотеку реализация на уровне. - person David Rodríguez - dribeas; 17.06.2013
comment
@DavidRodríguez-dribeas: Должен признаться, я читал это интересное предложение слишком давно, чтобы точно помнить его содержание. Теперь я немного озадачен, поэтому я спрошу вас, если вы не возражаете: если нарушение предварительного условия является неопределенным поведением, то вызов этой функции путем нарушения этого предварительного условия вызовет неопределенное поведение, и это означает, что наша тестовое утверждение не может ожидать последовательного результата. Я неправильно понимаю это? Или наш тест должен выполняться в контексте, где то, что обычно является неопределенным поведением, теперь является четко определенным поведением (например, создание исключения)? - person Andy Prowl; 17.06.2013
comment
Дело в том, что в вашей функции вы можете проверить предварительные условия до того, как ваша функция сама вызовет неопределенное поведение где-то еще с помощью одного из макросов assert. Структура в Bloomberg/BSL и предложение позволяют настроить то, что происходит, когда утверждение запускается из main (ну, во всяком случае, из одной точки кода). Теперь в наборе тестов (вы можете проверить тесты BSL) вы можете настроить обработчик утверждений как уникальное исключение, и таким образом вы можете добавить тесты, которые проверяют, что ваш код способен обнаруживать UB на собственный интерфейс [...] - person David Rodríguez - dribeas; 17.06.2013
comment
[...] это само по себе может быть использовано в тестах клиентского кода, чтобы сделать ваш код более надежным. Интерфейс по-прежнему имеет неопределенное поведение, поскольку разные утверждения могут различаться в разных режимах сборки, и эффект срабатывания утверждения также может быть изменен даже во время выполнения (для тех проверок, которые не удаляются во время компиляции). Например, у вас может быть функция, которая, если библиотека собрана с некоторыми флагами, выдаст исключение, а если она собрана с другими флагами, вообще не проверяет и вызывает жесткое неопределенное поведение в другом месте. - person David Rodríguez - dribeas; 17.06.2013
comment
Подводя итог: тот факт, что интерфейс является узким и имеет неопределенное поведение при вызове вне контракта, не означает, что вы не можете проверить входные данные до того, как функция сама вызовет UB, или что вам нужно расширить контракт на обеспечивая четко определенное поведение для всех возможных входных данных. - person David Rodríguez - dribeas; 17.06.2013
comment
@DavidRodríguez-dribeas: Хорошо, понятно. Спасибо, что разъяснили :) - person Andy Prowl; 17.06.2013
comment
@AndyProwl: Большое спасибо за ваши комментарии. Я понимаю вашу точку зрения о разрыве контракта. Моя главная проблема заключается в том, что позиция вообще может быть любым (целым) числом, поскольку вектор может быть круговым. Однако, если вектор не круговой, я не хочу, чтобы пользователь наступал на позицию за пределами. Для этой цели я перешел от оператора [] к методу .at() вектора и хотел передать пользователю ошибку, выдаваемую vector::at() осмысленным образом. - person Nikhil J Joshi; 18.06.2013
comment
Я думаю, что вернусь к своим циклам if() для явных проверок. Ваши предложения не работают для меня, так как распространение ошибки приводит к сбою запуска, и пользователь будет сбит с толку, что пошло не так. Спасибо, кстати - Нихил - person Nikhil J Joshi; 18.06.2013