Luka Steinbach Dynamische Analyse von JavaScript durch Tests

Die Seite wird erstellt Richard Linke
 
WEITER LESEN
Luka Steinbach Dynamische Analyse von JavaScript durch Tests
Bachelorarbeit

Dynamische Analyse von JavaScript
          durch Tests

           Luka Steinbach

 Gutachter: Prof. Dr. Peter Thiemann
 Betreuer: Fernando Cristiani

      Albert-Ludwigs-Universität Freiburg
             Technische Fakultät
            Institut für Informatik
      Lehrstuhl für Programmiersprachen

              16. September 2021
Luka Steinbach Dynamische Analyse von JavaScript durch Tests
Bearbeitungszeit

16. 06. 2021 – 16. 09. 2021

Gutachter

Prof. Dr. Peter Thiemann

Betreuer

Fernando Cristiani
Erklärung

Hiermit erkläre ich, dass ich diese Abschlussarbeit selbständig verfasst habe,
keine anderen als die angegebenen Quellen/Hilfsmittel verwendet habe und
alle Stellen, die wörtlich oder sinngemäß aus veröffentlichten Schriften ent-
nommen wurden, als solche kenntlich gemacht habe. Darüber hinaus erkläre
ich, dass diese Abschlussarbeit nicht, auch nicht auszugsweise, bereits für
eine andere Prüfung angefertigt wurde.

 Ort, Datum                               Unterschrift

                                      i
ii
Zusammenfassung

TypeScript ist eine Programmiersprache, die die JavaScript-Syntax erweitert
und immer mehr an Beliebtheit gewinnt. Um JavaScript-Module in Type-
Script sinnvoll zu nutzen, können so genannte Declaration Files verwendet
werden, die Beschreibungen zu den Typen der Funktionen und Objekte eines
JavaScript-Moduls enthalten. Diese Declaration Files müssen manuell erstellt
und gewartet werden, was zeitaufwendig sein und zu Fehlern führen kann.

Diese Arbeit befasst sich mit der automatischen Erstellung von Declaration
Files. Dafür werden Tests benutzt, die von den Entwicklern des JavaScript-
Moduls geschrieben wurden.

Mit diesem Ansatz wurden 1243 Declaration Files aus 7505 Modulen erzeugt,
von denen 55 keine Unterschiede im Vergleich zu manuell geschriebenen
Declaration Files aus dem Repository DefinitelyTyped aufwiesen.

                                     iii
iv
Inhaltsverzeichnis

1 Einleitung                                                                    1

2 Hintergrund                                                                    3
  2.1   JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . .     3
        2.1.1   NPM . . . . . . . . . . . . . . . . . . . . . . . . . . . .      3
        2.1.2   ECMAScript . . . . . . . . . . . . . . . . . . . . . . .         4
        2.1.3   Babel . . . . . . . . . . . . . . . . . . . . . . . . . . .      5
        2.1.4   Testen in JavaScript . . . . . . . . . . . . . . . . . . .       7
        2.1.5   Typen . . . . . . . . . . . . . . . . . . . . . . . . . . .      9
  2.2   TypeScript . . . . . . . . . . . . . . . . . . . . . . . . . . . .      10
        2.2.1   Typen . . . . . . . . . . . . . . . . . . . . . . . . . . .     11
        2.2.2   Declaration Files . . . . . . . . . . . . . . . . . . . . .     12
        2.2.3   DefinitelyTyped . . . . . . . . . . . . . . . . . . . . . .     13
  2.3   Jalangi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .   14

3 Ansatz                                                                        17
  3.1   Problemstellung . . . . . . . . . . . . . . . . . . . . . . . . . .     17
  3.2   Bisheriger Ansatz . . . . . . . . . . . . . . . . . . . . . . . . .     17
  3.3   Neuer Ansatz . . . . . . . . . . . . . . . . . . . . . . . . . . .      20
        3.3.1   Schritt 1: Herunterladen des Moduls . . . . . . . . . .         21
        3.3.2   Schritt 2: Instrumentierung des Moduls . . . . . . . .          21
        3.3.3   Schritt 3: Installation der Abhängigkeiten . . . . . . .        22

                                       v
3.3.4   Schritt 4: Generierung der Laufzeitinformationen . . .         23
        3.3.5   Schritt 5: Generierung des Declaration Files . . . . . .       24

4 Experimente                                                                  25
  4.1   Untersuchung der package.json der Module . . . . . . . . . . .         25
  4.2   Herunterladen der Module . . . . . . . . . . . . . . . . . . . .       27
  4.3   Instrumentierung der Module . . . . . . . . . . . . . . . . . .        28
  4.4   Installation von Abhängigkeiten . . . . . . . . . . . . . . . . .      29
  4.5   Generierung der Laufzeitinformationen . . . . . . . . . . . . .        30
  4.6   Generierung der Declaration Files . . . . . . . . . . . . . . . .      37
  4.7   Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . .    39

5 Evaluation der Ergebnisse                                                    41
  5.1   Vergleich mit gemeinsamen Modulen . . . . . . . . . . . . . .          44
  5.2   Vergleich mit allen Modulen . . . . . . . . . . . . . . . . . . .      45

6 Verwandte Arbeiten                                                           47

7 Fazit                                                                        51
  7.1   Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .   52

Bibliography                                                                   54

A Abbildungen                                                                  59

B Tabellen                                                                     71

                                      vi
1          Einleitung

JavaScript ist eine dynamisch typisierte, interpretierte und objektorientierte
Programmiersprache. Sie ist laut der Stack Overflow Entwicklerumfrage seit
mehreren Jahren die weltweit am häufigsten verwendete Programmierspra-
che [8, 9, 10, 11]. Ursprünglich wurde JavaScript für die Entwicklung von
kleinen Skripten zur Manipulation von Elementen auf Webseiten entwickelt.
Inzwischen wird JavaScript aber auch für große Anwendungen innerhalb
und außerhalb des Browsers eingesetzt. Die dynamische Typisierung der
Sprache kann hier die Entwicklung, durch zum Beispiel versteckte Fehler
und schlechte Lesbarkeit, erschweren. Aus diesem Grund hat Microsoft die
Programmiersprache TypeScript entwickelt, die JavaScript um statische Ty-
pisierung erweitert.

Die TypeScript-Syntax ist eine Obermenge der JavaScript-Syntax, was be-
deutet, dass bestehende JavaScript-Module in TypeScript verwendet werden
können. Um die JavaScript-Module mit Typen zu erweitern, können Entwick-
ler sogenannte Type Declaration Files schreiben. Diese ermöglichen es zum
einen eine falsche Benutzung des Moduls zu vermeiden und zum anderen den
Entwickler in Entwicklungsumgebungen, durch zum Beispiel intelligentere
Codenavigation und Autovervollständigung, zu unterstützen. Declaration
files für über 7500 JavaScript-Module werden im GitHub-Repository Defini-
telyTyped1 zur Verfügung gestellt und werden über NPM2 wöchentlich über

    1
        https://github.com/DefinitelyTyped/DefinitelyTyped
    2
        https://www.npmjs.com/

                                          1
100 Millionen Mal heruntergeladen und verwendet [13]. Diese Declaration
Files werden in den meisten Fällen manuell geschrieben, was vor allem bei
großen Modulen einen erheblichen Zeitaufwand bedeuten kann. Dies führt
außerdem dazu, dass bei neuen Versionen von manchen JavaScript-Modulen
erst nach längerer Zeit ein Declaration File für die Benutzung der neuen
Modulversion veröffentlicht wird. Durch das manuelle Schreiben kann es zu
Fehlern kommen, die bei der Aufnahme in das DefinitelyTyped-Repository
übersehen werden. Zusätzlich gibt es den Nachteil, dass Unstimmigkeiten
zwischen dem DeclarationFile und dem benutzten JavaScript-Modul nicht
vom TypeScript-Compiler erkannt werden, was zu fehlerhaften Verhalten
der Entwicklungsumgebung führen kann. Eine automatische Generierung der
Declaration Files könnte einige dieser Probleme vermeiden.

