I am trying to learn/use Catch (https://github.com/catchorg/Catch2) for the first time on a Qt applcation.
I am trying to follow the tutorial presented on Catch's initial page (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). 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...
Do I need a custom main 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
> 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? Preferably a desktop application?
Update 2022-01-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).
I created a Qt Widget application called QtCatch. 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. The main project is a "Application (Qt) > Qt Widgets Application" and the tests subproject is a "Other Project >> Auto Test Project"
My Qt app runs without problem.
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:
undefined reference to Calculator::Calculator()
despite the
#include "../include/calculator.h"
line in tst_qtcatchtest.cpp
How can I make this simple Catch2 test case work in 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 );
}