namespace cpp {}

C++ lernen, kennen, anwenden

Benutzer-Werkzeuge

Webseiten-Werkzeuge


lernen:doctest
no way to compare when less than two revisions

Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen angezeigt.


lernen:doctest [2022-05-21 07:20] (aktuell) – angelegt - Externe Bearbeitung 127.0.0.1
Zeile 1: Zeile 1:
 +====== doctest ======
 +
 +===== Unit-Tests =====
 +
 +Zum Schreiben von Programmen gehört auch das Testen, ob sie das tun, was sie tun sollen. Unit-Tests überprüfen Programmbausteine. Funktioniert die geschriebene Funktion oder Bibliothek? Erfüllt sie die Anforderungen? Erfüllt sie diese auch nach einem Umschreiben, bei dem ein besserer Lösungsweg gefunden wurde? Reagiert sie auf falsche Eingaben? Reagiert sie wie vorgeschrieben? Die nur aus einer Headerdatei bestehende C++-Bibliothek [[https://github.com/doctest/doctest|doctest]] ermöglicht es auf einfache Weise, Unit-Tests durchzuführen.
 +
 +===== Die Aufgabe =====
 +
 +Nehmen wir ein einfaches Programm:
 +
 +<code cpp>
 +#include <iostream>
 +#include "pythagorean.hpp"
 +
 +int main()
 +{   
 +    std::cout << is_pythagorean_triple(3,4,5) << "\n";
 +}
 +</code>
 +Bei einem pythagoräischen Tripel ist das Quadrat der größten (natürlichen) Zahl gleich der Summe der Quadrate der anderen beiden (natürlichen) Zahlen: 3² + 4² = 5². Der Name kommt vom Satz des Pythagoras, ein Dreieck mit den Seitenlängen 3, 4 und 5 (in willkürlichen Einheiten) ist rechtwinklig.
 +
 +Das Projekt wird in ''%%CMakeLists.txt%%'' für [[cmake]] beschrieben:
 +
 +<code>
 +cmake_minimum_required(VERSION 3.18)
 +project(Example)
 +
 +set(CMAKE_CXX_STANDARD 20)
 +set(CMAKE_CXX_STANDARD_REQUIRED ON)
 +set(CMAKE_CXX_EXTENSIONS OFF)
 +
 +add_executable(programm src/main.cpp)
 +target_include_directories(programm PRIVATE include) 
 +target_sources(programm PRIVATE src/pythagorean.cpp) 
 +</code>
 +Wir werden es mehrfach übersetzen und testen müssen. Der Header ''%%pythagorean.hpp%%''
 +
 +<code cpp>
 +#pragma once
 +bool is_pythagorean_triple(int a, int b, int c);
 +</code>
 +befindet sich im Unterverzeichnis ''%%include%%'', die anderen Quellen im Unterverzeichnis ''%%src%%''. Nach dem Kommandos
 +
 +<code>
 +mkdir build
 +cd build
 +cmake -G "MinGW Makefiles" ..
 +cmake --build .
 +programm
 +</code>
 +sollte als Ausgabe ''%%1%%'' (für ''%%true%%'') erscheinen. Fehlt nur noch ''%%pythgorean.cpp%%''
 +
 +===== Tests zuerst =====
 +
 +Halt! Zuvor sollte festgelegt werden, was die fehlende Funktion tun soll. Dafür bereiten wir eine Test-Umgebung vor. Wir erweitern ''%%CMakeLists.txt%%'':
 +
 +<code>
 +add_executable(test_programm doctest/doctest_main.cpp)
 +target_include_directories(test_programm PRIVATE include doctest/include) 
 +target_sources(test_programm PRIVATE src/pythagorean.cpp src/pythagorean.test.cpp) 
 +</code>
 +Das Hauptprogramm ''%%doctest_main.cpp%%'' für Tests mit [[https://github.com/doctest/doctest|doctest]] besteht nur aus zwei Zeilen:
 +
 +<code cpp>
 +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
 +#include "doctest.h"
 +</code>
 +Es schließt nur den von der Webseite zu holenden Header ''%%doctest.h%%'' ein. Ein erster Unit-Test wird in ''%%pythagorean.test.cpp%%'' erstellt. (Ich bevorzuge hier die Trennung zwischen Testcode und zu testenden Quellen.)
 +
 +<code cpp>
 +#include "pythagorean.hpp"
 +#include "doctest.h"
 +
 +TEST_CASE("known triples")
 +{
 +    CHECK(true == is_pythagorean_triple(3,4,5));
 +    CHECK(true == is_pythagorean_triple(5,4,3));
 +}
 +</code>
 +===== Roter Test (schlägt fehl) =====
 +
 +Eine erste Implementierung (wir überspringen die nullte Näherung ''%%return false%%'', weil zu albern)
 +
 +<code cpp>
 +#include "pythagorean.hpp"
 +
 +bool is_pythagorean_triple(int a, int b, int c)
 +{
 +    return a*a + b*b == c*c;
 +}
 +</code>
 +genügt zwar der ersten Anforderung. Die zweite schlägt beim Aufruf des Testprogramms fehl((
 + Ich weiß nicht warum. Hängt vermutlich damit zusammen, dass wir uns an Stelle des Satzes von Pythagoras die Formel a²+b²=c² gemerkt haben.
 + Wie oft hat deine Lehrerin darauf hingewiesen, dass die längste Seite nicht c heißen muss?
 +)). Die Beanstandung wird klar und deutlich ausgegeben:
 +
 +<code>
 +...>test_programm.exe
 +[doctest] doctest version is "2.4.0"
 +[doctest] run with "--help" for options
 +===============================================================================
 +...\pythagorean.test.cpp:4:
 +TEST CASE:  known triples
 +
 +...\pythagorean.test.cpp:7: ERROR: CHECK( true == is_pythagorean_triple(5,4,3) ) is NOT correct!
 +  values: CHECK( true == false )
 +
 +===============================================================================
 +[doctest] test cases:      1 |      0 passed |      1 failed |      0 skipped
 +[doctest] assertions:      2 |      1 passed |      1 failed |
 +[doctest] Status: FAILURE!
 +</code>
 +===== Grüner Test (ist bestanden) =====
 +
 +Erst durch Umordnen der übernommenen Werte
 +
 +<code cpp>
 +#include <algorithm>
 +#include "pythagorean.hpp"
 +
 +bool is_pythagorean_triple(int a, int b, int c)
 +{
 +    if (a > c) std::swap(a,c);
 +    if (b > c) std::swap(b,c);
 +    
 +    return a*a + b*b == c*c;
 +}
 +</code>
 +wird dieser Test bestanden:
 +
 +<code>
 +[doctest] test cases:      1 |      1 passed |      0 failed |      0 skipped
 +[doctest] assertions:      2 |      2 passed |      0 failed |
 +[doctest] Status: SUCCESS!
 +</code>
 +===== Neue Anforderungen =====
 +
 +Auch das andere mögliche Ergebnis muss eine boolesche Funktion erfüllen, was sie glücklicherweise tut. Der Test bleibt grün:
 +
 +<code cpp>
 +TEST_CASE("not pythagorean triples")
 +{
 +    CHECK(false == is_pythagorean_triple(1,1,2));
 +    CHECK(false == is_pythagorean_triple(5,4,6));
 +}
 +</code>
 +Weil (3,4,5) als das pythagoräische Tripel mit den kleinsten Zahlen bekannt ist, müssen wir aber auch die Null ausschließen: Ein Dreieck, bei dem mindestens eine Seite die Länge 0 hat, ist kein Dreieck. Das ist noch nicht geschehen. Ebenso haben negative ganze Zahlen keinen Sinn: Längen sind niemals negativ.
 +
 +<code>
 +TEST_CASE("pythagorean triples contain only positive numbers")
 +{
 +    CHECK(false == is_pythagorean_triple(0,1,1));
 +    CHECK(false == is_pythagorean_triple(-3,4,5));
 +    CHECK(false == is_pythagorean_triple(-3,-4,-5));
 +}
 +</code>
 +Dieser Test wird erst mit einer weiteren Änderung grün:
 +
 +<code cpp>
 +#include <algorithm>
 +#include "pythagorean.hpp"
 +
 +bool is_pythagorean_triple(int a, int b, int c)
 +{
 +    if (a < 1 || b < 1 || c < 1) return false;
 +    if (a > c) std::swap(a,c);
 +    if (b > c) std::swap(b,c);
 +    
 +    return a*a + b*b == c*c;
 +}
 +</code>
 +<code>
 +[doctest] test cases:      3 |      3 passed |      0 failed |      0 skipped
 +[doctest] assertions:      7 |      7 passed |      0 failed |
 +[doctest] Status: SUCCESS!
 +</code>
 +Sollten weitere Forderungen kommen (nur teilerfremde Zahlen?), sind wir gewappnet. Nebenbei gibt es unendlich viele weitere pythagoräische Tripel, z.B. (5,12,13)… Was sollte passieren, wenn die Quadrate größer werden als der Zahlenbereich für ''%%int%%''-Werte?
 +
 +===== CTest =====
 +
 +Unabhängig davon, ob wir ''%%doctest%%'', das ähnlich zu handhabende [[https://github.com/catchorg/Catch2|Catch2]], ein anderes Test-Framework nutzen oder eigene Testprogramme schreiben, können wir diese in ''%%CMakeLists.txt%%'' registrieren
 +
 +<code>
 +enable_testing()
 +add_test(test_pyth test_programm)
 +</code>
 +und dann mit einem der Befehle
 +
 +<code>
 +cmake --build . --target test
 +ctest
 +ctest -V
 +</code>
 +ausführen lassen:
 +
 +<code>
 +Test project .../build
 +   Start 1: test_pyth
 +1/1 Test #1: test_pyth ........................   Passed    0.01 sec
 +
 +100% tests passed, 0 tests failed out of 1
 +
 +Total Test time (real) =   0.03 sec
 +</code>
 +Die Option ''%%-V%%'' (mit großem V!) steht für //verbose// = ausführlich, nützlich vor allem, wenn ein Test scheitert.
  
lernen/doctest.txt · Zuletzt geändert: 2022-05-21 07:20 von 127.0.0.1

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki