KDevelop5/Manual/Code generation with templates: Difference between revisions

From KDE Wiki Sandbox
(Updated with an example of working testing units code examples and descriptive text)
(Marked this version for translation)
 
(5 intermediate revisions by 2 users not shown)
Line 59: Line 59:
Even though most testing frameworks require each test to also be a class, '''KDevelop''' includes a method to simplify the creation of unit tests. To create a new test, right-click on a project folder and choose <menuchoice>Create from Template...</menuchoice>. In the <tt>Language and Template</tt> selection page, choose your <tt>Programming Language</tt>, your <tt>Framework</tt> and <tt>Template</tt> and click <menuchoice>Next</menuchoice>.
Even though most testing frameworks require each test to also be a class, '''KDevelop''' includes a method to simplify the creation of unit tests. To create a new test, right-click on a project folder and choose <menuchoice>Create from Template...</menuchoice>. In the <tt>Language and Template</tt> selection page, choose your <tt>Programming Language</tt>, your <tt>Framework</tt> and <tt>Template</tt> and click <menuchoice>Next</menuchoice>.


<!--T:55-->
[[File:Kdevelop-test-selection.png|500px|thumb|center]]
[[File:Kdevelop-test-selection.png|500px|thumb|center]]


Line 64: Line 65:
You will be prompted for the test name and a list of test cases. For the test cases, you only have to specify a list of names. Some unit testing frameworks, such as PyUnit and PHPUnit, require that test cases start with a special prefix. In '''KDevelop''', the template is responsible for adding the prefix, so you do not have to prefix the test cases here. After clicking <menuchoice>Next</menuchoice>, specify the license and output locations for the generated files, and the test will be created. Unit tests created this way will not be added to any target automatically.
You will be prompted for the test name and a list of test cases. For the test cases, you only have to specify a list of names. Some unit testing frameworks, such as PyUnit and PHPUnit, require that test cases start with a special prefix. In '''KDevelop''', the template is responsible for adding the prefix, so you do not have to prefix the test cases here. After clicking <menuchoice>Next</menuchoice>, specify the license and output locations for the generated files, and the test will be created. Unit tests created this way will not be added to any target automatically.


<!--T:56-->
[[File:Kdevelop-testcases.png|500px|thumb|center]]
[[File:Kdevelop-testcases.png|500px|thumb|center]]


Adding test units and a <tt>CMakeLists.txt</tt> file to add the test units to the Bus project, gives the following <tt>CMakeLists.txt</tt> for the Bus project and the test subdirectory:
<!--T:57-->
* Bus project:{{Input|1=<nowiki>cmake_minimum_required(VERSION 3.0)
Adding test units and a <tt>CMakeLists.txt</tt> file to add the test units to the Bus project, gives the following <tt>CMakeLists.txt</tt> for the Bus project, src folder, and the tests folder:
* Bus project:<syntaxhighlight lang="CMake" line>cmake_minimum_required(VERSION 3.0)


<!--T:58-->
project(Bus LANGUAGES CXX)
project(Bus LANGUAGES CXX)


<!--T:59-->
set(QT_REQUIRED_VERSION "5.14.0")
set(QT_REQUIRED_VERSION "5.14.0")
find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED COMPONENTS Core Test)
find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED COMPONENTS Core Test)


<!--T:60-->
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTORCC ON)


<!--T:61-->
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD_REQUIRED ON)


<!--T:62-->
# Docs only available if this is the main app
# Docs only available if this is the main app
find_package(Doxygen)
find_package(Doxygen)
Line 89: Line 97:
endif()
endif()


<!--T:63-->
add_subdirectory(src)
add_subdirectory(src)


<!--T:64-->
enable_testing()
enable_testing()
add_subdirectory(tests)</nowiki>}}
add_subdirectory(tests)</syntaxhighlight>


