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

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

2 ответа

Решение

Возможно (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();
}

В случае (a) вы также можете рассмотреть возможность получения данных в глобальной функции- члене SetUp путем создания подклассов ::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-интерфейс с системами управления тестами и дефектами, но "Это тестовые данные, поэтому не кодируйте их жестко", не очень хороший.

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

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

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

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

(Предостережение - этот ответ является общим для любой среды модульного тестирования)

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

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

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

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