Ein bisheriger Ansatz für eine automatische Generierung der Declaration Files
basiert auf der dynamischen Analyse von Codebeispielen aus Readme-Dateien
von Modulen [17]. Auf diese Weise wurden 249 Declaration Files für 6029
DefinitelyTyped-Module erzeugt, was einem Anteil von 4,1% entspricht.

In dieser Arbeit wird ein neuer Ansatz zur Generierung von Declaration Files
untersucht und mit manuell geschriebenen Declaration Files aus DefinitelyTy-
ped und der bisherigen Methode verglichen. Der Ansatz dieser Arbeit basiert
auf der dynamischen Analyse von Tests von Modulen, die oft einen hohen
Informationsgehalt bezüglich der Verwendung des Moduls enthalten, ähnlich
wie Codebeispiele in Readme-Dateien von Modulen. Außerdem sind die Tests
in den meisten Fällen in einem Testskript in der package.json des Moduls
definiert, was einen einheitlichen Ausgangspunkt für die Tests darstellt.

                                     2
2          Hintergrund

2.1         JavaScript

JavaScript ist eine dynamisch typisierte, interpretierte und objektorientierte
Programmiersprache, die es dem Nutzer ermöglicht einfache Skripte zu schrei-
ben. Mittlerweile ist JavaScript die meistverwendete Programmiersprache
weltweit (siehe Abbildung 24) [11]. Ursprünglich wurde JavaScript für die
Verwendung in Browsern entwickelt, um die Struktur von Webseiten über
das DOM (Document Object Model) zu manipulieren. Moderne Frontend-
Frameworks, wie React, Angular und Vue.js, die auf JavaScript basieren,
werden immer mehr verwendet, um große und dynamische Webanwendungen
zu entwickeln [3]. Zusammen mit HTML und CSS bildet JavaScript die
Grundlage für moderne Webanwendungen.

2.1.1        NPM

Mit der JavaScript-Laufzeitumgebung Node.js1 wird JavaScript jedoch auch
zunehmend außerhalb des Browsers eingesetzt. Node.js ist plattformüber-
greifend und ermöglicht Entwicklern über NPM (ehemals Node Package
Manager) den Zugriff auf über 1,3 Millionen Module [12]. Module können
mit der NPM Command-Line-Schnittstelle (NPM CLI) über den Befehl npm
install heruntergeladen und installiert werden. Jedes NPM-Modul besitzt
eine package.json, die unter anderem Name, Version, Beschreibung, Autor,
    1
        https://nodejs.org/

                                      3
Softwareabhängigkeiten und Skripte des Moduls definiert (siehe Abbildung 1).
Scripte können vom Nutzer mit dem Befehl npm run  ausge-
führt werden und geben dem Entwickler die Möglichkeit komplexere Routinen
für den Benutzer leichter ausführbar zu machen. Häufige Anwendungsbeispie-
le sind Build-Scripte zur Kompilierung des Sourcecodes oder Testskripte zur
Ausführung der vom Entwicklern definierten Tests.

{
    "name": "beispiel-modul",
    "description": "",
    "version": "1.0.0",
    "scripts": {
      "test": "eslint && mocha test"
    },
    "repository": {
      "type": "git",
      "url": "https://github.com/beispiel-person/beispiel-modul.git"
    },
    "keywords": [],
    "author": "https://github.com/beispiel-person",
    "license": "MIT",
    "bugs": {
      "url": "https://github.com/beispiel-person/beispiel-modul/issues"
    },
    "homepage": "https://github.com/beispiel-person/beispiel-modul"
}

Abbildung 1: Beispiel für eine package.json - Die package.json de-
             finiert verschiedene Informationen eines Moduls. Für diese
             Arbeit relevante Informationen sind der Name (name), die
             Version (version), das Testskript (scripts.test) und die
             Repository-URL (repository.url).

2.1.2   ECMAScript

Den Sprachkern von JavaScript bildet das sognannte ECMAScript, das in der
ECMAScript Language Specification [2] definiert wird und die Syntax und
Semantik der Sprache standardisiert definiert. Einzelne Versionen des ECMA-
Script werden mit dem jeweiligen Veröffentlichungsjahr bezeichnet, so gibt es
zum Beispiel die ECMAScript-Versionen ECMAScript 2015 (ES2015 oder

                                     4
ES6) und ECMAScript 2016 (ES2016). Jede neue Version des ECMAScripts
erweitert den bestehenden Sprachkern um neue Funktionen wie zum Beispiel
beim ECMAScript 2015 die unterstützte Nutzung der JavaScript Object
Notation (JSON) oder im ECMAScript 2017 die Ergänzung von asynchronen
Funktionen [6].

2.1.3   Babel

Aufgrund der vielen verschiedenen Versionen von ECMAScript kann es zu
Problemen mit der Cross-Browser-Kompatibilität von JavaScript kommen.
Eine häufige Ursache ist die Verwendung von modernen JavaScript-Funktionen
in älteren Browsern oder älteren Browserversionen, die die neuen Versionen
und damit die neuen JavaScript-Funktionen nicht unterstützen. Moderne
JavaScript-Funktionen, die oft zu solchen Problemen führen, sind zum Beispiel
Arrow-Funktionen und Template-Strings [15].

Um die Cross-Browser-Kompatibilität für den Benutzer zu gewährleisten,
verwenden Entwickler oft das Tool Babel, das JavaScript-Code zu einer
bestimmten EMCAScript-Version kompiliert und so moderne JavaScript-
Funktionen durch alte ersetzt, ohne die Semantik des Programms zu verändern
(siehe Abbildung 2 und Abbildung 3).

                                     5
Vor Kompilierung:
1   var helloWorld = () => {
2       console.log("Hello World!")
3   }
4
5   helloWorld()

                           Nach Kompilierung:

1   "use strict";
2
3   var helloWorld = function helloWorld() {
4     console.log("Hello World!");
5   };
6
7   helloWorld();

Abbildung 2: Babel - Kompilieren von Code mit Arrow-Funktionen zu
             ES5-kompatiblem Code.

                           Vor Kompilierung:
1   var personName = "Max"
2   var personAge = 10
3   console.log(`${personName} is ${personAge} years old.`)

                           Nach Kompilierung:

