it-swarm.com.ru

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

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

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

Какую технику используют API тестирования?

36
Daniel Saad

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

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

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

36
Mr Fooz

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

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

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

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

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

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

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

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

Друзья

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

class X
{
private:
    friend class Test_X;
};

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

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

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

class X
{
protected:
    int myPrivate;
};

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

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

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

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

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


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

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

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


Мое мнение

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

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

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

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

22
Stefan Weiser

Я сам не нашел золотого решения, но вы можете использовать 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.

19
jdm

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

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

Для более подробной информации, обратитесь к Чистый код .

4
darch

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

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

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

3
mbells

Иногда требуется тестировать частные методы. Тестирование можно выполнить, добавив 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()

}
1
Syam Sanal

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

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

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

0
Langley