Verbesserung der Testüberdeckung

Content

Wenn wir eine Klasse testen, wäre es gut, wenn wir ein Testprogramm haben, das zumindest eine C0-Überdeckung erreicht, d.h. der gesamte Programmtext einer Klasse kommt zur Ausführung im Rahmen von Tests.

Wenn wir die C0-Überdeckung überprüfen möchten, dann können wir das beim g++ mit den Optionen --coverage und dem Werkzeug gcov tun:

theon$ g++ --coverage -Wall -o test1 test1.cpp
theon$ ./test1
 2 3 5 7 11 13
theon$ gcov test1
File 'test1.cpp'
Lines executed:100.00% of 6
Creating 'test1.cpp.gcov'

File '/opt/ulm/ballinrobe/include/c++/7.3.0/iostream'
Lines executed:100.00% of 1
Creating 'iostream.gcov'

File '/opt/ulm/ballinrobe/include/c++/7.3.0/bits/stl_iterator_base_funcs.h'
Lines executed:100.00% of 5
Creating 'stl_iterator_base_funcs.h.gcov'

File 'array.hpp'
Lines executed:100.00% of 24
Creating 'array.hpp.gcov'

File '/opt/ulm/ballinrobe/include/c++/7.3.0/bits/stl_iterator_base_types.h'
Lines executed:100.00% of 2
Creating 'stl_iterator_base_types.h.gcov'

File '/opt/ulm/ballinrobe/include/c++/7.3.0/initializer_list'
Lines executed:100.00% of 3
Creating 'initializer_list.gcov'

File '/opt/ulm/ballinrobe/include/c++/7.3.0/x86_64-pc-solaris2.11/bits/gthr-default.h'
Lines executed:0.00% of 3
Creating 'gthr-default.h.gcov'

File '/opt/ulm/ballinrobe/include/c++/7.3.0/new'
Lines executed:100.00% of 2
Creating 'new.gcov'
theon$ cat array.hpp.gcov
        -:    0:Source:array.hpp
        -:    0:Graph:test1.gcno
        -:    0:Data:test1.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#ifndef ARRAY_HPP
        -:    2:#define ARRAY_HPP
        -:    3:
        -:    4:#include 
        -:    5:#include 
        -:    6:#include 
        -:    7:#include 
        -:    8:#include 
        -:    9:#include 
        -:   10:#include 
        -:   11:
        -:   12:template
        -:   13:class Array {
        -:   14:   private:
        -:   15:      using Tag = enum {AllocateStorage};
        1:   16:      Array(Tag tag, std::size_t size) :
        -:   17:	 size(size),
        2:   18:	 data(size > 0?
        1:   19:	       static_cast(operator new(sizeof(T) * size))
        -:   20:	    :
        2:   21:	       nullptr) {
        1:   22:      }
        -:   23:   public:
        -:   24:      Array() noexcept : size(0), data(nullptr) {
        -:   25:      }
        -:   26:      Array(std::size_t size) : Array(AllocateStorage, size) {
        -:   27:	 for (std::size_t index = 0; index < size; ++index) {
        -:   28:	    new (data + index) T();
        -:   29:	 }
        -:   30:      }
        -:   31:      Array(std::size_t size, const T& t) : Array(AllocateStorage, size) {
        -:   32:	 for (std::size_t index = 0; index < size; ++index) {
        -:   33:	    new (data + index) T(t);
        -:   34:	 }
        -:   35:      }
        -:   36:      template
        1:   37:      Array(Iterator it1, Iterator it2) :
        1:   38:	    Array(AllocateStorage, std::distance(it1, it2)) {
        1:   39:	 std::size_t index = 0;
       13:   40:	 while (it1 != it2 && index < size) {
        6:   41:	    new (data + index++) T(*it1++);
        -:   42:	 }
        1:   43:	 size = index; // just in case
        1:   44:      }
        1:   45:      Array(std::initializer_list elements) :
        1:   46:	    Array(elements.begin(), elements.end()) {
        1:   47:      }
        -:   48:      Array(const Array& other) : Array(AllocateStorage, other.size) {
        -:   49:	 for (std::size_t index = 0; index < size; ++index) {
        -:   50:	    new (data + index) T(other.data[index]);
        -:   51:	 }
        -:   52:      }
        -:   53:      friend void swap(Array& a1, Array& a2) noexcept {
        -:   54:	 using std::swap;
        -:   55:	 swap(a1.size, a2.size);
        -:   56:	 swap(a1.data, a2.data);
        -:   57:      }
        -:   58:      Array(Array&& other) noexcept : Array() {
        -:   59:	 swap(*this, other);
        -:   60:      }
        1:   61:      ~Array() {
        7:   62:	 for (std::size_t index = 0; index < size; ++index) {
        6:   63:	    data[index].~T();
        -:   64:	 }
        1:   65:	 operator delete(data);
        1:   66:      }
        -:   67:      Array& operator=(Array other) noexcept {
        -:   68:	 swap(*this, other);
        -:   69:	 return *this;
        -:   70:      }
        -:   71:      std::size_t get_size() const noexcept {
        -:   72:	 return size;
        -:   73:      }
        -:   74:      T& operator()(std::size_t index) {
        -:   75:	 assert(index < size);
        -:   76:	 return data[index];
        -:   77:      }
        -:   78:      const T& operator()(std::size_t index) const {
        -:   79:	 assert(index < size);
        -:   80:	 return data[index];
        -:   81:      }
        1:   82:      T* begin() noexcept {
        1:   83:	 return data;
        -:   84:      }
        -:   85:      const T* begin() const noexcept {
        -:   86:	 return data;
        -:   87:      }
        1:   88:      T* end() noexcept {
        1:   89:	 return data + size;
        -:   90:      }
        -:   91:      const T* end() const noexcept {
        -:   92:	 return data + size;
        -:   93:      }
        -:   94:   private:
        -:   95:      std::size_t size;
        -:   96:      T* data;
        -:   97:};
        -:   98:
        -:   99:#endif
