如何使用 Catch 为我的 Qt 应用程序进行第一次测试?
How can I make my first test with Catch for my Qt application?
我正在尝试 learn/use 第一次在 Qt 应用程序上捕获 (https://github.com/catchorg/Catch2)。
我正在尝试按照 Catch 初始页面 (https://github.com/catchorg/Catch2/blob/devel/docs/tutorial.md#top) 上提供的教程进行操作。
上述教程的第一行说 理想情况下 我应该通过其“CMake 集成”(https://github.com/catchorg/Catch2/blob/devel/docs/cmake-integration.md#top) 使用 Catch2。我忠实地走着“理想”的道路。
在“CMake 集成”页面的第二段我开始迷路了:如果你不需要自定义主函数,你应该...
我需要自定义主函数吗?为什么有人需要一个?一个人没有它怎么活?我完全不知道,文本既没有解释任何这些也没有提供任何一种合理的默认方向(如果你不知道我们在说什么就假装你...或类似的东西)。
我试图忽略它并继续。
在第三段(根据请求在下面转载)中显示了一段代码,reader 知道 它应该足以完成 代码块。什么是做一段代码?我应该将这段代码包含在一些预先存在的文件中吗?哪个文件?在所述文件的哪一部分?或者我应该用建议的内容创建一个新文件吗?哪个文件?我应该把它放在哪里?
This means that if Catch2 has been installed on the system, it should
be enough to do
> find_package(Catch2 3 REQUIRED)
> # These tests can use the Catch2-provided main add_executable(tests test.cpp) target_link_libraries(tests PRIVATE Catch2::Catch2WithMain)
>
> # These tests need their own main add_executable(custom-main-tests test.cpp test-main.cpp) target_link_libraries(custom-main-tests
> PRIVATE Catch2::Catch2)
有人可以提供一个在 Qt 项目中简单使用 Catch2 的工作示例吗?最好是桌面应用程序?
2022-01-14更新:
这是我尝试实现类似于 Catch 教程 (https://github.com/catchorg/Catch2/blob/v2.x/docs/tutorial.md#writing-tests) 中第一个示例的最小 Qt + Catch2 集成的看法。
我创建了一个名为 QtCatch 的 Qt Widget 应用程序。这是它的文件结构:
.
├── CMakeLists.txt
├── include
│ ├── calculator.cpp
│ └── calculator.h
├── main.cpp
├── mainwindow.cpp
├── mainwindow.h
├── mainwindow.ui
└── tests
├── CMakeLists.txt
├── main.cpp
└── tst_qtcatchtest.cpp
我把所有文件内容收录在下面以供参考。
此文件结构是通过 Qt“新建项目”对话框创建的。主项目是“Application (Qt) > Qt Widgets Application”,测试子项目是“Other Project >> Auto Test Project”
我的 Qt 应用程序运行没有问题。
如果我尝试编译测试子项目或主项目,取消注释主 CMakeLists.txt 文件中的“add_subdirectory(tests)”行,我会得到同样的错误:
undefined reference to Calculator::Calculator()
尽管
#include "../include/calculator.h"
tst_qtcatchtest.cpp
行
如何让这个简单的 Catch2 测试用例在 Qt 6 中工作?
CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project(QtCatch VERSION 0.1 LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED)
# Manually added
#add_subdirectory(tests)
set(PROJECT_SOURCES
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
include/calculator.h include/calculator.cpp
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(QtCatch
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET QtCatch APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
if(ANDROID)
add_library(QtCatch SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(QtCatch
${PROJECT_SOURCES}
)
endif()
endif()
target_link_libraries(QtCatch PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
set_target_properties(QtCatch PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
)
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(QtCatch)
endif()
main.cpp:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_factorialPushButton_clicked();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "include/calculator.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_factorialPushButton_clicked()
{
Calculator aCalc;
int factorial = aCalc.Factorial(ui->numberLineEdit->text().toInt());
QString result = QString("Result: %1").arg(factorial);
ui->resultLabel->setText(result);
}
mainwindow.ui:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>214</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="numberLabel">
<property name="text">
<string>Number</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="numberLineEdit"/>
</item>
<item>
<widget class="QPushButton" name="factorialPushButton">
<property name="text">
<string>Calculate Factorial</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="resultLabel">
<property name="text">
<string>Result</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
include/calculator.h:
#ifndef CALCULATOR_H
#define CALCULATOR_H
class Calculator
{
public:
Calculator();
int Factorial( int number );
};
#endif // CALCULATOR_H
include/calculator.cpp:
#include "calculator.h"
Calculator::Calculator()
{
}
int Calculator::Factorial( int number )
{
return number <= 1 ? 1 : Factorial( number - 1 ) * number;
}
tests/CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project(QtCatch VERSION 0.1 LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED)
# Manually added
add_subdirectory(tests)
set(PROJECT_SOURCES
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
include/calculator.h include/calculator.cpp
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(QtCatch
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET QtCatch APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
if(ANDROID)
add_library(QtCatch SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(QtCatch
${PROJECT_SOURCES}
)
endif()
endif()
target_link_libraries(QtCatch PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
set_target_properties(QtCatch PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
)
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(QtCatch)
endif()
tests/main.cpp:
#define CATCH_CONFIG_RUNNER
#include <catch2/catch.hpp>
#include <QtGui/QGuiApplication>
int main(int argc, char** argv)
{
QGuiApplication app(argc, argv);
return Catch::Session().run(argc, argv);
}
tests/tst_qtcatchtest.cpp:
#include <catch2/catch.hpp>
#include "../include/calculator.h"
TEST_CASE( "Factorial of 0 is 1 (fail)", "[qt]" ) {
Calculator aCalc;
REQUIRE( aCalc.Factorial(0) == 1 );
}
TEST_CASE( "Factorials of 1 and higher are computed (pass)", "[qt]" ) {
Calculator aCalc;
REQUIRE( aCalc.Factorial(1) == 1 );
REQUIRE( aCalc.Factorial(2) == 2 );
REQUIRE( aCalc.Factorial(3) == 6 );
REQUIRE( aCalc.Factorial(10) == 3628800 );
}
你的意思是你连要不要自定义主函数都不知道?!当然,开个玩笑,读起来很有趣,我同意这可以更清楚一点。不过,我对Catch2和CMake比较熟悉,所以我现在排除一切疑虑!
Catch2 测试需要程序的 main
函数中的少量代码,以将命令行参数传递给它的实现并启动 运行 您的测试用例。因此,为方便起见,它提供了一个默认的 main 函数来为您执行此操作,这通常就足够了。 Their own documentation 给出了一些示例,说明您可以如何提供自己的 main 来更改命令行的解析。另一种情况可能是您使用的外部库需要一些全局设置 and/or 清理。
所以是的,您确实需要一个或多个单独的可执行文件来进行测试,第三段显示了此类可执行文件的基本 CMake 设置。 CMake 的主题过于宽泛,无法在这个答案中涵盖,但我通常使用像这样的相当标准的目录布局:
|- build/ // all compilation output
|- src/
| |- // project sources
| |- CMakeLists.txt
|- tests/
| |- test.cpp
| |- CMakeLists.txt
|- CMakeLists.txt
根CMakeLists.txt可用于全局定义,并添加有自己的CMake文件的子目录,例如:
cmake_minimum_required(VERSION 3.5)
project(baz LANGUAGES CXX VERSION 0.0.1)
find_package(Qt5 CONFIG REQUIRED COMPONENTS Core Gui)
add_subdirectory(src)
add_subdirectory(tests)
测试目标需要链接到与应用程序可执行文件本身相同的对象,因此最简单的配置是将源代码分为库和可执行目标。示例 src/CMakeLists.txt:
set(CMAKE_AUTOMOC ON)
set(lib_SRC
foo.cpp
bar.cpp
// sources excluding main.cpp
)
add_library(foo_lib STATIC ${lib_SRC})
target_link_libraries(foo_lib Qt5::Core)
add_executable(foo main.cpp)
target_link_libraries(foo foo_lib)
请注意,将库设为 STATIC 目标是最简单的解决方案,因为创建共享 Qt 库涉及 additional steps。
然后 tests/CMakeLists.txt 将使用 Catch2 文档中的命令:
set(CMAKE_AUTOMOC OFF)
find_package(Catch2 3 REQUIRED)
add_executable(test test.cpp)
target_link_libraries(test PRIVATE foo_lib Catch2::Catch2WithMain)
include(CTest)
include(Catch)
catch_discover_tests()
在这里禁用全局CMAKE_AUTOMOC是避免重复元对象编译的最简单方法。这会导致链接器错误,因为它已经为 foo_lib.
完成了
另请参阅 ,了解如何扩展此设置以有条件地编译测试的示例,以便您可以默认禁用它们,但为自己或自动构建测试系统启用它们。
这里我包含了我的问题的最终解决方案以及必要的 Qt 咒语以供将来参考。
请注意,我声称没有此解决方案的作者身份,因为它源自@sigma 的
├── CMakeLists.txt
├── include
│ ├── calculator.cpp
│ └── calculator.h
├── main.cpp
├── mainwindow.cpp
├── mainwindow.h
├── mainwindow.ui
└── tests
├── CMakeLists.txt
├── main.cpp
└── tst_qtcatchtest.cpp
这是最终文件版本。
CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(QtCatch VERSION 0.1 LANGUAGES CXX)
enable_testing()
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED)
if(CMAKE_TESTING_ENABLED)
add_subdirectory(tests)
endif()
set(LIB_SOURCES
mainwindow.cpp
mainwindow.h
mainwindow.ui
include/calculator.h include/calculator.cpp
)
add_library(QtCatchLib STATIC ${LIB_SOURCES})
target_link_libraries(QtCatchLib PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
add_executable(QtCatch main.cpp)
target_link_libraries(QtCatch PRIVATE QtCatchLib)
target_link_libraries(QtCatch PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
include/calculator.cpp
#include "calculator.h"
Calculator::Calculator()
{
}
int Calculator::Factorial( int number )
{
return number <= 1 ? 1 : Factorial( number - 1 ) * number;
}
include/calculator.h
#ifndef CALCULATOR_H
#define CALCULATOR_H
class Calculator
{
public:
Calculator();
int Factorial( int number );
};
#endif // CALCULATOR_H
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.cpp
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "include/calculator.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_factorialPushButton_clicked()
{
Calculator aCalc;
int factorial = aCalc.Factorial(ui->numberLineEdit->text().toInt());
QString result = QString("Result: %1").arg(factorial);
ui->resultLabel->setText(result);
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_factorialPushButton_clicked();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>214</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="numberLabel">
<property name="text">
<string>Number</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="numberLineEdit"/>
</item>
<item>
<widget class="QPushButton" name="factorialPushButton">
<property name="text">
<string>Calculate Factorial</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="resultLabel">
<property name="text">
<string>Result</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
tests/CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(QtCatchTest LANGUAGES CXX)
SET(CMAKE_CXX_STANDARD 11)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Gui REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Gui REQUIRED)
add_executable(QtCatchTest tst_qtcatchtest.cpp main.cpp)
target_link_libraries(QtCatchTest PRIVATE Qt${QT_VERSION_MAJOR}::Gui)
target_link_libraries(QtCatchTest PRIVATE QtCatchLib)
tests/main.cpp
#define CATCH_CONFIG_RUNNER
#include <catch2/catch.hpp>
#include <QtGui/QGuiApplication>
int main(int argc, char** argv)
{
QGuiApplication app(argc, argv);
return Catch::Session().run(argc, argv);
}
tests/tst_qtcatchtest.cpp
#include <catch2/catch.hpp>
#include "../include/calculator.h"
TEST_CASE( "Factorial of 0 is 1 (fail)", "[qt]" ) {
Calculator *aCalc = new Calculator();
REQUIRE( aCalc->Factorial(0) == 1 );
}
TEST_CASE( "Factorials of 1 and higher are computed (pass)", "[qt]" ) {
Calculator aCalc;
REQUIRE( aCalc.Factorial(1) == 1 );
REQUIRE( aCalc.Factorial(2) == 2 );
REQUIRE( aCalc.Factorial(3) == 6 );
REQUIRE( aCalc.Factorial(10) == 3628800 );
}
我正在尝试 learn/use 第一次在 Qt 应用程序上捕获 (https://github.com/catchorg/Catch2)。
我正在尝试按照 Catch 初始页面 (https://github.com/catchorg/Catch2/blob/devel/docs/tutorial.md#top) 上提供的教程进行操作。
上述教程的第一行说 理想情况下 我应该通过其“CMake 集成”(https://github.com/catchorg/Catch2/blob/devel/docs/cmake-integration.md#top) 使用 Catch2。我忠实地走着“理想”的道路。
在“CMake 集成”页面的第二段我开始迷路了:如果你不需要自定义主函数,你应该...
我需要自定义主函数吗?为什么有人需要一个?一个人没有它怎么活?我完全不知道,文本既没有解释任何这些也没有提供任何一种合理的默认方向(如果你不知道我们在说什么就假装你...或类似的东西)。
我试图忽略它并继续。
在第三段(根据请求在下面转载)中显示了一段代码,reader 知道 它应该足以完成 代码块。什么是做一段代码?我应该将这段代码包含在一些预先存在的文件中吗?哪个文件?在所述文件的哪一部分?或者我应该用建议的内容创建一个新文件吗?哪个文件?我应该把它放在哪里?
This means that if Catch2 has been installed on the system, it should be enough to do
> find_package(Catch2 3 REQUIRED)
> # These tests can use the Catch2-provided main add_executable(tests test.cpp) target_link_libraries(tests PRIVATE Catch2::Catch2WithMain)
>
> # These tests need their own main add_executable(custom-main-tests test.cpp test-main.cpp) target_link_libraries(custom-main-tests
> PRIVATE Catch2::Catch2)
有人可以提供一个在 Qt 项目中简单使用 Catch2 的工作示例吗?最好是桌面应用程序?
2022-01-14更新:
这是我尝试实现类似于 Catch 教程 (https://github.com/catchorg/Catch2/blob/v2.x/docs/tutorial.md#writing-tests) 中第一个示例的最小 Qt + Catch2 集成的看法。
我创建了一个名为 QtCatch 的 Qt Widget 应用程序。这是它的文件结构:
.
├── CMakeLists.txt
├── include
│ ├── calculator.cpp
│ └── calculator.h
├── main.cpp
├── mainwindow.cpp
├── mainwindow.h
├── mainwindow.ui
└── tests
├── CMakeLists.txt
├── main.cpp
└── tst_qtcatchtest.cpp
我把所有文件内容收录在下面以供参考。
此文件结构是通过 Qt“新建项目”对话框创建的。主项目是“Application (Qt) > Qt Widgets Application”,测试子项目是“Other Project >> Auto Test Project”
我的 Qt 应用程序运行没有问题。
如果我尝试编译测试子项目或主项目,取消注释主 CMakeLists.txt 文件中的“add_subdirectory(tests)”行,我会得到同样的错误:
undefined reference to Calculator::Calculator()
尽管
#include "../include/calculator.h"
tst_qtcatchtest.cpp
行如何让这个简单的 Catch2 测试用例在 Qt 6 中工作?
CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project(QtCatch VERSION 0.1 LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED)
# Manually added
#add_subdirectory(tests)
set(PROJECT_SOURCES
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
include/calculator.h include/calculator.cpp
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(QtCatch
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET QtCatch APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
if(ANDROID)
add_library(QtCatch SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(QtCatch
${PROJECT_SOURCES}
)
endif()
endif()
target_link_libraries(QtCatch PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
set_target_properties(QtCatch PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
)
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(QtCatch)
endif()
main.cpp:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_factorialPushButton_clicked();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "include/calculator.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_factorialPushButton_clicked()
{
Calculator aCalc;
int factorial = aCalc.Factorial(ui->numberLineEdit->text().toInt());
QString result = QString("Result: %1").arg(factorial);
ui->resultLabel->setText(result);
}
mainwindow.ui:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>214</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="numberLabel">
<property name="text">
<string>Number</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="numberLineEdit"/>
</item>
<item>
<widget class="QPushButton" name="factorialPushButton">
<property name="text">
<string>Calculate Factorial</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="resultLabel">
<property name="text">
<string>Result</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
include/calculator.h:
#ifndef CALCULATOR_H
#define CALCULATOR_H
class Calculator
{
public:
Calculator();
int Factorial( int number );
};
#endif // CALCULATOR_H
include/calculator.cpp:
#include "calculator.h"
Calculator::Calculator()
{
}
int Calculator::Factorial( int number )
{
return number <= 1 ? 1 : Factorial( number - 1 ) * number;
}
tests/CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project(QtCatch VERSION 0.1 LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED)
# Manually added
add_subdirectory(tests)
set(PROJECT_SOURCES
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
include/calculator.h include/calculator.cpp
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(QtCatch
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET QtCatch APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
if(ANDROID)
add_library(QtCatch SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(QtCatch
${PROJECT_SOURCES}
)
endif()
endif()
target_link_libraries(QtCatch PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
set_target_properties(QtCatch PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
)
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(QtCatch)
endif()
tests/main.cpp:
#define CATCH_CONFIG_RUNNER
#include <catch2/catch.hpp>
#include <QtGui/QGuiApplication>
int main(int argc, char** argv)
{
QGuiApplication app(argc, argv);
return Catch::Session().run(argc, argv);
}
tests/tst_qtcatchtest.cpp:
#include <catch2/catch.hpp>
#include "../include/calculator.h"
TEST_CASE( "Factorial of 0 is 1 (fail)", "[qt]" ) {
Calculator aCalc;
REQUIRE( aCalc.Factorial(0) == 1 );
}
TEST_CASE( "Factorials of 1 and higher are computed (pass)", "[qt]" ) {
Calculator aCalc;
REQUIRE( aCalc.Factorial(1) == 1 );
REQUIRE( aCalc.Factorial(2) == 2 );
REQUIRE( aCalc.Factorial(3) == 6 );
REQUIRE( aCalc.Factorial(10) == 3628800 );
}
你的意思是你连要不要自定义主函数都不知道?!当然,开个玩笑,读起来很有趣,我同意这可以更清楚一点。不过,我对Catch2和CMake比较熟悉,所以我现在排除一切疑虑!
Catch2 测试需要程序的 main
函数中的少量代码,以将命令行参数传递给它的实现并启动 运行 您的测试用例。因此,为方便起见,它提供了一个默认的 main 函数来为您执行此操作,这通常就足够了。 Their own documentation 给出了一些示例,说明您可以如何提供自己的 main 来更改命令行的解析。另一种情况可能是您使用的外部库需要一些全局设置 and/or 清理。
所以是的,您确实需要一个或多个单独的可执行文件来进行测试,第三段显示了此类可执行文件的基本 CMake 设置。 CMake 的主题过于宽泛,无法在这个答案中涵盖,但我通常使用像这样的相当标准的目录布局:
|- build/ // all compilation output
|- src/
| |- // project sources
| |- CMakeLists.txt
|- tests/
| |- test.cpp
| |- CMakeLists.txt
|- CMakeLists.txt
根CMakeLists.txt可用于全局定义,并添加有自己的CMake文件的子目录,例如:
cmake_minimum_required(VERSION 3.5)
project(baz LANGUAGES CXX VERSION 0.0.1)
find_package(Qt5 CONFIG REQUIRED COMPONENTS Core Gui)
add_subdirectory(src)
add_subdirectory(tests)
测试目标需要链接到与应用程序可执行文件本身相同的对象,因此最简单的配置是将源代码分为库和可执行目标。示例 src/CMakeLists.txt:
set(CMAKE_AUTOMOC ON)
set(lib_SRC
foo.cpp
bar.cpp
// sources excluding main.cpp
)
add_library(foo_lib STATIC ${lib_SRC})
target_link_libraries(foo_lib Qt5::Core)
add_executable(foo main.cpp)
target_link_libraries(foo foo_lib)
请注意,将库设为 STATIC 目标是最简单的解决方案,因为创建共享 Qt 库涉及 additional steps。
然后 tests/CMakeLists.txt 将使用 Catch2 文档中的命令:
set(CMAKE_AUTOMOC OFF)
find_package(Catch2 3 REQUIRED)
add_executable(test test.cpp)
target_link_libraries(test PRIVATE foo_lib Catch2::Catch2WithMain)
include(CTest)
include(Catch)
catch_discover_tests()
在这里禁用全局CMAKE_AUTOMOC是避免重复元对象编译的最简单方法。这会导致链接器错误,因为它已经为 foo_lib.
完成了另请参阅
这里我包含了我的问题的最终解决方案以及必要的 Qt 咒语以供将来参考。
请注意,我声称没有此解决方案的作者身份,因为它源自@sigma 的
├── CMakeLists.txt
├── include
│ ├── calculator.cpp
│ └── calculator.h
├── main.cpp
├── mainwindow.cpp
├── mainwindow.h
├── mainwindow.ui
└── tests
├── CMakeLists.txt
├── main.cpp
└── tst_qtcatchtest.cpp
这是最终文件版本。
CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(QtCatch VERSION 0.1 LANGUAGES CXX)
enable_testing()
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED)
if(CMAKE_TESTING_ENABLED)
add_subdirectory(tests)
endif()
set(LIB_SOURCES
mainwindow.cpp
mainwindow.h
mainwindow.ui
include/calculator.h include/calculator.cpp
)
add_library(QtCatchLib STATIC ${LIB_SOURCES})
target_link_libraries(QtCatchLib PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
add_executable(QtCatch main.cpp)
target_link_libraries(QtCatch PRIVATE QtCatchLib)
target_link_libraries(QtCatch PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
include/calculator.cpp
#include "calculator.h"
Calculator::Calculator()
{
}
int Calculator::Factorial( int number )
{
return number <= 1 ? 1 : Factorial( number - 1 ) * number;
}
include/calculator.h
#ifndef CALCULATOR_H
#define CALCULATOR_H
class Calculator
{
public:
Calculator();
int Factorial( int number );
};
#endif // CALCULATOR_H
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.cpp
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "include/calculator.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_factorialPushButton_clicked()
{
Calculator aCalc;
int factorial = aCalc.Factorial(ui->numberLineEdit->text().toInt());
QString result = QString("Result: %1").arg(factorial);
ui->resultLabel->setText(result);
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_factorialPushButton_clicked();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>214</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="numberLabel">
<property name="text">
<string>Number</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="numberLineEdit"/>
</item>
<item>
<widget class="QPushButton" name="factorialPushButton">
<property name="text">
<string>Calculate Factorial</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="resultLabel">
<property name="text">
<string>Result</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
tests/CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(QtCatchTest LANGUAGES CXX)
SET(CMAKE_CXX_STANDARD 11)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Gui REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Gui REQUIRED)
add_executable(QtCatchTest tst_qtcatchtest.cpp main.cpp)
target_link_libraries(QtCatchTest PRIVATE Qt${QT_VERSION_MAJOR}::Gui)
target_link_libraries(QtCatchTest PRIVATE QtCatchLib)
tests/main.cpp
#define CATCH_CONFIG_RUNNER
#include <catch2/catch.hpp>
#include <QtGui/QGuiApplication>
int main(int argc, char** argv)
{
QGuiApplication app(argc, argv);
return Catch::Session().run(argc, argv);
}
tests/tst_qtcatchtest.cpp
#include <catch2/catch.hpp>
#include "../include/calculator.h"
TEST_CASE( "Factorial of 0 is 1 (fail)", "[qt]" ) {
Calculator *aCalc = new Calculator();
REQUIRE( aCalc->Factorial(0) == 1 );
}
TEST_CASE( "Factorials of 1 and higher are computed (pass)", "[qt]" ) {
Calculator aCalc;
REQUIRE( aCalc.Factorial(1) == 1 );
REQUIRE( aCalc.Factorial(2) == 2 );
REQUIRE( aCalc.Factorial(3) == 6 );
REQUIRE( aCalc.Factorial(10) == 3628800 );
}