Возможно ли в Qt для модульного тестирования (доступа) частных методов?

Я пишу модульные тесты для своего приложения, и теперь я наткнулся на класс, в котором я должен тестировать закрытые методы. Это может быть результатом плохого дизайна конкретного класса, но я должен это сделать. Есть ли способ в Qt вызывать частные методы, возможно, используя QMetaObject или что-то подобное?

Для модульного тестирования я использую фреймворк QTestLib.


person xx77aBs    schedule 12.09.2011    source источник
comment
Просто отметим, что обычно тестируются только общедоступные методы. Частные методы — это деталь реализации: lostechies.com/ chadmyers/2008/11/21/do-not-test-private-methods   -  person kroonwijk    schedule 13.09.2011
comment
kroonwijk: Спасибо за комментарий :) Я это знаю, но этот класс наследует QThread, поэтому только публичные методы — это конструктор и start(). И вещи, которые делает tread, являются частными методами. Теперь, когда я думаю об этом, я должен был сделать что-то другое... Но вот оно. Мой вопрос остается в силе :)   -  person xx77aBs    schedule 13.09.2011
comment
В идеале вы не должны создавать подклассы QThread (stackoverflow.com/questions/ 3911086/qthread-threaded-rly/). Инкапсулируйте свою логику обработки в подклассе QObject, создайте его экземпляр и переместите его в экземпляр QThread. Это также может помочь при модульном тестировании.   -  person Arnold Spence    schedule 13.09.2011
comment
Спасибо ;) В будущем я буду использовать эту методологию при использовании потоков ... Я думаю, что я читал, что я должен создать подкласс QThread в какой-то книге. Кроме того, в документации QT говорится, что для создания собственных потоков создайте подкласс QThread и повторно реализуйте run().   -  person xx77aBs    schedule 13.09.2011
comment
Да, документация для Qt действительно предполагает это, и внутри Trolltech очень медленно продвигается его удаление или, по крайней мере, обновление :)   -  person Arnold Spence    schedule 13.09.2011
comment
Вот лучший ответ: stackoverflow.com/questions/14186245/   -  person LinWM    schedule 08.01.2019


Ответы (5)


Правильный (читай раздражающий) ответ заключается в том, что вам не следует тестировать частные методы, это детали реализации ;-).

OTOH - вы думали об условном объявлении их защищенными/приватными в зависимости от того, находитесь ли вы в тестировании или нет, а затем расширяете? Я использовал это, чтобы вытащить меня из подобного зажима в прошлом.

#ifdef TESTING
// or maybe even public!
#define ACCESS protected
#else
#define ACCESS private
#endif

/* your class here */
class Foo {
ACCESS
    int fooeyness;
}

// or better yet, place this in a different file!
#ifdef TESTING
/*
    class which extends the tested class which has public accessors
*/
#endif
person cwallenpoole    schedule 12.09.2011
comment
Это интересный способ решить проблему :) Я все еще ищу другой (специфический для qt) способ доступа к закрытым методам. Но если не найду, воспользуюсь этим :) Спасибо ;) - person xx77aBs; 13.09.2011
comment
Или еще грязнее: #define private public перед тем, как вы `#включите заголовок. - person larsmoa; 13.09.2011
comment
@larsm хахаха, это меня немного пугает - person xx77aBs; 13.09.2011
comment
@larsm ... Это все еще зло спустя три года. Просто подумал, что укажу на это. Шутки в сторону. Это заставляет меня съеживаться, просто думая об этом. - person cwallenpoole; 20.08.2014

Решение 1, быстрое и простое: сделайте тестовый класс(ы) другом общедоступного.

class Foo {
   // ...
private:
   friend class FooTest;
};

Таким образом, ваш класс FooTest может получить доступ ко всем членам открытого класса. Однако таким образом вам нужно изменять исходный класс каждый раз, когда вы хотите получить доступ к закрытым данным из другого теста, и вы пропускаете информацию о тестах в общедоступный API, и вы, возможно, открываете конфликты имен классов (что, если есть / другой/класс FooTest вокруг?), и так далее.