theon$ rm -f *.gcov
theon$ 

Das in array.hpp.gcov abgelegte Listing zeigt

Zu suchen sind hier Zeilen, die mit ##### beginnen, d.h. die nie ausgeführt worden sind und bei denen eine Ausführung zu erwarten wäre. Ignorieren Sie dabei die Zeilen am Ende einer Methode. Teilweise wird aber auch nur - angegeben, wenn der entsprechende Code nie erzeugt worden ist.

Aufgabe

Entwickeln Sie ein Testprogramm für die Klasse Array, die

Dazu empfiehlt sich die Entwicklung einer Testfunktion mit drei Parametern:

Die Ausführung des Funktionsobjekt sollte dabei in einen try-Block eingeschlossen werden, der alle Ausnahmen behandelt. Für jeden Test sollte es eine Ausgabe geben mit der Testnummer, der kurzen Beschreibung und dem Ausgang des Tests.

Um die noexcept-Zusicherung zu testen, sollten Sie eine kleine Testklasse als Elementtyp für die Arrays hinzufügen, die bei Bedarf Ausnahmen erzeugt. Wenn es zu einer Ausnahme kommt in einer Funktion oder Methode, die mit noexcept deklariert ist, wird std::terminate aufgerufen und Ihr Testprogramm würde nicht regulär bis zum Ende laufen.

Überprüfen Sie Ihren Testumfang auch dahingehend, dass Sie testweise bei einer der Methoden, bei denen noexcept nicht zulässig ist dieses Schlüsselwort hinzufügen, ob es dann zum Aufruf von std::terminate kommt, weil Sie einen entsprechenden geeigneten Test haben.

Vorlage

#ifndef ARRAY_HPP
#define ARRAY_HPP

#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <initializer_list>
#include <iterator>
#include <new>
#include <utility>

template<typename T>
class Array {
   private:
      using Tag = enum {AllocateStorage};
      Array(Tag tag, std::size_t size) :
	 size(size),
	 data(size > 0?
	       static_cast<T*>(operator new(sizeof(T) * size))
	    :
	       nullptr) {
      }
   public:
      Array() noexcept : size(0), data(nullptr) {
      }
      Array(std::size_t size) : Array(AllocateStorage, size) {
	 for (std::size_t index = 0; index < size; ++index) {
	    new (data + index) T();
	 }
      }
      Array(std::size_t size, const T& t) : Array(AllocateStorage, size) {
	 for (std::size_t index = 0; index < size; ++index) {
	    new (data + index) T(t);
	 }
      }
      template<typename Iterator>
      Array(Iterator it1, Iterator it2) :
	    Array(AllocateStorage, std::distance(it1, it2)) {
	 std::size_t index = 0;
	 while (it1 != it2 && index < size) {
	    new (data + index++) T(*it1++);
	 }
	 size = index; // just in case
      }
      Array(std::initializer_list<T> elements) :
	    Array(elements.begin(), elements.end()) {
      }
      Array(const Array& other) : Array(AllocateStorage, other.size) {
	 for (std::size_t index = 0; index < size; ++index) {
	    new (data + index) T(other.data[index]);
	 }
      }
      friend void swap(Array& a1, Array& a2) noexcept {
	 using std::swap;
	 swap(a1.size, a2.size);
	 swap(a1.data, a2.data);
      }
      Array(Array&& other) noexcept : Array() {
	 swap(*this, other);
      }
      ~Array() {
	 for (std::size_t index = 0; index < size; ++index) {
	    data[index].~T();
	 }
	 operator delete(data);
      }
      Array& operator=(Array other) noexcept {
	 swap(*this, other);
	 return *this;
      }
      std::size_t get_size() const noexcept {
	 return size;
      }
      T& operator()(std::size_t index) {
	 assert(index < size);
	 return data[index];
      }
      const T& operator()(std::size_t index) const {
	 assert(index < size);
	 return data[index];
      }
      T* begin() noexcept {
	 return data;
      }
      const T* begin() const noexcept {
	 return data;
      }
      T* end() noexcept {
	 return data + size;
      }
      const T* end() const noexcept {
	 return data + size;
      }
   private:
      std::size_t size;
      T* data;
};

#endif