Откуда загружать заглушки данных при модульном тестировании

StackOverflow https://stackoverflow.com//questions/23001887

Вопрос

Для целей модульного тестирования мне нужно смоделировать сетевой ответ.Ответ обычно представляет собой поток байтов, сохраненный в виде const vector<uint8_t>.Однако для модульного теста я хотел бы создать вектор с данными, которые либо жестко закодированы в файле CPP, либо считаны из файла в том же решении.Мой пример данных составляет около 6 КБ.Каковы общие рекомендации относительно того, куда размещать данные при использовании гуглтест?

Это было полезно?

Решение

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

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

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

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

#include <vector>
#include <fstream>
#include <stdexcept>
#include "gtest/gtest.h"

class foo_test : public ::testing::Test
{
protected:
    virtual void SetUp() {
        std::ifstream in("path/to/case_data");
        if (!in) {
            throw std::runtime_error("Could not open \"path/to/case_data\" for input");
        }
        _case_data.assign(
            std::istream_iterator<char>(in),std::istream_iterator<char>());
        if (_global_data.empty()) {
            std::ifstream in("path/to/global_data");
            if (!in) {
                throw std::runtime_error(
                    "Could not open \"path/to/global_data\" for input");
            }
            _global_data.assign(
                std::istream_iterator<char>(in),std::istream_iterator<char>());
        }
    }
    // virtual void TearDown() {}   
    std::vector<char> & case_data() {
        return _case_data;
    }
    static std::vector<char> const & global_data() {
        return _global_data;
    }

private:
    std::vector<char> _case_data;
    static std::vector<char> _global_data;

};

std::vector<char> foo_test::_global_data;

TEST_F(foo_test, CaseDataWipe) {
  EXPECT_GT(case_data().size(),0);
  case_data().resize(0);
  EXPECT_EQ(case_data().size(),0);
}

TEST_F(foo_test, CaseDataTrunc) {
  EXPECT_GT(case_data().size(),0);
  case_data().resize(1);
  EXPECT_EQ(case_data().size(),1);
}

TEST_F(foo_test, HaveGlobalData) {
  EXPECT_GT(global_data().size(),0);
}


int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

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

... Или закодировать это жестко?

Затем к вопросу о том, хранить ли тестовые данные вообще в файле или в жестком коде они в исходном коде теста. Читателям, которые довольны на данный момент, отныне будет только скучно.

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

Скажем, перестройка набора тестов для изменения этого элемента требует значительных затрат, и вы ожидаете, что содержимое элемента будет меняться в будущем независимо от связанного тестового кода.Или он может меняться независимо от соответствующего тестового кода для разных конфигураций тестируемой системы.В этом случае лучше всего получить элемент из файла или другого источника, который может быть выбран параметрами времени выполнения набора тестов.В googletest подкласс class ::testing::Environment является разработанным средством для параметризованного получения ресурсов набора тестов.

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

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

Я считаю совершенно желательным, чтобы, если файл тестовых данных является основным тестовым ресурсом, который набор тестов не может сгенерировать, то его содержимое лучше всего представляло собой текстовые данные, которые компетентный сопровождающий может легко понять и манипулировать.В этой настройке я бы, конечно, рассмотрел, что список шестнадцатеричных констант C / C ++, например, равен текстовые данные - это исходный код.Если тестовый файл содержит двоичные или устрашающе машинно-ориентированные данные тогда набор тестов лучше всего должен содержать средства его создания из разборчивых первичных ресурсов.Иногда набор тестов неизбежно зависит от "архетипичных" двоичных файлов, полученных извне, но они почти неизбежно влекут за собой мрачное зрелище инженеров-тестировщиков и багфиксаторов, седеющих на глазах у шестнадцатеричных редакторов.

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

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

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

Но обоим этим пунктам можно противопоставить замечание о том, что определяющее объявление BMT может быть закодировано в исходном файле само по себе.Это может быть политика проверки кода для набора тестов, которая проверяет инициализацию данных должен быть закодированным таким образом - и, возможно, в файлах, которые придерживаются особого соглашения об именовании .Фанатичная политика, конечно, но не более фанатичная, чем та, которая настаивает на том, что все инициализаторы тестовых данных должны быть извлечены из файлов.Если сопровождающий обязан проверять BMT в любом файле, содержащем его, не будет иметь значения, является ли расширение файла .cpp, .dat или что угодно:все дело в понятности "кода".

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

В случае googletest и сопоставимых функциональных фреймворков этому пункту можно в некоторой степени противостоять, обратившись к базовым классам полиморфных приспособлений например ::testing::Test и ::testing::Environment.Это облегчает разработчику тестов инкапсулирование получения тестовых ресурсов в тестовом примере инициализацию набора тестов, чтобы все было завершено либо успешно, либо при диагностированном сбое - до запуска составляющих тестов любого тестового набора.RAII может поддерживать беспроблемный разрыв между сбоями настройки и реальными сбоями.

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

С тех пор как исходные файлы может гигиенично инкапсулировать инициализацию тестовых данных для отслеживания версий их жесткое кодирование можно приравнять к извлечению из файла, при этом препроцессор выполняет извлечение как побочный продукт компиляции.В свете этого, зачем использовать другое оборудование с дополнительными обязательствами для его извлечения?Могут быть хорошие ответы, такие как предлагаемый XML-интерфейс с управлением тестированием, системы управления дефектами, но "Это тестовые данные, поэтому не программируйте их жестко" - не лучший вариант.

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

До сих пор я не оспаривал аргумент revision-tracking-hygeine для разделения инициализаторов тестовых данных на основе файлов.Я только что отметил , что обычные исходные файлы, в которых жестко запрограммированы инициализаторы, могут выполнять это разделение.И я не хочу опровергать этот аргумент, но Я хочу остановиться на фанатичном выводе о том, что инициализаторы тестовых данных в принципе всегда должны извлекаться из выделенных файлов - будь то исходные файлы или файлы данных.

Нет необходимости подробно излагать причины, по которым мы сопротивляемся этому выводу.Таким образом, лежит тестовый код, который локально Меньше понятнее, чем средний любитель пиццы программист мог бы писать и организовывать файлы набора тестов, которые растут ошеломляюще быстро, чем это необходимо или полезно для здоровья.Согласно нормативам, все основные ресурсы набора тестов представляют собой "какой-то код".A набор навыков программиста включает в себя умение разбивать код на файлы с соответствующей степенью детализации для обеспечения соответствующей последовательности отслеживания версий.Это не механическая процедура, это опыт, который должен охватывать анализ кода.Проверка кода может и должна гарантировать, что инициализации тестовых данных, однако они выполнены, хорошо спроектированы и проработаны с точки зрения отслеживания изменений так же, как и во всех других рутинных аспектах.

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

Другие советы

(предостережение - этот ответ универсален для любой системы тестирования подразделения)

Я предпочитаю держать файлы тестирования данных в качестве отдельных объектов в системе контроля ревизии.Это обеспечивает следующие преимущества:

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top