1   "use strict";
2
3   var personName = "Max";
4   var personAge = 10;
5   console.log("".concat(personName, " is ").concat(personAge, " years
    ,→  old."));

Abbildung 3: Babel - Kompilierung von Code mit Template-Strings zu
             ES5-kompatiblem Code.

                                      6
2.1.4    Testen in JavaScript

Das Schreiben von Tests spielt bei der Softwareentwicklung eine sehr wichtige
Rolle und ist oft ein wesentlicher Bestandteil weitverbreiteter Methoden
wie Extreme Programming [14]. Tests können zum einen die Produktivität
erhöhen, da man nicht nach der Implementierung oder Änderung einzelner
Codebestandteile manuell testen muss, ob das gewünschte Ergebnis erreicht
wird. Stattdessen kann man einfach die Tests ausführen und so übersichtlich
fehlerhafte und funktionierende Bestandteile sehen. Dies erleichtert oft auch
die Fehlerbehebung, da Fehlerursachen leichter zu finden sind. Darüber hinaus
fungieren die Tests auch als eine Art Dokumentation, da sie oft zeigen, wie
die Nutzung des Programms beabsichtigt ist [16]. Diese Eigenschaft bildet
die Grundlage für den Ansatz diese Arbeit.

Der Artikel von Zaidman (2020) [23] präsentiert den aktuellen Stand für das
Schreiben von Tests in JavaScript für das Jahr 2021. Dieser wird im folgenden
zusammengefasst.

Grundsätzlich gibt es drei verschiedene Testarten: Unittests, Integrationstests
und End-to-End-Tests.

Bei Unittests werden funktionale Einheiten eines Programms getestet, indem
man eine Eingabe macht und überprüft, ob die Ausgabe dem erwarteten Wert
entspricht. Eine funktionale Einheit kann hierbei zum Beispiel ein ganzes
Modul, eine Funktion oder eine Klasse sein. Ein Beispiel für Unittests ist in
Abbildung 25 dargestellt.

Integrationstests bauen häufig auf Unittests auf und werden verwendet, um
die Interaktion von funktionalen Einheiten untereinander und die Interaktion
von funktionalen Einheiten mit zum Beispiel Datenbanken und Program-
mierschnittstellen (APIs) zu testen. Ein Beispiel hierfür ist in Abbildung 26
dargestellt.

End-to-End-Tests werden in der Regel bei Webanwendungen eingesetzt und

                                      7
testen das eigentliche Produkt, mit dem der Benutzer interagiert. End-to-
End-Tests ignorieren die interne Struktur der Anwendung und behandeln sie
wie eine Blackbox. Zu diesem Zweck werden häufig Browser ohne grafische
Benutzeroberfläche verwendet, wie zum Beispiel der Browser PhantomJS.
Je nach Testtool werden Tests auch in normalen Browsern oder in Node.js
ausgeführt.

Das Schreiben von Tests für Software ist eine bewährte Praxis und ist in
den meisten großen Repositories üblich. Von den 1000 meistgenutzten NPM-
Modulen haben 942 ein GitHub-Repository, wovon 92% ein nicht leeres, nicht
standardmäßiges Testskript in der package.json definiert haben (siehe Abbil-
dung 4a). Im Vergleich dazu haben von 7505 Modulen in DefinitelyTyped
6054 ein GitHub-Repository, wovon wiederum 80,3% ein nicht leeres, nicht
standardmäßiges Testskript in der package.json definiert haben (siehe Abbil-
dung 4b).

              B
                                                  B
                  8%
                                                      19.7%

                   92%                                         80.3%

                            A                                          A
       (a) Top 1000 NPM-Module                  (b) DefinitelyTyped-Module

                       A: Mit Testskript, B: Ohne Testskript

Abbildung 4: Module mit Testskript - Von 942 Modulen (mit einem
             GitHub-Repository) der Top 1000 NPM-Module haben 867
             ein nicht leeres, nicht standardmäßiges Testskript.
             Von 6054 DefinitelyTyped-Modulen haben 4864 ein nicht
             leeres, nicht standardmäßiges Testskript.

                                        8
Die bekanntesten Test-Frameworks sind Mocha, Tap, Jest, Tape, AVA und
tsd (siehe Abbildung 27). Mocha ist das bei weitem am häufigsten verwendete
Framework.

2.1.5   Typen

In den meisten Programmiersprachen haben Werte einen Typ, der angibt,
wie die Daten des Wertes zu interpretieren sind. JavaScript besitzt sieben
primitive Datentypen: string, number, bigint, boolean, undefined, symbol
und null. Außerdem gibt es den Typ object, mit dem zum Beispiel Arrays
umgesetzt sind. Wenn Methoden auf einem primitiven Datentyp aufgerufen
werden, erstellt JavaScript automatisch sogenannte Wrapper-Objekte für alle
primitiven Datentypen außer undefined und null. Diese Wrapper-Objekte
heißen dann String, Number, BigInt, Boolean oder Symbol und besitzen
Methoden, wie zum Beispiel valueOf(), die aufgerufen werden können [7].

Ein Typ kann in einen anderen umgewandelt werden, was als Typkonvertie-
rung bezeichnet wird. Die Regeln, die dieser Konvertierung zugrunde liegen,
werden von der jeweiligen Sprache bestimmt. Eine Typkonvertierung kann
explizit sein, wenn sie vom Entwickler vorgenommen wird. Andererseits kann
sie auch implizit sein, wenn sie vom Compiler beziehungsweise Interpreter
durchgeführt wird. Da JavaScript eine dynamisch typisierte Sprache ist, wer-
den implizite Typkonvertierungen häufig durchgeführt [21].

Implizite Typkonvertierungen sind ein kontroverses Thema, da sie sowohl Vor-
als auch Nachteile mit sich bringen. Der Hauptvorteil impliziter Typkonver-
tierungen besteht darin, dass sie Entwicklern die Programmierung erleichtern,
was Anfängern oft den Einstieg in JavaScript erleichtert. Allerdings können
implizite Typkonvertierungen auch dazu führen, dass der Code schwerer
zu lesen ist, da der Typ eines Wertes manchmal nicht eindeutig aus dem
Code hervorgeht. Darüber hinaus können implizite Typkonvertierungen zu
einer erhöhten Fehleranfälligkeit führen, da möglicherweise unbeabsichtigte

                                     9
Operationen auf einem Wert unbemerkt bleiben. Implizite Typkonvertierun-
gen können in JavaScript zu kontraintuitiven Eigenschaften führen, die zu
frustrierenden Problemen führen können (siehe Abbildung 5). Die genannten
Nachteile zeigen sich häufig vor allem in großen Codebasen [19].

1     0 == '0' // true
2
3     false == [] // true
4     false == ![] // true
5
6     !!null // false
7     null == false // false
8
9     null == undefined // true

Abbildung 5: Beispiele für unintuitive Eigenschaften von JavaS-
             cript - Unintuitive Eigenschaften von JavaScript werden
             meistens durch implizite Typkonvertierungen verursacht.

2.2     TypeScript

TypeScript ist eine von Microsoft entwickelte Programmiersprache, die die
bestehende JavaScript-Syntax erweitert. Die Syntax von TypeScript ist somit
eine Obermenge der JavaScript-Syntax. Wie in Abschnitt 2.1.5 beschrieben,
kann die dynamische Typisierung von JavaScript einige Nachteile haben,
die TypeScript durch die Erweiterung der JavaScript-Syntax mit optionaler
statischer Typisierung zu beheben versucht. Dies soll die Entwicklung großer
Codebasen erleichtern und die Produktivität erhöhen. TypeScript-Code wird
vom TypeScript-Compiler zu gewöhnlichem JavaScript-Code kompiliert. Dies
bedeutet, dass TypeScript mit allen JavaScript-Modulen kompatibel ist. Dar-
über hinaus führt die statische Typisierung zu strukturierterem Code, der für
den Entwickler besser lesbar ist und sich vorhersehbarer verhält als entspre-
chender JavaScript-Code. Außerdem können Entwicklungsumgebungen den
Entwickler im Code gezielter unterstützen. Dies ist zum Beispiel durch bessere

                                     10
Codenavigation und Autovervollständigung möglich, was die Produktivität
erhöhen kann.

2.2.1   Typen

Genau wie in JavaScript gibt es in TypeScript die primitiven Datentypen
string, number, bigint, boolean, undefined, symbol und null und den
nicht-primitiven Datentyp object. Hierbei wird wie in JavaScript nicht un-
terschieden, ob es sich um eine ganze Zahl oder eine Gleitkommazahl handelt.
Die primitiven Datentypen können bei einer Variablendefinition vermerkt
werden und führen bei einer Zuweisung der Variable mit einem falschen
Datentyp zu einem Kompilierungsfehler (siehe Abbildung 6).

1   // Primitive Datentypen 'string', 'number' und 'boolean'
2   var personName: string = 'Max'
3   var personAge: number = 10
4   var personIsAdult: boolean = false
5
6   var personSiblingNames: string[] = ['Julian', 'Emily']

 Abbildung 6: Primitive Datentypen string, number und boolean

Zusätzlich gibt es den Datentyp any, der verwendet werden kann, um jegliche
Kompilierungsfehler bei der Überprüfung des Typs der jeweiligen Variable zu
vermeiden (siehe Abbildung 28).

Für Funktionsparameter und -rückgabewerte können ebenfalls Typen fest-
gelegt werden, die bei einer falschen Benutzung der Funktion zu einem
Kompilierungsfehler führen (siehe Abbildung 29).

Um Typen für ein Objekt festzulegen, kann ein neuer Typ definiert werden.
Dieser kann die Struktur und Typen der Attribute des Objekts festlegen und
kann wie primitive Datentypen verwendet werden (siehe Abbildung 30).

                                    11
2.2.2      Declaration Files

JavaScript-Entwicklern stehen über NPM mehr als 1,3 Millionen Module zur
Verfügung, mit denen sie schnell und einfach kleine oder komplexere Module
mit JavaScript implementieren können. Diese NPM-Module können auch in
TypeScript verwendet werden. Hierbei fehlen allerdings die Vorteile in Ent-
wicklungsumgebungen, die reguläre TypeScript-Module mit sich bringen. Die
Erweiterung von JavaScript-Modulen mit Typdeklarationen ist mit sogenann-
ten Declaration Files möglich. Sie haben in der Regel die Dateierweiterung
.d.ts und enthalten declare-, interface- und export-Anweisungen, die
die Typen von Funktionsparametern, Funktionsrückgabewerten, Objekten,
Klassen und globalen Variablen deklarieren. Ein Beispiel für ein Declaration
File ist in Abbildung 7 abgebildet.

Um das Schreiben von Declaration Files zu erleichtern, hat Microsoft das
Tool dts-gen2 entwickelt, das als Ausgangspunkt für das Schreiben qualitativ
hochwertige Declaration Files dienen soll. Zur Generierung der Declaration
Files untersucht dts-gen die Objekte eines Moduls zur Laufzeit, um eine
Grundstruktur und teilweise auch Typen der Objekte des Moduls zu erken-
nen. Dies geschieht ohne die Verwendung von Quellcode, der das Modul
importiert und verwendet. Dies kann dazu führen, dass vielen Funktions-
parametern und -rückgabewerten der Wert any zugewiesen wird und auch
Teile des Moduls im generierten Declaration File deklariert werden, die vom
Entwickler des Moduls nicht zur öffentlichen Verwendung vorgesehen waren.
Selbst mit dts-gen kann das Schreiben von Declaration Files, insbesondere
für große JavaScript-Module, sehr zeitaufwändig sein und außerdem leicht zu
Fehlern führen, die vom TypeScript-Compiler bei der späteren Verwendung
nicht erkannt werden. Dies kann zu Fehlern in der Entwicklungsumgebung
und zu frustrierendem Debugging für den Benutzer führen.

  2
      https://github.com/microsoft/dts-gen

                                       12
helper.js
1    var someVar = 7;
2
3    function getArraySum(array) {
4        let sum = 0
5        for (let i = 0; i < array.length; i++) {
6            sum += array[i]
7        }
8        return sum
9    }
10
11   module.exports = {
12       getArraySum
13   };

                                 helper.d.ts

1    export function getArraySum(arr: number[]): number;
2    declare var someVar: number;

Abbildung 7: Beispiel für ein Declaration File - Für die exportier-
             te Funktion getArraySum wird der Funktionsparameter als
             Zahlenarray (number[]) und der Rückgabewert als Zahl
             deklariert. Die Variable someVar wird mit einer declare-
             Anweisung als Zahl deklariert, da sie nicht exportiert wird.

2.2.3   DefinitelyTyped

Da Entwickler von JavaScript-Modulen in den meisten Fällen die Declaration
Files für ihre Module nicht selber schreiben, wird das GitHub-Repository Defi-
nitelyTyped betrieben. Es enthält Declaration Files für über 7500 JavaScript-
Module, die von der Open-Source-Community manuell geschrieben und ge-
pflegt werden. Die Declaration Files werden über den bezeichner @types
auf NPM veröffentlicht. So kann man zum Beispiel mit dem Befehl npm
install –save-dev @types/jquery sehr leicht, die Declaration Files für
die JavaScript-Bibibliothek jQuery herunterladen. Über NPM werden die
Declaration Files von DefinitelyTyped wöchentlich über 100 Millionen Mal

                                     13
heruntergeladen und verwendet [13]. Um ein Declaration File für ein Modul
zu DefinitelyTyped hinzuzufügen, muss eine Pull-Request auf GitHub erstellt
werden. Diese wird von Mitwirkenden oder DefinitelyTyped-Maintainern
überprüft. Nach einer erfolgreichen Überprüfung werden die Änderungen der
Pull-Request zu DefinitelyTyped hinzugefügt.

2.3       Jalangi

Jalangi3 ist ein Framework zur Durchführung dynamischer Analysen von
Front-End- und Back-End-JavaScript. Jalangi ermöglicht es dem Benut-
zer, jede im Code ausgeführte Operation zu überwachen und sie in selbst
geschriebenen Analysen zu verwenden. Die Überwachung der ausgeführten
Operationen wird mit sogenannten Callback-Funktionen umgesetzt, die immer
dann ausgeführt werden, wenn die jeweilige Operation ausgeführt wird. Eine
Callback-Funktion kann nach Bedarf angepasst werden. Ein Beispiel für eine
solche Callback-Funktion ist getField(), die immer aufgerufen wird, wenn
auf eine Eigenschaft eines Objekts zugegriffen wurde. Ein weiteres Beispiel ist
die Callback-Funktion functionEnter(), die immer vor der Ausführung eines
Funktionskörpers aufgerufen wird. Insgesamt gibt es 28 Callback-Funktionen,
die dem Benutzer zur Verfügung stehen.

Um JavaScript-Code zu überwachen, muss er zunächst instrumentiert werden,
um ihn dann mit Jalangi und den benutzerdefinierten Analysen auszuführen.
Die Instrumentierung verändert im Idealfall nicht das Verhalten des Codes
bei der Ausführung mit Jalangi.

Allgemein gibt es drei Befehle die Jalangi zur Verfügung stellt: jalangi.js,
instrument.js und direct.js. Mit jalangi.js wird der übergebene Code
automatisch intern instrumentiert und direkt analysiert. Mit instrument.js
wird der übergebene Code nur instrumentiert und in einem Ausgabeordner
  3
      https://github.com/Samsung/jalangi2

                                       14
abgespeichert. Der Befehlt direct.js analysiert die angegebenen Dateien.
Diese müssen bereits instrumentiert sein, um analysiert zu werden [4].

Jalangi unterstützt offiziell die ECMAScript-Version 5.1 [5].

                                     15
16
3      Ansatz

3.1    Problemstellung

Für die Nutzung von JavaScript-Modulen in TypeScript können Declaration
Files geschrieben werden, die Informationen bezüglich der Typen von Funk-
tionen und Objekten eines Moduls enthalten. Das manuelle Schreiben von
Declaration Files ist zeitaufwendig und kann zu Fehlern führen. Gesucht wird
ein Verfahren, das für ein gegebenes Modul automatisch ein Declaration File
erstellen kann, das inhaltlich möglichst nah an der vom Autor vorgesehenen
Nutzung ist. Hierfür muss eine robuste Informationsquelle gefunden werden,
die Informationen bezüglich der Nutzung enthält. Diese Informationen müssen
analysiert und verarbeitet werden, um ein Declaration File zu erstellen.

3.2    Bisheriger Ansatz

Der bisherige Ansatz von Cristiani und Thiemann [17] basiert im Wesentli-
chen auf der dynamischen Analyse von Codebeispielen aus der Readme-Datei
eines Moduls zur Generierung von Declaration Files.

Readme-Dateien von JavaScript-Modulen sind in der Regel Markdown-
Dateien, die wichtige Informationen wie Konfigurations-, Installations- und
Betriebsanweisungen sowie Informationen über die Struktur des Moduls, die
Lizenzierung und den Prozess der Zusammenarbeit enthalten. Zum Zweck
der Betriebsanweisung werden häufig Codeblöcke mit einfachen Beispielen für

                                    17
die Verwendung des Moduls eingefügt, die dem Benutzer des Moduls einen
leichten Einstieg ermöglichen sollen.

Diese Codeblöcke werden bei diesem Ansatz mit Jalangi (siehe Abschnitt 2.3)
instrumentiert und dann ausgeführt, um Laufzeitinformationen zu sammeln.
Diese Laufzeitinformationen werden verwendet, um Declaration Files zu
generieren. Die Qualität der generierten Declaration Files ist hier von der
Ausführlichkeit der Codebeispiele abhängig, die der Entwickler des Moduls
zur Verfügung stellt. Dies kann man zum Beispiel beim NPM-Modul abs1
sehen (siehe Abbildung 8). Bei jeder Verwendung der Funktion abs() in

1         const abs = require("abs");
2
3         console.log(abs("/foo"));
4         // => "/foo"
5
6         console.log(abs("foo"));
7         // => "/path/to/where/you/are/foo"
8
9         console.log(abs("~/foo"));
10        // => "/home/username/foo"

Abbildung 8: Codebeispiele für Abs - Codebeispiele aus der Readme-
             Datei des NPM-Moduls abs

den Beispielen der Readme-Datei des Moduls ist der Funktionsparameter
nicht leer. Nach der Generierung der Laufzeitinformationen und des darauf
basierenden Declaration Files führt dies dazu, dass der Funktionsparameter
input nicht optional ist. Im manuell geschriebenen Declaration File von
DefinitelyTyped ist dies jedoch der Fall (siehe Abbildung 9). Hier liegt also
ein Fehler vor, weil ein Beispiel im Codeblock fehlt.

Eine weitere Einschränkung des Ansatzes besteht darin, dass manchmal
Codeblöcke in der Readme-Datei nicht zusammenhängend sind und zum
Beispiel durch Text unterbrochen werden. Da es keine einheitliche Methode
     1
         https://www.npmjs.com/package/abs

                                             18
Automatisch generiertes Declaration File
1   export = Abs ;
2   declare function Abs( input : string ) : string ;

                   Declaration File von DefinitelyTyped

1   declare function abs( input ?: string ) : string ;
2   export = abs ;

Abbildung 9: Declaration Files für Abs - Im automatisch generierten
             Declaration File ist der Funktionsparameter input nicht op-
             tional, anders als im Declaration File von DefinitelyTyped.

gibt, um zu unterscheiden, ob zwei Codeblöcke zusammengehören oder als
unabhängige Beispiele zu betrachten sind, kann dies zu unvollständigen Co-
debeispielen führen, die analysiert werden und eventuell Fehler erzeugen oder
zu unvollständigen Declaration Files führen.

Darüber hinaus gibt es einige Module, bei denen die Dokumentation und
damit auch die Code-Beispiele auf externe Websites ausgelagert sind, so dass
die Erzeugung von Declaration Files für diese Module nicht möglich ist.

Insgesamt wurden mit diesem Ansatz aus 6029 DefinitelyTyped-Modulen 249
Declaration Files generiert, was einem Anteil von 4,1% entspricht.

                                     19
DefinitelyTyped-
                                                                                    6029
         Module

   GitHub-Repository                                                         4974

         README-Datei                                                    4292

                       Codebeispiele                            2260

                   Funktionierende Beispiele              946

                         Laufzeitinformationen        436

                    Generierte Declaration Files     249

             Abbildung 10: Anzahl der analysierten Module für jede Phase der
                           Experiments mit der Readme-Methode - Für 249 von
                           6029 DefinitelyTyped-Modulen wurde ein Declaration File
                           generiert [17].

             3.3       Neuer Ansatz

             Der Ansatz dieser Arbeit, wie er im Überblick in Abbildung 31 dargestellt
             ist, basiert auf der dynamischen Analyse von Tests der Module zur Gene-
             rierung von Declaration Files. Tests sind ein wesentlicher Bestandteil in der
             Softwareentwicklung und können viele Informationen zur Benutzung eines
             Programms beinhalten. Außerdem werden Tests für JavaScript-Module in
             den meisten Fällen einheitlich in einem NPM-Script mit dem Namen test
             definiert, die mit dem Befehl npm run test ausgeführt werden können.

             Es wurden die 1000 meistgenutzten Module und 7505 DefinitelyTyped-Module
             auf ihre Verwendung von Tests über das NPM-Script test untersucht. Dies
             ergab, dass 942 der 1000 meistgenutzten Module und 4864 der 6054 der
             DefinitelyTyped-Module mit einem GitHub-Repository ein nicht leeres und
             nicht voreingestelltes Testskript besitzen. “Nicht voreingestellt” heißt hier, dass
             das Testskript nicht dem Testskript entspricht, das automatisch bei der Initia-
             lisierung eines Moduls vom NPM-Kommandozeilentool generiert wird (echo

                                                     20
\“Error: no test specified\” && exit 1). Die häufige Verwendung von
Testskripten macht die Verwendung von Tests zu einer vielversprechenden
Alternative zum bisherigen Ansatz als Informationsquelle für die Verwendung
von Modulen.

Der Ansatz dieser Arbeit ist im Repository ts-declaration-file-generator-
service2 implementiert und verwendet intern run-time-information-gathering-
tests3 zur Generierung der Laufzeitinformationen mit Tests.

3.3.1   Schritt 1: Herunterladen des Moduls

Bei der normalen Verwendung eines NPM-Moduls würde man den Befehl npm
install  beziehungsweise npm install @
ausführen, um das Modul herunterzuladen. Tests, Entwicklungsabhängigkei-
ten und andere Komponenten, die man für die übliche Verwendung nicht
benötigt, werden jedoch oft aus der .tar-Datei ausgeschlossen, die NPM
zum Herunterladen bereitstellt. Der Hauptgrund dafür liegt in der Verkür-
zung der Downloadzeit. Da Tests und Entwicklungsabhängigkeiten, wie zum
Beispiel Test-Frameworks, jedoch für den Ansatz dieser Arbeit benötigt
werden, muss ein alternativer Weg zum Herunterladen des Moduls gewählt
werden. Die package.json von NPM-Modulen beinhaltet unter dem Schlüs-
sel repository Informationen, wie eine Repository-URL. Diese ist in den
meisten fällen eine URL zu einem GitHub-Repository, die man in dem Be-
fehl git clone  benutzen kann um das gesamte Modul
herunterzuladen.

3.3.2   Schritt 2: Instrumentierung des Moduls

Da Jalangi nur JavaScript-Code der Version ES5.1 unterstützt, wird das Tool
Babel4 verwendet, um das jeweilige Modul zu ES5-kompatiblen JavaScript-
  2
    https://github.com/luka1199/ts-declaration-file-generator-service
  3
    https://github.com/luka1199/run-time-information-gathering-tests
  4
    https://babeljs.io/

                                    21
Code zu kompilieren. Dadurch können Fehler bei der Instrumentierung mit
Jalangi vermieden werden (siehe Abschnitt 4.3). Falls hierbei ein Fehler
auftritt, wird der unkompilierte Code des Moduls weiterverwendet.

Der ES5-kompatible Code wird daraufhin mit Jalangi instrumentiert. Hiebei
ist zu beachten, dass zu diesem Zeitpunkt noch keine externen Abhängigkeiten
installiert sind. Das heißt, dass nur der Code und die Tests des Moduls
instrumentiert sind und später analysiert werden.

3.3.3   Schritt 3: Installation der Abhängigkeiten

In diesem Schritt werden die externen Abhängigkeiten, die in der package.json
des Moduls definiert sind, mit dem Befehl npm install heruntergeladen.
Dazu gehören sowohl die normalen Abhängigkeiten unter dependecies als
auch die Entwicklungsabhängigkeiten, wie z. B. Test-Frameworks, die unter
devDependecies in der package.json definiert sind. Für Abhängigkeiten, die
nicht über NPM installiert werden können, gibt es keine einheitliche Methode
diese zu erkennen. Das bedeutet, dass Module mit solchen Abhängigkeiten
nicht weiterverwendet werden können.

Unter normalen Umständen kann von Jalangi instrumentierter Code mit
dem Jalangi-Befehl direct.js ausgeführt werden. Da im Anwendungsfall
dieser Arbeit jedoch in den meisten Fällen ein Test-Framework die Tests
ausführen muss, ist dies nicht möglich. Grundsätzlich wäre es möglich das
Test-Framework mit dem direct.js-Befehl auszuführen. Dies kann jedoch
bei manchen Test-Frameworks aus verschiedenen Gründen zu Problemen füh-
ren. Ein Beispiel dafür sind Test-Frameworks, die Child-Prozesse starten, um
die instrumentierten Tests isoliert auszuführen und somit Fehler verursachen,
da die instrumentierten Tests nicht über Jalangi ausgeführt werden.

Die Lösung, die hier angewendet wird, besteht darin, eine leere HTML-Datei
inklusive Analyse-Dateien zu instrumentieren. Aus der resultierenden HTML-

                                     22
Datei kann man dann den generierten JavaScript-Code extrahieren, der die
gesamte Jalangi-Umgebung inklusive der beigefügten Analysen beinhaltet.
Diese Datei wird in den node_modules-Ordner eingefügt und in allen in-
strumentierten Dateien des Moduls importiert. Der node_modules-Ordner
beinhaltet die Abhängigkeiten des Moduls. Dadurch ist in den instrumentier-
ten Dateien die globale Variable von Jalangi J$ definiert. Die globale Variable
J$ wird im nächsten Schritt von Jalangi verwendet, wenn die Analysen
durchgeführt werden.

3.3.4     Schritt 4: Generierung der Laufzeitinformationen

Generell werden Laufzeitinformationen von den Analysen bei der Ausfüh-
rung der Tests mit npm run test automatisch generiert. Um hier Fehler zu
vermeiden, werden zuvor Tools zur Codeanalyse wie zum Beispiel ESLint
oder StandardJS aus dem Testskript entfernt. Das resultierende Testskript
wird als __test__ gespeichert (siehe Abbildung 11). Dies hat den Grund,

{
    "scripts": {
      "test": "eslint && mocha",
      "__test__": "mocha"
    }
}

Abbildung 11: Entfernung von Analysetools - Das Skript test ist das
              ursprüngliche Testskript. Das Skript __test__ ist das ur-
              sprüngliche Testskript ohne Tools zur Codeanalyse.

dass Tools zur Codeanalyse bei der Ausführung auf instrumentierten Code in
den meisten Fällen Fehler erzeugen, da der instrumentierte Code nicht den
Stilnormen der Tools entspricht.

Daraufhin werden die Tests mit npm run __test__ ausgeführt, wodurch von
den Analysedateien Laufzeitinformationen in JSON-Format generiert werden.

                                      23
3.3.5      Schritt 5: Generierung des Declaration Files

Zur Generierung des Declaration Files mit den generierten Laufzeitinforma-
tionen wird das Tool ts-declaration-file-generator5 verwendet, wie es
von Cristiani und Thiemann [17] vorgestellt wurde.

  5
      https://github.com/proglang/ts-declaration-file-generator

                                       24
4          Experimente

Um die Effizienz und Limitationen des Ansatzes zu untersuchen, wurden
die Schritte des neuen Ansatzes, wie sie in Abschnitt 3.3 vorgestellt wurden,
für 7505 DefinitelyTyped-Module (Stand 13. Juni 2021) ausgeführt und an-
schließend evaluiert. Von Schritt zu Schritt wird die Anzahl der verwendeten
Module aufgrund von zum Beispiel fehlenden Informationen oder Fehlern bei
der Durchführung der Experimente kleiner.

Alle Ergebnisse der folgenden Experimente sind im GitHub-Repository bachelor-
results1 zu finden. Jegliche Tools, die zur Erstellung dieser Ergebnisse ver-
wendet wurden sind im GitHub-Repository bachelor-tools2 enthalten.

4.1         Untersuchung der package.json der Module

6054 der 7505 DefinitelyTyped-Modulen haben ein GitHub-Repository, was
80.7% entspricht. Die restlichen Module werden in den meisten Fällen auf
anderen Platformen wie GitLab, Bitbucket oder auf privaten Servern zur
Verfügung gestellt. Um zu evaluieren, wie viele der 6054 Module mit einem
GitHub-Repository über Tests verfügen, die für die Generierung der Declara-
tion Files verwendet werden können, wurden die Testskripte der Module aus
der package.json extrahiert. Dabei wurden auch Unterskripte ausgewertet,
die im Testskript ausgeführt werden (siehe Abbildung 12).

    1
        https://github.com/luka1199/bachelor-results/
    2
        https://github.com/luka1199/bachelor-tools/

                                         25
{
    "scripts": {
      "test": "npm run test:lint && npm run test:mocha",
      "test:lint": "eslint",
      "test:mocha": "mocha"
    }
}

Abbildung 12: Testskript mit Unterskripten - Die NPM-Skripte
              test:lint und test:mocha werden im Testskript ausge-
              führt. Dies resultiert in dem eingelesenen Testskript “eslint
              && mocha”.

Die extrahierten Testskripte wurden in zwei Gruppen unterteilt:

    • Gruppe A: Testskripte die leer sind, nicht in der package.json ent-
       halten sind oder dem standardmäßigen Testskript entsprechen, das bei
       der Initialisierung eines NPM-Moduls vom NPM-Kommandozeilentool
       generiert wird (echo \“Error: no test specified\” && exit 1).

    • Gruppe B: Definierte Testskripte

Ergebnisse

Hierbei fielen 1190 Module in die Gruppe A und 4864 Module in die Gruppe
B. Dies bedeutet, dass 80,3% der DefinitelyTyped-Module mit einem GitHub-
Repository ein definiertes Testskript besitzen und damit Tests besitzen, die
möglicherweise für die Generierung von Declaration Files verwendet werden
können. Die Anzahl der Vorkommen verschiedener Tools in den Testskripten
sind in den Tabellen 4-7 aufgelistet. Vor allem interessant für diese Arbeit
sind die am häufigsten verwendeten Test-Frameworks. Diese sind Mocha, Tap,
Jest, Tape und AVA.

                                      26
4.2           Herunterladen der Module

Um sicherzustellen, dass die verwendete Version eines Moduls mit der Versi-
on übereinstimmt, mit der das Declaration File des Moduls erstellt wurde,
wurden Informationen aus dem Declaration File verwendet. Die meisten
Declaration Files enthalten mehrere Kommentarzeilen, die unter anderem die
verwendete Modulversion, die Repository-URL, den Autor des Declaration
Files und die verwendete TypeScript Version enthält (siehe Abbildung 13).

1        //   Type definitions for amp 0.3
2        //   Project: https://github.com/visionmedia/node-amp
3        //   Definitions by: Vilim Stubičan 
4        //   Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5        //   TypeScript Version: 2.4

Abbildung 13: Kommentarzeilen in Declaration File - Beispiel für
              Kommentarzeilen im Declaration File des Moduls amp3

NPM folgt zur Standardisierung der Versionsbezeichnungen von Modulen
dem Versionierungsschema Semantic Versioning4 . Da die Versionsbezeich-
nungen in den Kommentarzeilen der Declaration Files von DefinitelyTyped
häufig nicht diesem Versionierungsschma folgen, werden diese zunächst nach
der Spezifikation von Semantic Versioning umgewandelt. Diese umgewan-
delte Versionsbezeichnung eines Moduls kann verwendet werden, um das
Veröffentlichungsdatum der Modulversion zu bestimmen. Hierfür wird der Be-
fehl npm view  time ––json verwendet (siehe Abbildung 32).
Mit dem entsprechenden Veröffentlichungsdatum kann der passende Commit
des Moduls identifiziert werden. Das Verfahren hierfür ist in Abbildung 33
dargestellt. Das Modul kann daraufhin mit git clone 
heruntergeladen und mit git checkout  auf dem Stand der
Version des Declaration Files gesetzt werden.
    3
        https://www.npmjs.com/package/amp
    4
        https://semver.org/

                                            27
Ergebnisse

Das Herunterladen wurde für 5448 Module erfolgreich abgeschlossen. Diese
Reduzierung der Anzahl ist darauf zurückzuführen, dass es sich bei vielen
der Repositories um sogenannte Monorepositories handelt, die viele kleine
Untermodule in Unterordnern enthalten. Diese sind für die weitere Verwen-
dung dieser Arbeit nicht brauchbar, da es keine einheitliche Struktur dieser
Monorepositories gibt. Sie wurden teilweise manuell aussortiert oder konn-
ten aufgrund der Struktur der URL der Unterordner des Repositories nicht
geclont werden.

4.3    Instrumentierung der Module

Bei der Instrumentierung wurde, wie in Abschnitt 3.3.2 beschrieben, das Tool
Babel verwendet, um den Code der Module in ES5-kompatiblen Code zu
konvertieren. Um die Effizienz der Verwendung von Babel bei der Instrumen-
tierung zu evaluieren, wurde die Instrumentierung für die DefinitelyTyped-
Module sowohl mit als auch ohne Babel durchgeführt. Bei der Instrumentie-
rung mit Babel wurde bei einem Kompilierungsfehler automatisch die un-
kompilierte Version des Moduls verwendet, um Verluste bei der Verwendung
von Babel zu vermeiden. Wichtig ist außerdem, dass die Instrumentierung
hierbei noch keine Analysedateien beinhaltet.

Ergebnisse

Instrumentierung ohne Babel

Die Instrumentierung ohne Babel wurde 2977 Mal erfolgreich abgeschlossen
und scheiterte 2469 Mal. Dies entspricht einer Erfolgsquote von 54,7%.

Die Fehler bei der Instrumentierung sind in den meisten Fällen auf modernen
JavaScript-Funktionen der JavaScript-Versionen ab ES6 zurückzuführen.

                                    28
Instrumentierung mit Babel

Die Instrumentierung mit Babel wurde 4530 Mal erfolgreich abgeschlossen
und scheiterte 916 Mal. Dies entspricht einer Erfolgsquote von 83,2% und
einer Verbesserung um 28,5%.

Die Anzahl der fehlgeschlagenen Instrumentierungen ist oft auf eine fehl-
geschlagene Kompilierung mit Babel zurückzuführen, die dazu führt, dass
moderne JavaScript-Funktionen nicht konvertiert werden und zu Fehlern in
Jalangi führen. Ein Beispiel, das häufig zu Fehlern bei der Kompilierung mit
Babel führt, ist die Verwendung von JavaScript-Syntaxerweiterungen wie
JSX5 , die in vielen Modulen für das React.js-Framework verwendet wird. Für
solche Fälle gibt es häufig Erweiterungen für Babel, die diese Probleme lösen,
aber die Erkennung der erforderlichen Erweiterungen für das jeweilige Modul
ist schwierig auf automatisierte Weise zu implementieren.

Generell kann in seltenen Fällen selbst eine erfolgreiche Kompilierung mit
Babel zu Fehlern beim Instrumentieren mit Jalangi führen.

4.4       Installation von Abhängigkeiten

Für das spätere Ausführen der Tests werden die Abhängigkeiten der Module
benötigt. Diese sind in der package.json des Moduls aufgelistet und können
mit dem Befehl npm install ausgeführt werden. Weitere benötigte Schritte
zur Installation können hierbei nicht beachtet werden, da diese häufig in
der Readme-Datei des Moduls angegeben sind, wodurch eine automatisierte
Extrahierung der weiteren Schritte nicht einheitlich möglich ist. Außerdem
wurde für die Installation ein Zeitlimit von 250 Sekunden festgelegt.

Dieses Experiment wird mit 4358 Modulen durchgeführt, für die die Instru-
mentierung erfolgreich war und für die die package.json gültig ist.
  5
      https://reactjs.org/docs/introducing-jsx.html

                                       29
36.4%
                                                 Zeitlimit überschritten
                                                 Nicht erfolgreich
                                                 Erfolgreich
                                         59.1%

          4.4%

Abbildung 14: Installation der Abhängigkeiten - 2577 Mal erfolgreich,
              1588 Mal Zeitlimit überschritten, 193 Mal nicht erfolgreich

Ergebnisse

Die Abhängigkeiten für 2577 der 4358 Module wurden erfolgreich installiert.
Bei 1588 der 4358 Module wurde das Zeitlimit von 250 Sekunden überschritten
und bei 193 der 4358 Module schlug die Installation fehl.

4.5     Generierung der Laufzeitinformationen

Zur Generierung der Laufzeitinformationen müssen die Tests der instrumen-
tierten Module ausgeführt werden. Um hierbei Auswirkungen der Instrumen-
tierung auf den Erfolg der Tests zu evaluieren, wurden zunächst die Tests
der Module im nicht instrumentierten Zustand ausgeführt. Die Ausführung
der Tests wurde hierbei einheitlich mit npm run test durchgeführt.

Daraufhin wurden die Tests der instrumentierten Module in vier verschiedenen
Varianten ausgeführt:

  1. Ausführung der Tests mit Codeanalyse-Tools und ohne Analysedateien

  2. Ausführung der Tests ohne Codeanalyse-Tools und ohne Analysedateien

  3. Ausführung der Tests mit Codeanalyse-Tools und mit Analysedateien

                                    30
4. Ausführung der Tests ohne Codeanalyse-Tools und mit Analysedateien

Der Erfolg der einzelnen Ausführungen kann hier an zwei Werten gemessen
werden. Der erste Wert ist die Anzahl der Ausführungen bei denen Fehler
aufgetreten sind. Der zweite Wert ist die Anzahl der nicht leeren Laufzeit-
informationen, die bei der Ausführung mit Analysedateien erzeugt wurden.
Für die Generierung der Declaration Files ist vor allem die Anzahl der nicht
leeren Laufzeitinformationen wichtig.

Die Entfernung der Codeanalyse-Tools wird wie in Abschnitt 3.3.4 beschrie-
ben durchgeführt und bei allen Ausführungen der Tests wurde ein Zeitlimit
von 100 Sekunden festgelegt.

Dieses Experiment wird mit 1922 Modulen durchgeführt, bei denen die In-
stallation der Abhängigkeiten erfolgreich war und die ein nicht leeres und
nicht voreingestelltes Testskript besitzen.

Ergebnisse

Ohne Instrumentierung

Die Ausführung der Tests auf die normalen, nicht instrumentierten Modu-
le war bei 1227 Modulen erfolgreich, bei 559 nicht erfolgreich und bei 136
Modulen dauerte die Ausführung länger als das Zeitlimit von 100 Sekunden.
Dies entspricht einer Erfolgsquote von 64,8%.

Die relativ hohe Anzahl an nicht erfolgreichen Tests ist auf mehrere Gründe
zurückzuführen.

Ein Hauptgrund war, dass bei einigen Modulen die für die Tests benötig-
ten Frameworks nicht in den devDependecies der package.json angegeben
waren. Dies führte dazu, dass sie bei der Ausführung der Tests fehlten, was
zu Fehlern führte. Dies wurde in späteren Versionen der Module teilweise
korrigiert.

Ein weiterer Grund war, dass bei manchen Modulen weitere Schritte vor

                                      31
5.7%

    29.6%
                                                 Zeitlimit überschritten
                                                 Nicht erfolgreich
                                                 Erfolgreich

                                         64.8%

Abbildung 15: Ausführung der nicht instrumentierten Tests - 1227
              Mal erfolgreich, 559 Mal nicht erfolgreich, 136 Mal Zeitlimit
              überschritten

der Ausführung von npm run test benötigt werden, die nicht im Testskript
angeben waren.

In seltenen Fällen waren außerdem Syntax-Fehler im Testskript vorhanden,
die zu Fehlern geführt haben.

Mit Instrumentierung, ohne Analyse

Die Ausführung der instrumentierten Tests ohne Analysedateien und mit
Codeanalyse-Tools war bei 797 Modulen erfolgreich, bei 1030 Modulen nicht
erfolgreich und bei 95 Modulen wurde das Zeitlimit von 100 Sekunden über-
schritten. Dies entspricht einer Erfolgsquote von 41,5%.

Die Ausführung der instrumentierten Tests ohne Analysedateien und ohne
Codeanalyse-Tools hingegen war bei 900 Modulen erfolgreich, bei 924 Mo-
dulen nicht erfolgreich und bei 98 Modulen wurde das Zeitlimit von 100
Sekunden überschritten. Dies entspricht einer Erfolgsquote von 46,8%.

Somit konnte die Erfolgsquote durch die Entfernung von Codeanalyse-Tools
um 5,3% verbessert werden. Dies zeigt jedoch auch, dass die Instrumentierung
mit Jalangi bei ungefähr der Hälfte der Module zu Fehlern führt.

                                    32
4.9%

                                       41.5%
                                                Zeitlimit überschritten
                                                Nicht erfolgreich
                                                Erfolgreich

    53.6%

Abbildung 16: Ausführung der instrumentierten Tests mit
              Codeanalyse-Tools, ohne Analyse - 797 Mal er-
              folgreich, 1030 Mal nicht erfolgreich, 95 Mal Zeitlimit
              überschritten

                5.1%

                                        46.8%
                                                Zeitlimit überschritten
                                                Nicht erfolgreich
                                                Erfolgreich
    48.1%

Abbildung 17: Ausführung der instrumentierten Tests ohne
              Codeanalyse-Tools, ohne Analyse - 900 Mal erfolgreich,
              924 Mal nicht erfolgreich, 98 Mal Zeitlimit überschritten

                                  33
Mit Instrumentierung, mit Analyse

Die Ausführung der instrumentierten Tests mit Analysedateien und mit
Codeanalyse-Tools war bei 355 Modulen erfolgreich, bei 1384 Modulen nicht
erfolgreich und bei 183 Modulen wurde das Zeitlimit von 100 Sekunden
überschritten. Dies entspricht einer Erfolgsquote von 18,5%.

Die Ausführung der instrumentierten Tests mit Analysedateien und ohne
Codeanalyse-Tools hingegen war bei 396 Modulen erfolgreich, bei 1353 Mo-
dulen nicht erfolgreich und bei 173 Modulen wurde das Zeitlimit von 100
Sekunden überschritten. Dies entspricht einer Erfolgsquote von 20,6%.

Die Erfolgsquote konnte hier also durch die Entfernung von Codeanalyse-
Tools um 2,1% verbessert werden.

Die Hinzunahme der Analysedateien führte generell zu einer starken Re-
duzierung der Erfolgsquote. Trotz dessen konnten mit Codeanalyse-Tools
für 1914 Module und ohne Codeanalyse-Tools für 1915 Module nicht leere
Laufzeitinformationen generiert werden.

Ob die Entfernung der Codeanalyse-Tools auch einen qualitativen Unterschied
macht, wird in Abschnitt 4.6 untersucht.

                                    34
9.5%
                              18.5%

                                            Zeitlimit überschritten
                                            Nicht erfolgreich
                                            Erfolgreich

             72%

Abbildung 18: Ausführung der instrumentierten Tests mit
              Codeanalyse-Tools, mit Analyse - 355 Mal er-
              folgreich, 1384 Mal nicht erfolgreich, 183 Mal Zeitlimit
              überschritten

              9%
                                20.6%

                                            Zeitlimit überschritten
                                            Nicht erfolgreich
                                            Erfolgreich

          70.4%

Abbildung 19: Ausführung der instrumentierten Tests ohne
              Codeanalyse-Tools, mit Analyse - 396 Mal erfolgreich,
              1353 Mal nicht erfolgreich, 173 Mal Zeitlimit überschritten

                                   35
Erfolgsquote nach Testtools

Ausschlaggebend für den Erfolg der instrumentierten Tests sind häufig die
verwendeten Tools. Diesbezüglich wurde bei der Ausführung der instrumen-
tierten Tests mit Analysedateien und Codeanalyse-Tools die Erfolgsquote der
verwendeten Tools untersucht (siehe Tabelle 1).

Interessant ist hier vor allem die Erfolgsquote von Mocha, da Mocha das am
häufigsten verwendete Tool der DefinitelyTyped-Module war. Mocha hatte
eine Erfolgsquote von 18,37%. Die unter “Andere” kategorisierten Tools hatten
die höchste Erfolgsquote von 34,21%. Dies ist darauf zurückzuführen, dass
es sich um kleine Tools handelt, die selten mit Unterprozessen arbeiten und
nicht sehr komplex sind, und somit seltener Fehler mit Jalangi verursacht
werden. Tests, die direkt mit Node.js ausgeführt wurden, hatten aus ähnlichen
Gründen eine relativ hohe Erfolgsquote von 27,41%. Make hatte die geringste
Erfolgsquote von 6,38%.

    Tool       Erfolgreich   Nicht erfolgreich   Timeout   Erfolgsquote
    Andere         26               46              4           34,21%
    Jasmine        11               24              4           28,21%
    Node          159              370             51           27,41%
    Tap            50              182             27           19,31%
    Mocha         174              695             78           18,37%
    Tape           31              133              8           18,02%
    grunt           8               68              3           10,13%
    make            6               79              9             6,38%

Tabelle 1: Erfolgsquote der am häufigsten verwendeten Tools - “An-
           dere” sind hier verschiedene Tools, die sehr selten verwendet
           werden. In der Regel sind diese nicht sehr große Tools. “Node” be-
           deutet hier, dass die Tests direkt von Node.js ausgeführt wurden
           und kein Framework die Tests ausführt. Häufig wurde hierbei die
           Assertion-Bibliothek von Node.js verwendet.

                                     36
4.6    Generierung der Declaration Files

Declaration Files wurden sowohl für Laufzeitinformationen, die mit Tests mit
Codeanalysetools generiert wurden, als auch für Laufzeitinformationen, die
mit Tests ohne Codeanalysetools generiert wurden, erstellt. In diesem Schritt
wurde nur untersucht, ob die Declaration Files nicht leer sind.

Ergebnisse

Mit Codeanalyse-Tools

Mit den Laufzeitinformationen für 1922 Module, die mit den Tests mit
Codeanalyse-Tools generiert wurden, konnten 1233 nicht leere Declaration
Files generiert werden. 662 Declaration Files waren leer und bei 27 gab es
Fehler bei der Generierung. Das bedeutet, dass für 64,2% der 1922 Module mit
benutzbaren Tests und erfolgreicher Installation Declaration Files generiert
werden konnten.

Ohne Codeanalyse-Tools

Mit den Laufzeitinformationen für 1922 Module, die mit den Tests ohne
Codeanalyse-Tools generiert wurden, konnten 1243 nicht leere Declaration
Files generiert werden. 653 Declaration Files waren leer und bei 26 gab es
Fehler bei der Generierung. Somit konnten für 64,7% der 1922 Module mit
benutzbaren Tests und erfolgreicher Installation Declaration Files generiert
werden.

Die Entfernung der Codeanalyse-Tools hat somit auch zu einer leichten Ver-
besserung der Qualität der Laufzeitanalyse-Dateien geführt.

Die Fehler bei der Generierung der Declaration Files sind häufig auf unvoll-
ständige Laufzeitinformationen zurückzuführen, zum Beispiel durch Über-
schreitung des Zeitlimits bei der Ausführung der Tests.

                                     37
Sie können auch lesen