简体   繁体   English

如何使用 Catch 为我的 Qt 应用程序进行第一次测试?

[英]How can I make my first test with Catch for my Qt application?

I am trying to learn/use Catch ( https://github.com/catchorg/Catch2 ) for the first time on a Qt applcation.我第一次尝试在 Qt 应用程序上学习/使用 Catch ( https://github.com/catchorg/Catch2 )。

I am trying to follow the tutorial presented on Catch's initial page ( https://github.com/catchorg/Catch2/blob/devel/docs/tutorial.md#top ).我正在尝试遵循 Catch 初始页面上提供的教程( https://github.com/catchorg/Catch2/blob/devel/docs/tutorial.md#top )。

The first line of the above tutorial says that ideally I should be using Catch2 through its "CMake integration" ( https://github.com/catchorg/Catch2/blob/devel/docs/cmake-integration.md#top ).上述教程的第一行说,理想情况下,我应该通过其“CMake 集成”( https://github.com/catchorg/Catch2/blob/devel/docs/cmake-integration.md#top )使用 Catch2。 I faithfully follow the "ideal" path.我忠实地遵循“理想”的道路。

On the second paragraph of the "CMake integration" page I start to get lost: If you do not need custom main function, you should...在“CMake 集成”页面的第二段,我开始迷路:如果您不需要自定义主 function,您应该...

Do I need a custom main function?我需要定制主 function 吗? Why would anyone need one?为什么有人需要一个? How can a person live without one?一个人没有一个人怎么活? I have no idea at all and the text neither explains any of this nor provides any kind of sensible default orientation ( If you don't know what we are talking about just pretend you... or something similar).我完全不知道,文本既没有解释这些,也没有提供任何合理的默认方向(如果你不知道我们在说什么,就假装你......或类似的东西)。

I tried to ignore that and just follow on.我试图忽略这一点,然后继续。

On the third paragraph (reproduced below per request) it is presented a block of code and the reader gets to know that it should be enough to do the block of code.在第三段(根据请求在下面复制)呈现了一个代码块,读者会知道它应该足以完成代码块。 What is to do a block of code?代码块是什么的? Should I include this code in some pre existing file?我应该将此代码包含在一些预先存在的文件中吗? Which file?哪个文件? In what part of said file?在所述文件的哪一部分? Or should I create a new file with the proposed content?或者我应该使用建议的内容创建一个新文件? Which file?哪个文件? Where should I put it?我应该把它放在哪里?

This means that if Catch2 has been installed on the system, it should be enough to do这意味着如果系统上已经安装了 Catch2,应该足够了

> 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)

Can someone please present an working example of a simple use of Catch2 on a Qt project?有人可以提供一个在 Qt 项目上简单使用 Catch2 的工作示例吗? Preferably a desktop application?最好是桌面应用程序?

Update 2022-01-14: 2022 年 1 月 14 日更新:

Here is my take on trying to implement a minimal Qt + Catch2 integration similar to the first example in Catch's tutorial ( https://github.com/catchorg/Catch2/blob/v2.x/docs/tutorial.md#writing-tests ).这是我尝试实现最小的 Qt + Catch2 集成,类似于 Catch 教程中的第一个示例( https://github.com/catchorg/Catch2/blob/v2.x/docs/tutorial.md#writing-tests )。

I created a Qt Widget application called QtCatch.我创建了一个名为 QtCatch 的 Qt Widget 应用程序。 Here is it's file structure:这是它的文件结构:

.
├── CMakeLists.txt
├── include
│   ├── calculator.cpp
│   └── calculator.h
├── main.cpp
├── mainwindow.cpp
├── mainwindow.h
├── mainwindow.ui
└── tests
    ├── CMakeLists.txt
    ├── main.cpp
    └── tst_qtcatchtest.cpp

I included all files contents below for reference.我在下面包含了所有文件内容以供参考。

This file structure was created through Qt "New Project" dialog box.此文件结构是通过 Qt“新建项目”对话框创建的。 The main project is a "Application (Qt) > Qt Widgets Application" and the tests subproject is a "Other Project >> Auto Test Project"主项目是“Application (Qt) > Qt Widgets Application”,测试子项目是“Other Project >> Auto Test Project”

My Qt app runs without problem.我的 Qt 应用程序运行没有问题。

If I try to compile either the tests subproject or the main project uncommenting the "add_subdirectory(tests)" line in main CMakeLists.txt file I get the same error:如果我尝试编译测试子项目或主项目取消注释主 CMakeLists.txt 文件中的“add_subdirectory(tests)”行,我会收到相同的错误:

undefined reference to Calculator::Calculator()对 Calculator::Calculator() 的未定义引用

despite the尽管

#include "../include/calculator.h"

line in tst_qtcatchtest.cpp tst_qtcatchtest.cpp中的行

How can I make this simple Catch2 test case work in Qt 6?如何使这个简单的 Catch2 测试用例在 Qt 6 中工作?

CMakeLists.txt: 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:主.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:主窗口.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:主窗口.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:主窗口.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:包括/计算器.h:

#ifndef CALCULATOR_H
#define CALCULATOR_H


class Calculator
{
public:
    Calculator();

    int Factorial( int number );
};

#endif // CALCULATOR_H

include/calculator.cpp:包括/calculator.cpp:

#include "calculator.h"

Calculator::Calculator()
{

}

int Calculator::Factorial( int number )
{
    return number <= 1 ? 1      : Factorial( number - 1 ) * number;
}

tests/CMakeLists.txt:测试/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:测试/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:测试/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 );
}

