Qt: Как организовать Unit Test с несколькими классами?
У меня есть проект Qt Unit test (sub), который генерирует мне один класс (с основным сгенерированным QTEST_APPLESS_MAIN
). Я могу запустить это из Qt Creator в качестве консольного приложения.
В: Как добавить дополнительные классы в качестве тестовых примеров для этого конкретного проекта.
- Если в этих классах есть только "тестовые" слоты (
private Q_SLOTS
), методы не вызывают, а только те, что относятся к классу с QTEST_APPLESS_MAIN
- Так как может быть только один
main(..)
, я не могу использовать QTEST_APPLESS_MAIN
с более чем одним классом в проекте (это правильно?)
- Конечно, я могу вручную "проложить" слоты в (дополнительных) классах с одним классом, содержащим
main
, но это очень утомительно.
Итак, каков наилучший способ запустить Unit test по нескольким классам в проекте Unit test?
PS:
В "Использование тестов QT Unit в проекте - конфликтующие основные функции (...)" a Блог упоминается, однако я не могу загрузить zip, описывающий решение.
![Qt Unit Test subproject]()
Ответы
Ответ 1
В соответствии с решением, с которым вы связались, способ тестирования двух (или более) классов в рамках одного проекта Qt unit test заключается в обеспечении того, чтобы каждый тестируемый класс имел соответствующий тестовый класс и что вы создал пользовательский int main
, который выполняет каждый тестовый класс.
Например:
class TestClassA : public QObject
{
Q_OBJECT
public:
TestClassA();
...
private Q_SLOTS:
void testCase1();
...
};
class TestClassB : public QObject
{
Q_OBJECT
public:
TestClassB();
...
private Q_SLOTS:
void testCase2();
...
};
void TestClassA::testCase1()
{
// Define test here.
}
void TestClassB::testCase2()
{
// Define test here.
}
// Additional tests defined here.
// Note: This is equivalent to QTEST_APPLESS_MAIN for multiple test classes.
int main(int argc, char** argv)
{
int status = 0;
{
TestClassA tc;
status |= QTest::qExec(&tc, argc, argv);
}
{
TestClassB tc;
status |= QTest::qExec(&tc, argc, argv);
}
return status;
}
Очевидно, что различные тестовые классы могут быть распределены по нескольким единицам перевода, а затем просто включены в блок перевода с вашим int main
. Не забудьте включить соответствующие файлы .moc
.
Ответ 2
Основываясь на принятом ответе, и если вы используете С++ 11, вас может заинтересовать решение с использованием лямбда-выражений. Это позволяет избежать написания одного и того же кода каждый раз. Хотя вы можете заменить лямбду функцией, я думаю, что лямбда чище.
#include <QtTest>
#include "test1.h"
#include "test2.h"
int main(int argc, char** argv)
{
int status = 0;
auto ASSERT_TEST = [&status, argc, argv](QObject* obj) {
status |= QTest::qExec(obj, argc, argv);
delete obj;
};
ASSERT_TEST(new Test1());
ASSERT_TEST(new Test2());
return status;
}
#ifndef TEST1_H
#define TEST1_H
Образец теста
#include <QtTest>
class Test1 : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testCase1();
};
Ответ 3
В поисках того же ответа я нашел очень хорошее решение из http://qtcreator.blogspot.de/2009/10/running-multiple-unit-tests.html. Он создает пространство имен с контейнером, в котором регистрируются все созданные тесты (с помощью макроса DECLARE_TEST), а затем использует его для запуска всех тестов в списке. Я переписал его, чтобы он соответствовал моему коду, и выкладываю свою версию здесь (версия My Qt Creator: 4.1.0):
/* BASED ON
* http://qtcreator.blogspot.de/2009/10/running-multiple-unit-tests.html
*/
#ifndef TESTCOLLECTOR_H
#define TESTCOLLECTOR_H
#include <QtTest>
#include <memory>
#include <map>
#include <string>
namespace TestCollector{
typedef std::map<std::string, std::shared_ptr<QObject> > TestList;
inline TestList& GetTestList()
{
static TestList list;
return list;
}
inline int RunAllTests(int argc, char **argv) {
int result = 0;
for (const auto&i:GetTestList()) {
result += QTest::qExec(i.second.get(), argc, argv);
}
return result;
}
template <class T>
class UnitTestClass {
public:
UnitTestClass(const std::string& pTestName) {
auto& testList = TestCollector::GetTestList();
if (0==testList.count(pTestName)) {
testList.insert(std::make_pair(pTestName, std::make_shared<T>()));
}
}
};
}
#define ADD_TEST(className) static TestCollector::UnitTestClass<className> \
test(#className);
#endif // TESTCOLLECTOR_H
Затем просто добавьте строку ADD_TEST (class) в заголовок теста следующим образом:
#ifndef TESTRANDOMENGINES_H
#define TESTRANDOMENGINES_H
#include <QtTest>
#include "TestCollector.h"
class TestRandomEngines : public QObject
{
Q_OBJECT
private Q_SLOTS:
void test1();
};
ADD_TEST(TestRandomEngines)
#endif // TESTRANDOMENGINES_H
И чтобы запустить все тесты, просто выполните:
#include "TestCollector.h"
#include <iostream>
int main(int argc, char *argv[]) {
auto nFailedTests = TestCollector::RunAllTests(argc, argv);
std::cout << "Total number of failed tests: "
<< nFailedTests << std::endl;
return nFailedTests;
}
Ответ 4
Как я это делаю:
- Создайте общий проект "subdirs".
- Поместите тестируемый код в подпроект библиотеки С++.
- Вместо использования проекта unit test я использую подпроект консольного приложения.
- Свяжите библиотеку с этим консольным приложением, не забудьте обработать зависимости в файле .pro в верхней части иерархии.
- В этом подпроекте консоли укажите столько классов тестов, сколько пожелаете, и запустите их в основном из этого же проекта.
Я в основном сделал небольшое изменение этот пост.
Ответ 5
Я использую следующий код для сбора всех результатов теста:
#include "testclassa.h"
#include "testclassb.h"
#include <QtTest>
#include <QDebug>
int main(int argc, char** argv){
int failedTests = 0;
TestClassA testClassA
TestClassB testClassB
failedTests += QTest::qExec(&testClassA, argc, argv);
failedTests += QTest::qExec(&testClassB, argc, argv);
if(failedTests > 0){
qDebug() << "total number of failed tests: " << failedTests;
}else{
qDebug() << "all tests passed :)";
}
return failedTests;
}