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

Модульное тестирование С++. Как проверить частных участников?

Я хотел бы сделать модульные тесты для моего приложения на С++.

Какова правильная форма для проверки частных членов класса? Сделать класс друга, который будет проверять частных членов, использовать производный класс или какой-либо другой трюк?

Какой метод использует API тестирования?

4b9b3361

Ответ 1

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

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

Один из способов разрешить доступ модульных тестов к непубличным членам через конструкцию .

Ответ 2

Ответ на этот вопрос затрагивает многие другие темы. Помимо любой религиозности в CleanCode, TDD и других:

Существует несколько способов доступа к закрытым членам. В любом случае вам нужно отменить проверенный код! Это возможно на обоих уровнях синтаксического анализа С++ (препроцессор и сам язык):

Определить все для общедоступных

Используя препроцессор, вы можете разбить инкапсуляцию.

#define private public
#define protected public
#define class struct

Недостатком является то, что класс доставленного кода не совпадает с классом теста! В стандарте С++ в главе 9.2.13 говорится:

Порядок распределения нестатических данных с разными контроль доступа не указан.

Это означает, что компилятор имеет право переупорядочить переменные-члены и виртуальные функции для теста. Вы можете бороться, что это не повредит вашим классам, если переполнение буфера не произойдет, но это означает, что вы не будете тестировать тот же код, который вы доставляете. Это означает, что если вы обращаетесь к членам объекта, который был инициализирован кодом, скомпилирован с private, не определенным в public, смещение вашего члена может отличаться!

Друзья

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

class X
{
private:
    friend class Test_X;
};

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

Объявление конфиденциальных вещей, защищенных и полученных из класса для тестов

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

class X
{
protected:
    int myPrivate;
};

class Test_X: public X
{
    // Now you can access the myPrivate member.
};

Любой другой способ с макросами

Работает, но имеет те же недостатки в стандартном соответствии, что и первый способ. например:.

class X
{
#ifndef UNITTEST
private:
#endif
};

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


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

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

Классы становятся сложными! Тогда вы должны решить разделить их или взять их такими, какими они есть. Иногда второе решение имеет больше смысла. В конце концов, это всегда вопрос о том, какие цели вы хотите достичь (например, идеальный дизайн, быстрое время регистрации, низкие затраты на разработку...).


Мое мнение

Мое решение для доступа к закрытым членам выглядит следующим образом:

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

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

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

Ответ 3

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

В заголовке кода, который я хочу проверить (stylesheet.h), у меня есть:

#ifndef TEST_FRIENDS
#define TEST_FRIENDS
#endif

class Stylesheet {
TEST_FRIENDS;
public:
    // ...
private:
    // ...
};

и в тесте у меня есть:

#include <gtest/gtest.h>

#define TEST_FRIENDS \
    friend class StylesheetTest_ParseSingleClause_Test; \
    friend class StylesheetTest_ParseMultipleClauses_Test;

#include "stylesheet.h"

TEST(StylesheetTest, ParseSingleClause) {
    // can use private members of class Stylesheet here.
}

Вы всегда добавляете новую строку в TEST_FRIENDS, если вы добавляете новый тест, который обращается к частным членам. Преимущества этого метода заключаются в том, что он довольно ненавязчив в тестируемом коде, поскольку вы добавляете только несколько #defines, которые не влияют на тестирование. Недостатком является то, что в тестах это немного многословно.

Теперь одно слово о том, почему вы хотели бы это сделать. В идеале, конечно, у вас есть небольшие классы с четко определенными обязанностями, а классы имеют легко проверяемые интерфейсы. Однако на практике это не всегда легко. Если вы пишете библиотеку, то что private и public продиктовано тем, что вы хотите, чтобы потребитель библиотеки мог использовать (ваш открытый API), а не тем, что нужно для тестирования или нет. У вас могут быть инварианты, которые вряд ли будут меняться, и их нужно протестировать, но они не представляют интереса для вашего API. Тогда, черного ящика-тестирования API недостаточно. Также, если вы столкнулись с ошибками и напишите дополнительные тесты для предотвращения регрессий, может потребоваться протестировать материал private.

Ответ 4

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

Существует несколько подходов к пониманию того, что вам нужно проверить, что ваши личные методы делают то, что они говорят на жестяной банке. Классы друзей - худшие из них; они связывают тест с реализацией тестируемого класса способом, который является prima facie хрупким. В некоторой степени лучше вливание зависимостей: создание атрибутов класса зависимостей частных методов, которые могут содержать тестовые версии, позволяющие тестировать частные методы через открытый интерфейс. Лучше всего извлечь класс, который инкапсулирует поведение, которое ваши частные методы используют в качестве своего открытого интерфейса, а затем тестирует новый класс, как обычно.

Подробнее см. Очистить код.

Ответ 5

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

// In testable.hpp:
#if defined UNIT_TESTING
#   define ACCESSIBLE_FROM_TESTS : public
#   define CONCRETE virtual
#else
#   define ACCESSIBLE_FROM_TESTS
#   define CONCRETE
#endif

Затем в коде:

#include "testable.hpp"

class MyClass {
...
private ACCESSIBLE_FROM_TESTS:
    int someTestablePrivateMethod(int param);

private:
    // Stuff we don't want the unit tests to see...
    int someNonTestablePrivateMethod();

    class Impl;
    boost::scoped_ptr<Impl> _impl;
}

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

Ответ 6

В С++ есть простое решение, использующее #define. Просто оберните включение своего "ClassUnderTest" следующим образом:

#define protected public
 #define private   public
    #include <ClassUnderTest.hpp>
 #undef protected
#undef private

[Кредит относится к этой статье и RonFox] [1]

Ответ 7

Иногда требуется проверять частные методы. Тестирование можно выполнить, добавив FRIEND_TEST в класс.

// Production code
// prod.h

#include "gtest/gtest_prod.h"
...   

class ProdCode 
    {
     private:
      FRIEND_TEST(ProdTest, IsFooReturnZero);
      int Foo(void* x);
    };

//Test.cpp
// TestCode
...
TEST(ProdTest, IsFooReturnZero) 
{
  ProdCode ProdObj;
  EXPECT_EQ(0, ProdObj.Foo(NULL)); //Testing private member function Foo()

}

Ответ 8

Я бы предпочел добавить параметр -Dprivate = public в Makefile unit-test, избегая изменять что-либо в моих оригинальных проектах