Here I include the final solution for my question with the necessary Qt incantations for future reference.在这里,我包含了我的问题的最终解决方案以及必要的 Qt 咒语,以供将来参考。

Please be aware that I claim no authorship of this solution as it is derived from @sigma's answer请注意,我声称没有此解决方案的作者身份,因为它源自@sigma 的回答

├── CMakeLists.txt
├── include
│   ├── calculator.cpp
│   └── calculator.h
├── main.cpp
├── mainwindow.cpp
├── mainwindow.h
├── mainwindow.ui
└── tests
    ├── CMakeLists.txt
    ├── main.cpp
    └── tst_qtcatchtest.cpp

Here are the final file versions.这是最终文件版本。

CMakeLists.txt 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包括/calculator.cpp

#include "calculator.h"

Calculator::Calculator()
{

}

int Calculator::Factorial( int number )
{
    return number <= 1 ? 1 : Factorial( number - 1 ) * number;
}

include/calculator.h包括/计算器.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主窗口.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主窗口.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主窗口.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测试/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测试/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测试/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 );
}

You mean you do not even know whether you need a custom main function?, Just kidding, of course.你的意思是你甚至不知道你是否需要一个自定义的主 function?,当然是在开玩笑。 that was entertaining to read and I agree this could be made a little clearer, However, I am familiar with Catch2 and CMake, so I shall now expel all doubt!读起来很有趣,我同意这可以更清楚一点,但是,我熟悉 Catch2 和 CMake,所以我现在要消除所有疑问!

Catch2 tests need a small amount of code in a program's main function, to pass the command line arguments to its implementation and start running your test cases. Catch2 测试需要程序main function 中的少量代码,以将命令行 arguments 传递给其实现并开始运行您的测试用例。 So, as a convenience, it offers a default main function that does this for you, which is normally sufficient.因此,为了方便起见,它提供了一个默认的主 function 来为您执行此操作,这通常就足够了。 Their own documentation gives some examples of how you might supply your own main to alter the parsing of the command line. 他们自己的文档提供了一些示例,说明您如何提供自己的 main 来更改命令行的解析。 Another case could be an external library you use that requires some global setup and/or cleanup.另一种情况可能是您使用的外部库,它需要一些全局设置和/或清理。

So yes, you do need one or more separate executables for your tests, and the third paragraph shows the basic CMake setup for such an executable.所以是的,您确实需要一个或多个单独的可执行文件来进行测试,第三段显示了此类可执行文件的基本 CMake 设置。 CMake is far too broad of a topic to cover in this answer, but I typically use a fairly standard directory layout like this: CMake 的主题过于广泛,无法在此答案中涵盖,但我通常使用如下相当标准的目录布局:

|- build/ // all compilation output
|- src/
|  |- // project sources
|  |- CMakeLists.txt
|- tests/
|  |- test.cpp
|  |- CMakeLists.txt
|- CMakeLists.txt

The root CMakeLists.txt can be used for global definitions and adds the subdirectories that have their own CMake files, for example:根 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)

Then tests/CMakeLists.txt would use commands as in the Catch2 documentation:然后 tests/CMakeLists.txt 将使用 Catch2 文档中的命令:

find_package(Catch2 3 REQUIRED)
add_executable(foo test.cpp)
target_link_libraries(foo PRIVATE Qt5::Core Catch2::Catch2WithMain)

include(CTest)
include(Catch)
catch_discover_tests()

See also this answer for an example of how to extend this setup to compile the tests conditionally, so that you could disable them by default, but enable them for yourself or automated build testing systems.另请参阅此答案以获取有关如何扩展此设置以有条件地编译测试的示例,以便您可以默认禁用它们,但为您自己或自动构建测试系统启用它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM