Как вы создаете тестовые объекты для стороннего устаревшего кода - PullRequest
4 голосов
/ 07 января 2009

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

Я пытаюсь написать тестовый код без изменения этих базовых классов. Тем не менее, есть проблемы с созданием значимого теста объекты из-за отсутствия интерфейсов:

//ACommonClass.h
#include "globalthermonuclearwar.h" //which contains deep #include dependencies...
#include "tictactoe.h" //...and need to exist at compile time to get into test...

class Something //which may or may not inherit from another class similar to this...
{
public:
  virtual void fxn1(void);  //which often calls into many other classes, similar to this
  //...
  int data1;  //will be the only thing I can test against, but is often meaningless without fxn1 implemented
  //...
};

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

В настоящее время я создал отдельный файл, который содержит поддельные реализации для функций, которые определены в сторонних поставляемых заголовках базового класса на основе необходимости знать, как было описано в книге «Работа с устаревшим кодом» .

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

//SomethingRequiredImplementations.cpp
#include "ACommonClass.h"
void CGlobalThermoNuclearWar::Simulate(void) {};  // fake this and all other required functions...
// fake implementations for otherwise undefined functions in globalthermonuclearwar.h's #include files...
void Something::fxn1(void) { data1 = blah(); } //test specific functionality.

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

Обратите внимание, что все рассматриваемые кодовые базы написаны на C ++.

Ответы [ 5 ]

1 голос
/ 22 апреля 2009

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

Что я хотел бы сделать на этом этапе, так это взять внутренности другого фальшивого фреймворка и использовать его внутри моих поддельных объектов. Это выглядело бы примерно так:

Production.h

class ConcreteProductionClass { // regular everyday class
protected:
    ConcreteProductionClass(); // I've found the 0 arg constructor useful
public:
    void regularFunction(); // regular function that I want to mock
}

Mock.h

class MockProductionClass 
    : public ConcreteProductionClass
    , public ClassThatLetsMeSetExpectations 
{
    friend class ConcreteProductionClass;
    MockTypes membersNeededToSetExpectations;
public:
    MockClass() : ConcreteProductionClass() {}
}

ConcreteProductionClass::regularFunction() {
    membersNeededToSetExpectations.PassOrFailTheTest();
}

ProductionCode.cpp

void doSomething(ConcreteProductionClass c) {
    c.regularFunction();
}

test.cpp

TEST(myTest) {
    MockProductionClass m;
    m.SetExpectationsAndReturnValues();
    doSomething(m);
    ASSERT(m.verify());
}

Самая болезненная часть всего этого заключается в том, что другие фиктивные фреймворки так близки к этому, но не делают этого точно, а макросы настолько замысловаты, что адаптировать их нетривиально. Я начал изучать это в свободное время, но оно не продвигается очень быстро. Даже если бы мой метод работал так, как я хочу, и имел код установки ожиданий, у этого метода все еще есть пара недостатков, один из которых заключается в том, что ваши команды сборки могут быть довольно длинными, если вам придется связываться с много .o файлов, а не один .a, но это управляемо. Также невозможно перейти к реализации по умолчанию, так как мы не связываем ее. В любом случае, я знаю, что это не отвечает на вопрос или даже не говорит вам ничего, чего вы еще не знаете, но показывает, насколько близко сообщество C ++ к возможности имитировать классы, которые не имеют чисто виртуального интерфейса.

1 голос
/ 07 января 2009

Поддельные объекты подходят для такого рода задач. Они позволяют имитировать существование других компонентов без необходимости их присутствия. Вы просто определяете ожидаемый ввод и вывод в своих тестах.

Google имеет хороший фреймворк для C ++.

0 голосов
/ 08 января 2009

Одна вещь, которую вы не указали в своем вопросе, это причина, по которой ваши классы основаны на базовых классах другого подразделения. Являются ли отношения действительно IS-A отношениями?

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

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

0 голосов
/ 07 января 2009

Если у меня есть тестируемый класс, который происходит от чего-то, что я не могу (или не хочу) тестировать, я:

  1. Создать новый класс только для логики.
  2. Переместить тест code-i-wanna-test в класс логики.
  3. Используйте интерфейс для общения с реальным классом, чтобы взаимодействовать с базовым классом и / или вещами, которые я не могу или не буду вставлять в логику.
  4. Определить тестовый класс, используя тот же интерфейс. В этом тестовом классе не может быть ничего, кроме noops или причудливого кода, имитирующего реальные классы.

Если у меня есть класс, который мне просто нужно использовать при тестировании, но использование реального класса является проблемой (зависимости или нежелательное поведение):

  1. Я определю новый интерфейс, который выглядит как все открытые методы, которые мне нужно вызывать.
  2. Я создам макетную версию объекта, которая поддерживает этот интерфейс для тестирования.
  3. Я создам еще один класс, построенный на «реальной» версии этого класса. Он также поддерживает этот интерфейс. Все вызовы интерфейса перенаправляются в методы реальных объектов.
  4. Я сделаю это только для методов, которые я на самом деле вызываю, а не для ВСЕХ открытых методов. Я добавлю к этим классам, когда напишу больше тестов.

Например, я обертываю классы GDI MFC таким образом, чтобы протестировать код рисования Windows GDI. Шаблоны могут облегчить это, но мы часто не делаем этого по разным техническим причинам (например, экспорт классов Windows DLL ...).

Я уверен, что все это есть в книге Перса Работа с Legacy Code - и то, что я описываю, имеет реальные термины. Только не заставляй меня тянуть книгу с полки ...

0 голосов
/ 07 января 2009

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

...