Решение 2, также выполненное правильно: не помещайте частные методы в открытый класс, а создайте частный класс с общедоступными методами.

class Foo {
    // 
private:
    friend class FooPrivate;
    FooPrivate *d;
};

FooPrivate объявляется в своем собственном заголовке, который может быть не установлен, или оставаться в подкаталоге include-privates/ или что-то еще, т. е. он не мешает нормальному использованию. Таким образом, публичный класс остается чистым.

class FooPrivate {
public:
    // only public stuff in here;
    // and especially this:
    static FooPrivate *get(Foo *f) { return f->d; }
};

Затем тест включает закрытый заголовок и вызывает FooPrivate::get(fooObj) для получения экземпляра закрытого класса, а затем успешно использует его.

person peppe    schedule 04.04.2014
comment
Хорошая идея, но если FooPrivate не установлен, программа не сможет скомпилироваться. Как насчет того, чтобы использовать Решение 1, но обернуть его в IFDEF, которые включаются только во время выполнения тестов? - person DBedrenko; 22.06.2016
comment
Конечно. Но, как я уже сказал, FooPrivate можно установить в какой-нибудь include/private подкаталог. Или его можно установить только в том случае, если вы запустите команду configure, чтобы включить тесты... - person peppe; 22.06.2016
comment
Теперь я понимаю, что вы имеете в виду, но я не уверен, какое это преимущество перед очень похожим (и более простым методом) переноса friend class TestFoo в IFDEF, который определен, если проект настроен для включения тестов. - person DBedrenko; 22.06.2016

Я не согласен с менталитетом "частные члены - это детали реализации", который, на мой взгляд, примерно переводится как "тестировать только часть вашего кода".

Я могу относиться к аргументу «единицы есть единицы», но почему бы не попытаться покрыть как можно большую часть вашего кода тестами, даже внутри единиц? Ака. тщательное ректальное исследование ваших устройств.

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

  1. Всегда объявляйте элементы, которые вы хотите протестировать, как protected вместо private в своем коде.
  2. Подкласс вашего класса в тестовом коде и просто создайте нужные вам селекторы или просто напишите тестовый код непосредственно как члены в реализации этого подкласса.

ПРИМЕЧАНИЕ. Вы должны быть осторожны с классами, которые полагаются на сложные шаблоны создания экземпляров, чтобы убедиться, что вы создаете их правильно (читай: вызов конструктора исходного класса из подкласса ctor).

person Lennart Rolland    schedule 17.07.2016

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

Foo a;

Затем мы можем использовать QMetaObject::invokeMethod для вызова любого слота, который есть у этого метода (открытого или закрытого). Итак, если мы хотим вызвать метод Test, мы можем сделать это так:

QMetaObject::invokeMethod(&a, "Test", Qt::DirectConnection);

Кроме того, у нас может быть возвращаемое значение и отправка аргументов... На самом деле все ответы здесь: http://doc.qt.nokia.com/stable/qmetaobject.html#invokeMethod

person xx77aBs    schedule 13.09.2011
comment
Также можно пометить приватные методы с помощью Q_INVOKABLE вместо того, чтобы делать их слотами. - person Ignitor; 12.11.2014
comment
Разве это не делает метод общедоступным? - person syockit; 18.05.2019

Я когда-то читал, что модульный тест должен тестировать общедоступный интерфейс класса, а не защищенный/частный материал.

Ваш класс должен просто вести себя правильно снаружи. Если стратегия реализации изменится, ваш класс Unit Test останется прежним.

person nyarlathotep108    schedule 04.04.2014
comment
Но если вы тестируете графический интерфейс, приватный интерфейс становится общедоступным для человека, использующего графический интерфейс. - person Chuck Claunch; 11.10.2017