Подтвердить что ты не робот

Поддельные/макетные невиртуальные С++-методы

Известно, что в С++ mocking/faking не виртуальные методы для тестирования сложны. Например, имеет два предложения - оба означают изменение исходного исходного кода в некотором роде (шаблонизация и переписывание как интерфейс).

Кажется, это очень плохая проблема для кода на С++. Как можно сделать лучше, если вы не можете изменить исходный код, который нужно подделать/издеваться? Дублирование всего кода/класса (с его иерархией всего базового класса?)

4b9b3361

Ответ 1

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

Тогда тестовое устройство может предоставить свои собственные реализации; эффективно используя компоновщик для выполнения грязной работы для нас.

В некоторых кругах это называется Link Seam".

Ответ 2

Я перешел по ссылке Link Seam из ответа sdg. Там я читал о различных типах швов, но больше всего меня поразила предварительная обработка швов. Это заставило меня задуматься о дальнейшей эксплуатации препроцессора. Оказалось, что можно макетировать любую внешнюю зависимость, фактически не изменяя вызывающий код.

Чтобы сделать это, вы должны скомпилировать исходный файл вызова с определением замещающей зависимости. Вот пример, как это сделать.

dependency.h

#ifndef DEPENDENCY_H
#define DEPENDENCY_H

class Dependency
{
public:
    //...
    int foo();
    //...
};

#endif // DEPENDENCY_H

caller.cpp

#include "dependency.h"

int bar(Dependency& dependency)
{
    return dependency.foo() * 2;
}

test.cpp

#include <assert.h>

// block original definition
#define DEPENDENCY_H

// substitute definition
class Dependency
{
public:
    int foo() { return 21; }
};

// include code under test
#include "caller.cpp"

// the test
void test_bar()
{
    Dependency mockDependency;

    int r = bar(mockDependency);

    assert(r == 42);
}

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

Я успешно применил эту технику в реальном проекте с большими жировыми зависимостями. Я описал это более подробно в Включить макет.

Ответ 3

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

Не виртуальные вызовы, не зависящие от параметра шаблона, представляют собой ту же проблему, что и методы final и static в Java [*] - проверенный код явно сказал: "Я хочу вызвать этот код, а не некоторый неизвестный бит кода, который каким-то образом зависит от аргумента". Вы, тестировщик, хотите, чтобы он вызывал различный код под тестом из того, что он обычно вызывает. Если вы не можете изменить тестируемый код, вы, тестер, потеряете этот аргумент. Вы также можете спросить, как ввести тестовую версию строки 4 10-строчной функции без изменения тестируемого кода.

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

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

[*] Знание моей Java довольно маломощное. Может быть какой-то умный способ издеваться над такими методами в Java, которые не применимы к С++. Если да, пожалуйста, не обращайте на них внимания, чтобы увидеть аналогию; -)

Ответ 4

@zaharpopov вы можете использовать Typemock IsolatorPP для создания mocks не виртуального класса и методов без изменения вашего кода (или устаревшего кода). например, если у вас есть не виртуальный класс с именем MyClass:

class MyClass
{
 public:
   int GetResult() { return -1; }
}

вы можете издеваться над ним с помощью typemock:

MyClass* fakeMyClass = FAKE<MyClass>();
WHEN_CALLED(fakeMyClass->GetResult()).Return(10);

Кстати, классы или методы, которые вы хотите проверить, также могут быть private, поскольку typemock также может их издеваться над ними, например:

class MyClass
{
private:
   int PrivateMethod() { return -1; }
}


MyClass* myClass =  new MyClass();

PRIVATE_WHEN_CALLED(myClass, PrivateMethod).Return(1);

для получения дополнительной информации перейдите здесь.

Ответ 5

Я думаю, что сейчас невозможно сделать это со стандартным C++ (но давайте надеяться, что мощное отражение времени компиляции скоро придет к C++...). Однако для этого есть несколько вариантов.

Вы могли бы взглянуть на Injector++. Это Windows только сейчас, но планирует добавить поддержку Linux & Mac.

Другой вариант - CppFreeMock, который, похоже, работает с GCC, но в последнее время не работает.

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

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

Существует C-Mock, который является расширением для Google Mock, позволяя имитировать не виртуальные функции, переопределяя их и полагаясь на тот факт, что оригинальные функции находятся в динамических библиотеках. Он ограничен платформой GNU/Linux.

Наконец, вы также можете попробовать PowerFake (для которого я являюсь автором), как указано здесь.

Это не фальшивый фреймворк (в настоящее время), и он предоставляет возможность замены производственных функций на тестовые. Я надеюсь, что смогу интегрировать его в одну или несколько насмешливых структур; если нет, он станет одним. ** Обновление: ** Интеграция с FakeIt.

Он также переопределяет исходную функцию во время компоновки (поэтому она не будет работать, если функция вызывается в той же единице перевода, в которой она определена), но использует другой трюк, чем C-Mock, так как использует GNU ld --wrap вариант. Он также требует некоторых изменений в вашей системе сборки для тестов, но никак не влияет на основной код (кроме случаев, когда вы вынуждены поместить функцию в отдельный файл .cpp); но поддерживается его простая интеграция в проекты CMake.

Но в настоящее время он ограничен GCC/GNU ld (работает также с MinGW).

Ответ 6

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

Без изменения этого источника вы все равно можете (для обычных операционных систем/инструментов) предварительно загружать объект, который определяет его собственную версию функции (ов), которую вы хотите перехватить. После этого они могут даже вызвать оригинальные функции. Я привел пример для этого в (моем) вопросе Хороших Linux-инструментов мониторинга TCP/IP, которым не нужен root-доступ?.

Ответ 7

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

О, по интерфейсу я имею в виду struct с только чистыми виртуальными методами. Больше ничего!

Ответ 8

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

ИЗМЕНИТЬ:

Объекту, который вы передаете конструктору, нужен интерфейс, и этот класс хранит только ссылку на интерфейс.


struct Abase
{
  virtual ~Abase(){}
  virtual void foo() = 0;
};

struct Aimp : public Abase
{
  virtual ~Aimp(){}
  virtual void foo(){/*do stuff*/}
};

struct B
{
  B( Aimp &objA ) : obja( objA )
  {
  }

  void boo()
  { 
    objA.foo();
  }

  Aimp &obja;
};


int main()
{
//...
Aimp realObjA;
B objB( realObjA );
// ...
}

В тесте вы можете легко передать макет объекта.