Как правильно использовать GTest для многократного повторения многопоточного теста?

Используя Google Test, я хочу проверить поведение метода ClientListener.AcceptRequest:

class ClientListener {
public:
    // Clients can call this method, want to test that it works
    Result AcceptRequest(const Request& request) {
        queue_.Add(request);
        ... blocks waiting for result ...
        return result;
    }
private:
    // Executed by the background_thread_;
    void ProcessRequestsInQueue() {
        while (true) {
            Process(queue_.PopEarliest());
        }
    }

    MyQueue queue_;
    std::thread background_thread_ = thread([this] {ProcessRequestsInQueue();});
};

Метод принимает запрос клиента, ставит его в очередь, блокирует ожидание результата, возвращает результат, когда он доступен.

Результат доступен, когда фоновый поток обрабатывает соответствующий запрос из очереди.

У меня есть тест, который выглядит следующим образом:

TEST(ListenerTest, TwoRequests) {
    ClientListener listener;
    Result r1 = listener.AcceptClientRequest(request1);
    Result r2 = listener.AcceptClientRequest(request2);
    ASSERT_EQ(r1, correctResultFor1);
    ASSERT_EQ(r2, correctResultFor2);
}

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

TEST_P(ListenerTest, TwoRequests) {
  ... same as before ...
}
INSTANTIATE_TEST_CASE_P(Instantiation, ListenerTest, Range(0, 100));

Но теперь команда make test рассматривает каждое параметризованное инстанцирование как отдельный тест, и в логах я вижу 100 тестов:

Test 1: Instantiation/ListenerTest.TwoRequests/1
Test 2: Instantiation/ListenerTest.TwoRequests/2
...
Test 100: Instantiation/ListenerTest.TwoRequests/100

Учитывая, что я не использую значение параметра, есть ли способ переписать код тестирования таким образом, чтобы команда make test регистрировала один тест, выполненный 100 раз, а не 100 тестов?


person mercury0114    schedule 19.06.2020    source источник
comment
На мой взгляд, у вас вообще не должно быть недетерминированных модульных тестов. Модульные тесты предназначены для тестирования вашей функциональности (в отличие от среды). Если вы чувствуете, что ваша логика, связанная с прослушивателем http, достаточно важна, чтобы вызывать модульные тесты, используйте фиктивный прослушиватель, который тест может полностью контролировать.   -  person 500 - Internal Server Error    schedule 19.06.2020
comment
@ 500-InternalServerError Но разве я не тестирую функциональность здесь? т.е. проверка того, что функция Listener.AcceptClientRequest возвращает правильный вывод с учетом ввода.   -  person mercury0114    schedule 19.06.2020
comment
Это не то, что традиционно называют модульным тестом. Модульные тесты проверяют отдельные блоки кода, а не системную интеграцию.   -  person 500 - Internal Server Error    schedule 19.06.2020
comment
Это сбивает с толку. Зачем вам изначально иметь недетерминированный код? Почему результат недетерминирован? Это не имеет смысла. Если под недетерминированным вы на самом деле подразумеваете, что он ведет себя по-разному в разных внешних условиях, то это не является недетерминированным, и вы должны просто воспроизвести/изобразить эти условия и проверить эти случаи. Или, может быть, под недетерминированным вы подразумеваете некоторый уровень случайности, тогда в этом случае вы можете запускать тест сотни раз, собирать результаты и выполнять некоторые статистические проверки.   -  person freakish    schedule 19.06.2020
comment
Тестируйте детерминированное поведение лучше. Например: вам действительно нужно проверить удостоверение личности?   -  person Louis Go    schedule 19.06.2020
comment
@LouisGo Заменил ID на что-то более значимое в вопросе. Но вообще у меня есть класс, использующий фоновый поток. Я хочу протестировать общедоступный метод этого класса. Учитывая, что фоновый поток может запуститься в неконтролируемый момент времени, общедоступный метод может иногда давать сбой (например, состояние гонки) и завершаться успехом в других случаях для одного и того же ввода. Как это правильно проверить?   -  person mercury0114    schedule 19.06.2020
comment
Если ваша проблема заключается в том, что ваш код не предназначен для многопоточности, тогда заставить googletest работать в одном потоке.   -  person Louis Go    schedule 19.06.2020
comment
@freakish Добавлено объяснение.   -  person mercury0114    schedule 19.06.2020
comment
Хм, поставить цикл for вокруг кода?   -  person rustyx    schedule 19.06.2020
comment
@rustyx да, это работает, мне было интересно, есть ли более элегантный способ без добавления дополнительного кода в тест   -  person mercury0114    schedule 19.06.2020


Ответы (1)


Простой ответ: используйте --gtest_repeat при выполнении тестов (по умолчанию 1).

Более длинный ответ: модульные тесты не должны использоваться для таких тестов. GTest является потокобезопасным по своей конструкции (как указано в их README), но это не значит, что это хороший инструмент для выполнения таких тестов. Возможно, это хорошая отправная точка, чтобы начать работать над реальными интеграционными тестами, я действительно рекомендую для этой цели фреймворк Python behave.

person Quarra    schedule 22.06.2020
comment
Можно ли добавить gtest_repeat в файл CMake? - person mercury0114; 22.06.2020
comment
Никогда не пробовал это, так что не уверен. Однако это конфигурация времени выполнения, как и --gtest_filter, поэтому я сомневаюсь в этом, но, возможно, CMake ее поддерживает. - person Quarra; 23.06.2020
comment
Даже если это так, я бы рекомендовал иметь одно задание CI, которое просто запускает все тесты один раз (т. е. без --gtest_repeat), и другое задание CI, которое запускает этот конкретный тест несколько раз (т. быть маленькими, и запускать их несколько раз не имеет особого смысла. Добавление специальных префиксов/суффиксов к тесту (например, BUGCHECK) может быть полезно для фильтрации тестов, которые необходимо запускать несколько раз. - person Quarra; 23.06.2020