Используйте C++ catch Framework для проверки утверждения assert

Можно ли использовать C++ CATCH рамки для проверки того, что assert В заявлении правильно указана неверная предпосылка?

// Source code
void loadDataFile(FILE* input) {
  assert(input != NULL);
  ...
}

// Test code
TEST_CASE("loadDataFile asserts out when passed NULL", "[loadDataFile]") {
   loadDataFile(NULL)
   // Now what do I look for?
}

3 ответа

Решение

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

Некоторые платформы с открытым исходным кодом, такие как BDE и Boost, имеют свой собственный макрос ASSERT, который можно настроить при запуске приложения так, чтобы он вел себя не так, как C assert. Например, вы можете указать, что сбойный ASSERT генерирует исключение, а затем вы можете использовать утверждение CQU REQUIRE_THROWS(), чтобы убедиться, что ваш код принудительно использует контракт, отличный от NULL FILE-дескриптора.

Пример BDE

#include <bsls_assert.h>

void loadDataFile(FILE* input) {
  BSLS_ASSERT_OPT(input != NULL);
  ...
}

TEST_CASE("loadDataFile asserts out when passed NULL", "[loadDataFile]") {
   // Opt-in to the 'throw exception on assert failure' handler
   // just for this test case.
   bsls::AssertFailureHandlerGuard guard(&bsls::Assert::failThrow);
   REQUIRE_THROWS_AS(loadDataFile(NULL), bsls::AssertFailedException);
}

Пример повышения

#include <boost/assert.hpp>

void loadDataFile(FILE* input) {
  BOOST_ASSERT(input != NULL);
  ...
}

namespace boost {
void assertion_failed(char const * expr, char const * function, char const * file, long line) {
    throw std::runtime_error("Assertion Failed"); // TODO: use expr, function, file, line
}
}

TEST_CASE("loadDataFile asserts out when passed NULL", "[loadDataFile]") {
   REQUIRE_THROWS(loadDataFile(NULL));
   // Now what do I look for?
}

Вы можете свернуть свой собственный макрос assert(). Это заново изобретать колесо - см. Примеры выше.

Вы можете изменить свой код, чтобы вместо этого выдавать исключение std::invalid_argument():

  void loadDataFile(FILE* input) {
    if (input == NULL) {
      throw std::invalid_argument("input file descriptor cannot be NULL");
    }
    ...
  }

Вы можете проверить, что ваш код обеспечивает его контракт с:

REQUIRE_THROWS_AS(loadDataFile(NULL), std::invalid_argument);

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

Наконец, если вы действительно этого хотите, вы можете изменить интерфейс вашего кода, чтобы показать ошибку контракта:

enum LoadDataFile_Result {
  LDF_Success,
  LDF_InputIsNull,
  ...
};

LoadDataFile_Result loadDataFile(FILE* input) {
  if (input == NULL) {
    // bail out early for contract failure
    return LDF_InputIsNull;
  }
  // input is non-NULL
  ...
  return LDF_Success;
}

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

Вы можете быть заинтересованы в Google Test Framework. Он может отлавливать аварийное завершение программы с помощью:

ASSERT_DEATH(statement, regex);
ASSERT_DEATH_IF_SUPPORTED(statement, regex);
ASSERT_EXIT(statement, predicate, regex);

EXPECT_DEATH(statement, regex);
EXPECT_DEATH_IF_SUPPORTED(statement, regex);
EXPECT_EXIT(statement, predicate, regex);

regex соответствует текст на stderrpredicate соответствует коду выхода программы.

Я подозреваю, что это работает, разлагая тестовую программу перед утверждением.

документация здесь:

https://github.com/google/googletest/blob/master/googletest/docs/advanced.md

Если вы можете имитировать функции C, вы можете временно изменить ошибку утверждения на исключение.

Например, при использовании Hippomocks ваш тестовый пример будет выглядеть примерно так:

      #ifndef NDEBUG
TEST_CASE("loadDataFile asserts out when passed NULL", "[loadDataFile]") {
    MockRepository mocks;
    mocks.OnCallFunc(__assert_fail).Throw(nullptr);

    CHECK_THROWS_AS(loadDataFile(NULL), std::nullptr_t);
}
#endif

Переносимость может быть проблемой, я тестировал это только в Linux с glibc и musl.

Другие вопросы по тегам