* Test units:{{Input|1=<nowiki>include_directories("../src")
<!--T:65-->
* src folder:<syntaxhighlight lang="CMake" line>set(HEADER_LIST "bus.h")
add_executable(bus main.cpp bus.cpp)


<!--T:66-->
target_link_libraries(bus PRIVATE Qt5::Core)
</syntaxhighlight>
<!--T:67-->
* tests folder:<syntaxhighlight lang="CMake" line>include_directories("../src")
<!--T:68-->
add_executable(TestBus testbus.cpp ../src/bus.cpp)
add_executable(TestBus testbus.cpp ../src/bus.cpp)


<!--T:69-->
add_test(NAME TestBus COMMAND TestBus)
add_test(NAME TestBus COMMAND TestBus)


target_link_libraries(TestBus PRIVATE Qt5::Test) </nowiki>}}
<!--T:70-->
target_link_libraries(TestBus PRIVATE Qt5::Test) </syntaxhighlight>


<!--T:71-->
Looking at the <tt>testbus.h</tt> file, you will see the test cases that were specified in the add test sequence of steps, the test prefix was added by the template:
Looking at the <tt>testbus.h</tt> file, you will see the test cases that were specified in the add test sequence of steps, the test prefix was added by the template:


{{Input|1=<nowiki>class TestBus: public QObject
<!--T:72-->
<syntaxhighlight lang="cpp" line>class TestBus: public QObject
{
{
     Q_OBJECT
     Q_OBJECT


<!--T:73-->
// each private slot is a test
// each private slot is a test
private slots:
private slots:
Line 118: Line 143:
private:
private:
     Transportation::Bus m_bus;
     Transportation::Bus m_bus;
};</nowiki>}}
};</syntaxhighlight>


The functions completed functions in testbus.cpp are detailed below:
<!--T:74-->
The completed functions in testbus.cpp are detailed below:


* testNumberOfPassengers{{Input|1=<nowiki>void TestBus::testNumberOfPassengers()
<!--T:75-->
* testNumberOfPassengers<syntaxhighlight lang="cpp" line>void TestBus::testNumberOfPassengers()
{
{
     m_bus.setNumberOfPassengers(1);
     m_bus.setNumberOfPassengers(1);
     QVERIFY(m_bus.numberOfPassengers() == 1);
     QVERIFY(m_bus.numberOfPassengers() == 1);
}</nowiki>}}
}</syntaxhighlight>


* testNameOfDriver{{Input|1=<nowiki>void TestBus::testNameOfDriver()
<!--T:76-->
* testNameOfDriver<syntaxhighlight lang="cpp" line>void TestBus::testNameOfDriver()
{
{
     m_bus.setNameOfDriver(QString("Billy"));
     m_bus.setNameOfDriver(QString("Billy"));
     QVERIFY(m_bus.nameOfDriver() == QString("Billy"));
     QVERIFY(m_bus.nameOfDriver() == QString("Billy"));
}</nowiki>}}
}</syntaxhighlight>


* testNumberOfPassengersChanged{{Input|1=<nowiki>void TestBus::testNumberOfPassengersChanged()
<!--T:77-->
* testNumberOfPassengersChanged<syntaxhighlight lang="cpp" line>void TestBus::testNumberOfPassengersChanged()
{
{
     // create spy object
     // create spy object
Line 145: Line 174:
     // verify the signal was sent
     // verify the signal was sent
     QCOMPARE(spy1.count(), 1);
     QCOMPARE(spy1.count(), 1);
}</nowiki>}}
}</syntaxhighlight>


* testNameOfDriverChanged{{Input|1=<nowiki>void TestBus::testNameOfDriverChanged()
<!--T:78-->
* testNameOfDriverChanged<syntaxhighlight lang="cpp" line>void TestBus::testNameOfDriverChanged()
{
{
     // create spy object
     // create spy object
Line 159: Line 189:
     QCOMPARE(spy1.count(), 1);
     QCOMPARE(spy1.count(), 1);
      
      
}</nowiki>}}
}</syntaxhighlight>


