поддельные / фиктивные невиртуальные методы C ++ - PullRequest
21 голосов
/ 25 ноября 2010

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

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

Ответы [ 8 ]

11 голосов
/ 25 ноября 2010

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

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

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

7 голосов
/ 12 ноября 2015

Я перешел по ссылке 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), чтобы тест мог компилироваться и выполняться.Таким образом, вы можете смоделировать не виртуальные, статические, глобальные функции или почти любые зависимости без изменения производительного кода.Еще одна причина, по которой мне нравится этот подход, заключается в том, что все, что связано с тестом, находится в одном месте.Вам не нужно настраивать конфигурации компилятора и компоновщика тут и там.

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

7 голосов
/ 25 ноября 2010

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

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

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

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

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

1 голос
/ 08 февраля 2018

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

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

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

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

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

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

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

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

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

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

1 голос
/ 30 октября 2016

@ zaharpopov Вы можете использовать Typemock IsolatorPP для создания макетов не виртуального класса и методов без изменения вашего кода (или старого кода). например, если у вас есть не виртуальный класс с именем 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);

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

0 голосов
/ 25 ноября 2010

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

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

0 голосов
/ 25 ноября 2010

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

РЕДАКТИРОВАТЬ:

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


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 );
// ...
}

В тесте вы можете легко пройти фиктивный объект.

0 голосов
/ 25 ноября 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...