<!--T:79-->
At this point, running build on the project also builds the tests. You can run Bus and TestBus separately. Right clicking on the TestBus target gives a pop-up menu where you can select <menuchoice>Execute As ...</menuchoice> then <menuchoice>Compiled Binary</menuchoice>. This opens the <tt>Run</tt> tool view with the following output:
At this point, running build on the project also builds the tests. You can run Bus and TestBus separately. Right clicking on the TestBus target gives a pop-up menu where you can select <menuchoice>Execute As ...</menuchoice> then <menuchoice>Compiled Binary</menuchoice>. This opens the <tt>Run</tt> tool view with the following output:


<!--T:80-->
{{Input|1=<nowiki>********* Start testing of TestBus *********
{{Input|1=<nowiki>********* Start testing of TestBus *********
Config: Using QtTest library 5.14.1, Qt 5.14.1 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 7.4.0)
Config: Using QtTest library 5.14.1, Qt 5.14.1 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 7.4.0)
Line 174: Line 206:
********* Finished testing of TestBus *********
********* Finished testing of TestBus *********
*** Finished ***</nowiki>}}
*** Finished ***</nowiki>}}
<!--T:81-->
Running the build, executable, and tests form the command line looks a little different. The tests are combined as one file test. Looking in <tt>build/Testing/Temporary</tt> you will find the <tt>LastTest.log</tt> that shows each test performed similar to above.
<!--T:82-->
[[File:kdevelop-bus-command-line.png|500px|thumb|center]]


<!--T:43-->
<!--T:43-->
Line 193: Line 231:


<!--T:48-->
<!--T:48-->
[[Image:kdevelop-template-manager.png|thumb|500px|center]]
[[File:Kdevelop-templatemanager.png|500px|thumb|center]]


<!--T:49-->
<!--T:49-->

Latest revision as of 05:05, 5 April 2020

Other languages:
Under Construction
This is a new page, currently under construction!


Code generation with templates

KDevelop uses templates for generating source code files and to avoid writing repeatable code.

Creating a new class

The most common use for code generation is probably writing new classes. To create a new class in an existing project, right-click on a project folder and choose Create from Template.... The same dialog can be started from the menu by clicking File -> New from Template..., but using a project folder has the benefit of setting a base URL for the output files. Choose C++ in the Language selection view, the desired Framework and Template in the other two views. At the bottom of the dialog, a Preview pane shows you the files it will generate and allows you to browse the files. After you are through with your selections, you will have to specify the details of the new class in the Class Basics dialog after clicking > Next.

First, you have to specify an identifier for the new class. This can be a simple name (like Bus) or a complete identifier with namespaces (like Transportation::Bus). In the latter case, KDevelop will parse the identifier and correctly separate the namespaces from the actual name. On the same page, you can add base classes for the new class. You may notice that some templates choose a base class on their own, you are free to remove it and/or add other bases. You should write the full inheritance statement here, which is language-dependent, such as public QObject for C++, extends SomeClass for PHP or simply the name of the class for Python.

On the next page, you are offered a selection of virtual methods from all inherited classes, as well as some default constructors, destructors and operators. Checking the checkbox next to a method signature will implement this method in the new class.

Clicking > Next brings up the Class Members dialog box where you can add members to a class. Depending on the selected template, these may appear in the new class as member variables, or the template may create properties with setters and getters for them. In a language where variable types have to be declared, such as C++, you have to specify both the type and the name of the member, such as int number or QString name. In other languages, you may leave out the type, but it is good practice to enter it anyway because the selected template could still make some use of it. Optionally you can rearrange the order of the class members by selecting a member and then hitting either Move Up or Move Down. The chosen order will be the sequence used in generating the source file.

In the following pages, you can choose a license for your new class, set any custom options required by the selected template, and configure output locations for all the generated files. By clicking Finish, you complete the assistant and create the new class. The generated files will be opened in the editor, so you can start adding code right away.

After creating a new C++ class, you will be given the option of adding the class to a project target. Choose a target from the dialog page, or dismiss the page and add the files to a target manually.

If you chose the QObject subclass template, checked some of the default methods, and added two member variables, the output should look like on the following picture. All of the automatically added documentation headers for each class, function, and data items have been removed for clarity.

You can see that data members are converted into Qt properties, with accessor functions and the Q_PROPERTY macros. Arguments to setter functions are even passed by const-reference, where appropriate. If you chose QObject pimpl subclass, a private class is declared, and a private pointer created with Q_DECLARE_PRIVATE. All this is done by the template, choosing a different template in the first step could completely change the output.

Creating a new unit test

Even though most testing frameworks require each test to also be a class, KDevelop includes a method to simplify the creation of unit tests. To create a new test, right-click on a project folder and choose Create from Template.... In the Language and Template selection page, choose your Programming Language, your Framework and Template and click Next.

You will be prompted for the test name and a list of test cases. For the test cases, you only have to specify a list of names. Some unit testing frameworks, such as PyUnit and PHPUnit, require that test cases start with a special prefix. In KDevelop, the template is responsible for adding the prefix, so you do not have to prefix the test cases here. After clicking Next, specify the license and output locations for the generated files, and the test will be created. Unit tests created this way will not be added to any target automatically.

Adding test units and a CMakeLists.txt file to add the test units to the Bus project, gives the following CMakeLists.txt for the Bus project, src folder, and the tests folder:

  • Bus project:
    cmake_minimum_required(VERSION 3.0)
    
    project(Bus LANGUAGES CXX)
    
    set(QT_REQUIRED_VERSION "5.14.0")
    find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED COMPONENTS Core Test)
    
    set(CMAKE_AUTOUIC ON)
    set(CMAKE_AUTOMOC ON)
    set(CMAKE_AUTORCC ON)
    
    set(CMAKE_CXX_STANDARD 11)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    
    # Docs only available if this is the main app
    find_package(Doxygen)
    if(Doxygen_FOUND)
    	add_subdirectory(docs)
    else()
    	message(STATUS "Doxygen not found, not building docs")
    endif()
    
    add_subdirectory(src)
    
    enable_testing()
    add_subdirectory(tests)
    
  • src folder:
    set(HEADER_LIST "bus.h")
    add_executable(bus main.cpp bus.cpp)
    
    target_link_libraries(bus PRIVATE Qt5::Core)
    
  • tests folder:
    include_directories("../src")
    
    add_executable(TestBus testbus.cpp ../src/bus.cpp)
    
    add_test(NAME TestBus COMMAND TestBus)
    
    target_link_libraries(TestBus PRIVATE Qt5::Test)
    

Looking at the testbus.h file, you will see the test cases that were specified in the add test sequence of steps, the test prefix was added by the template:

class TestBus: public QObject
{
    Q_OBJECT

// each private slot is a test
private slots:
    // -- tests to run on slots --
    void testNumberOfPassengers();
    void testNameOfDriver();
    // -- tests to run on signals --
    void testNumberOfPassengersChanged();
    void testNameOfDriverChanged();
private:
    Transportation::Bus m_bus;
};

The completed functions in testbus.cpp are detailed below:

  • testNumberOfPassengers
    void TestBus::testNumberOfPassengers()
    {
        m_bus.setNumberOfPassengers(1);
        QVERIFY(m_bus.numberOfPassengers() == 1);
    }
    
  • testNameOfDriver
    void TestBus::testNameOfDriver()
    {
        m_bus.setNameOfDriver(QString("Billy"));
        QVERIFY(m_bus.nameOfDriver() == QString("Billy"));
    }
    
  • testNumberOfPassengersChanged
    void TestBus::testNumberOfPassengersChanged()
    {
        // create spy object
        QSignalSpy spy1(&m_bus,
                        &Transportation::Bus::numberOfPassengersChanged);
        // now change the number of passengers
        m_bus.setNumberOfPassengers(4);
        // verify the check was made
        QVERIFY(m_bus.numberOfPassengers() == 4);
        // verify the signal was sent
        QCOMPARE(spy1.count(), 1);
    }
    
  • testNameOfDriverChanged
    void TestBus::testNameOfDriverChanged()
    {
        // create spy object
        QSignalSpy spy1(&m_bus,
                        &Transportation::Bus::nameOfDriverChanged);
        // now change the name of driver
        m_bus.setNameOfDriver(QString("Jim"));
        // verify the check was made
        QVERIFY(m_bus.nameOfDriver() == QString("Jim"));
        // verify the signal was sent
        QCOMPARE(spy1.count(), 1);
        
    }
    

At this point, running build on the project also builds the tests. You can run Bus and TestBus separately. Right clicking on the TestBus target gives a pop-up menu where you can select Execute As ... then Compiled Binary. This opens the Run tool view with the following output:

********* Start testing of TestBus *********
Config: Using QtTest library 5.14.1, Qt 5.14.1 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 7.4.0)
PASS   : TestBus::initTestCase()
PASS   : TestBus::testNumberOfPassengers()
PASS   : TestBus::testNameOfDriver()
PASS   : TestBus::testNumberOfPassengersChanged()
PASS   : TestBus::testNameOfDriverChanged()
PASS   : TestBus::cleanupTestCase()
Totals: 6 passed, 0 failed, 0 skipped, 0 blacklisted, 8ms
********* Finished testing of TestBus *********
*** Finished ***

Running the build, executable, and tests form the command line looks a little different. The tests are combined as one file test. Looking in build/Testing/Temporary you will find the LastTest.log that shows each test performed similar to above.

If you are using CTest or some other testing framework, make sure to add the new files to a target. KDevelop provides a Unit Tests toolview that integrates with CTest.

Other files

While classes and unit tests receive special attention when generating code from templates, the same method can be used for any kind of source code files. For example, one could use a template for a CMake Find module or a .desktop file. This can be done by choosing Create from Template..., and selecting the wanted category and template. If the selected category is neither Class nor Test, you will only have the option of choosing the license, any custom options specified by the template, and the output file locations. As with classes and tests, finishing the assistant will generate the files and open them in the editor.

Managing templates

From the File -> New from Template... assistant, you can also download additional file templates by clicking the Get more Templates... button. This opens a Get Hot New Stuff dialog, where you can install additional templates, as well as update or remove them. There is also a configuration module for template, which can be reached by clicking Settings -> Configure KDevelop -> Templates. From there, you can manage both file templates (explained above) and project templates (used for creating new projects).

Of course, if none of the available templates suit your project, you can always create new ones. The easiest way is probably to copy and modify an existing template, while a short tutorial and a longer specification document are there to help you. To copy an installed template, open the template manager by clicking Settings -> Configure KDevelop... -> Templates, select the template you wish to copy, then click the Extract Template button. Select a destination folder, then click OK, and the contents of the template will be extracted into the selected folder. Now you can edit the template by opening the extracted files and modifying them. After you are done, you can import your new template into KDevelop by opening the template manager, activating the appropriate tab (either Project Templates or File Templates) and clicking Load Template. Open the template description file, which is the one with the suffix either .kdevtemplate or .desktop. KDevelop will compress the files into a template archive and import the template.

Note
When copying an existing template, make sure you rename it before importing it again. Otherwise, you will either overwrite the old template or will end up with two templates with identical names. To rename a template, rename the description file to something unique (but keep the suffix), and change the Name entry in the description file.


If you want to write a template from scratch, you can start with a sample C++ class template by creating a new project and selecting the C++ Class Template project in category KDevelop.