Code automatisiert testen: Continuous Integration in TYPO3-Projekten
Der Begriff Continous Integration oder Kontinuierliche Integration geht auf einen Artikel des Softwarearchitektur-Experten Martin Fowler zurück. CI bezeichnet die Praxis, durch häufiges „Zusammenfügen“ von Komponenten eines Softwareprojekts eine konstant hohe Qualität sicherzustellen. Allgemeine Voraussetzung für CI ist in der Regel ein Versionskontrollsystem. Über entsprechende Werkzeuge kann dann automatisiert für jeden einzelnen Commit im Versionsverwaltungssystem überprüft werden, ob er auch den im Vorfeld gesetzten Qualitätsstandards entspricht. In diesem Artikel stehen die beiden Tools Jenkins und Travis im Mittelpunkt der Betrachtung.
In Webprojekten unterscheidet sich der Build-Prozess von dem in der klassischen Softwareentwicklung. Sofern der Quelltext eines TYPO3-Projekts oder einzelner TYPO3-Erweiterungen jedoch innerhalb eines Versionsverwaltungssystems organisiert ist, lässt sich auch mit solchen Projekten problemlos CI betreiben. (Sämtliche Codebeispiele und Quelltexte zu diesem Artikel und weiterführende Code-Beispiele finden sich auf Github ).
CI-Setup in TYPO3-Projekten
Typische Build-Schritte in einem TYPO3-Projekt könnten zum Beispiel sein:
- Ausführung von PHPUnit-Tests für eigene TYPO3-Extensions. Hierzu kann die TYPO3-Erweiterung phpunit genutzt werden.
- Erhebung von Code-Metriken durch statische Analyse von PHP-, HTML-, JavaScript- und CSS-Dateien. So kann der Code auf die Einhaltung von Programmier-Standards und auf häufige Programmierfehler überprüft werden. Dafür dienen Werkzeuge wie PHP_Codesniffer, JSHint oder CSSLint. Selbst für TypoScript gibt es ein Werkzeug, mit dem Quelltext auf die Einhaltung von Standards überprüft werden kann (http://t3n.me/typoscript-lint).
- Kompilierung von Zwischensprachen wie SASS oder LESS zu CSS; oder CoffeeScript oder TypeScript zu JavaScript.
Um diese einzelnen Schritte zu orchestrieren, werden außerdem häufig Build-Werkzeuge wie beispielsweise Ant, Phing oder Grunt eingesetzt. Für den Anfang genügen einfache Shell-Skripte.
Das Beispiel-Projekt enthält ein einfaches TYPO3-Projekt sowie eine Phing-Datei, die eine ganze Reihe von Build-Schritten steuert. Diese generieren einen Satz Log-Dateien, die später wiederum von einem CI-Werkzeug ausgewertet werden können. So führen beispielsweise fehlschlagende Unit-Tests oder eine Verschlechterung von Code-Metriken automatisch dazu, dass Berichte für die verantwortlichen Entwickler generiert werden. Um den Build-Prozess reproduzierbar zu machen, enthält das Projekt auch einen MySQL-Dump, der vor jedem Build neu eingespielt werden kann.
CI zum Selbstbauen
Der „Klassiker“ unter den Open-Source-CI-Werkzeugen ist Jenkins. Die Installation ist einfach, da für alle verbreiteten Betriebssysteme Installationspakete zur Verfügung stehen. Auch die Installation in einem bestehenden Servlet-Container wie Tomcat ist problemlos möglich.
Eine häufige Anforderung besteht darin, ein Softwareprojekt mit verschiedenen Konfigurationen zu testen. Bei einem TYPO3-Projekt könnte dies etwa bedeuten, dass mit verschiedenen PHP- und TYPO3-Versionen getestet werden soll, und zwar im Idealfall so, dass jede mögliche Kombination aus PHP- und TYPO3-Version einmal zum Zug kommt.
Um diese Vielzahl verschiedener Konfigurationen zu verwalten, nutzt unser Beispiel die Virtualisierungssoftware Vagrant. Dabei handelt es sich um ein komfortables Frontend für Oracles Virtualbox. Zur Konfiguration der virtuellen Maschinen kommt das Konfigurationsmanagement-Werkzeug Ansible zum Einsatz.
Listing 1 zeigt ein Beispiel für ein Vagrantfile. Darin wird die von Vagrant verwaltete virtuelle Maschine konfiguriert.
Konfiguration einer virtuellen Maschine mit Vagrant
VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "chef/debian-7.4" config.vm.provision "ansible" do |ansible| ansible.playbook = "provision.yaml" ansible.sudo = true end end
Listing 1
Innerhalb eines Verzeichnisses, in dem ein Vagrantfile existiert, reicht ein einfaches „vagrant up“ auf der Kommandozeile aus, um die entsprechende virtuelle Maschine zu starten. In diesem Fall handelt es sich um eine VM mit Debian 7, die anschließend mithilfe eines Ansible-Playbooks weiter konfiguriert wird.
Um nun diese Vielzahl an Konfigurationen zu testen, erstellt das Team in Jenkins einen sogenannten Multikonfigurations-Build. Bei solch einem Build können in Jenkins mehrere „Achsen“ angegeben werden. Im Beispiel wären das die zwei Achsen „PHP-Version“ und „TYPO3-Version“. Jenkins startet anschließend einen Build für jede mögliche Kombination aller Achsenwerte. Für einen Build lassen sich in Jenkins wiederum eine Reihe an Build-Schritten definieren, die darin bestehen können, Build-Werkzeuge aufzurufen oder einfache Shell-Skripte auszuführen.
Innerhalb eines Jenkins-Jobs kann das Entwickler-Team nun mit dem folgenden Fünfzeiler die konfigurierte virtuelle Maschine erstellen und den Build innerhalb dieser VM starten:
Jenkins: VM erstellen und Build starten
composer install vagrant up vagrant provision vagrant ssh -c 'cd /vagrant && bin/phing' vagrant suspend
Listing 2
Die üblichen Werkzeuge für statische Code-Analyse wie etwa JSHint oder CSSLint schreiben ihre Ergebnisse in XML-Dateien, die Jenkins im Anschluss an den Build auswertet. Speziell zu erwähnen ist hier das Jenkins-Plugin Violations, das verschiedene Report-Formate auswerten und aus mehreren Builds auch Berichte über die zeitliche Entwicklung der Code-Qualität generieren kann.
CI in der Cloud mit Travis
Speziell für Open-Source-Projekte hat sich in der letzten Zeit der Dienst Travis CI etabliert, der von dem gleichnamigen Berliner Unternehmen betrieben wird und sehr eng in Github integriert ist. Ein Travis-Build wird konfiguriert, indem das Entwickler-Team im Root-Verzeichnis eines Projekts eine Datei .travis.yml mit der notwendigen Konfiguration anlegt. In unserem Beispielprojekt könnte diese Konfigurationsdatei beispielsweise so aussehen:
Beispiel für eine Konfigurationsdatei .travis.yml
language: php php: [5.6, 5.5, 5.4, hhvm] env: - TYPO3_VERSION=master - TYPO3_VERSION=TYPO3_7-0-2 - TYPO3_VERSION=TYPO3_6-2-7 - TYPO3_VERSION=TYPO3_6-1-9 - TYPO3_VERSION=TYPO3_6-0-14 services: [mysql] install: - composer install - mkdir ${TRAVIS_BUILD_DIR}/src/typo3_src-${TYPO3_VERSION} && cd ${TRAVIS_BUILD_DIR}/src/typo3_src-${TYPO3_VERSION} && wget https://github.com/TYPO3/TYPO3.CMS/archive/${TYPO3_VERSION}.tar.gz && tar --strip-components=1 -xzf ${TYPO3_VERSION}.tar.gz before_script: - mysql -e 'CREATE DATABASE typo3;' - mysql typo3 < provision/database.sql script: - bin/phing
Listing 3
Um einen Travis-Build für ein Github-Projekt zu aktivieren,
genügt es, dass die .travis.yml vorhanden ist und ein entsprechender
Commit-Hook installiert wurde.
Dieser lässt sich auf Github pro Repository unter „Settings -> Webhooks & Services -> Services -> Travis CI“ konfigurieren. Anschließend wird sowohl für jeden neuen Push in das Repository als auch – besonders interessant – für jeden eingehenden Pull-Request ein Travis-Build gestartet. So können Pull-Requests bereits vor dem Merge automatisiert auf Funktionalität und Code-Qualität geprüft werden.
Fazit
Neben dem typischen Anwendungsfall, innerhalb eines CI-Durchlaufes Unit- und Integrationstests durchzuführen, stehen mit Werkzeugen wie PHPCodesniffer, JSHint oder CSSLint auch fortgeschrittene Möglichkeiten zur statischen Code-Analyse zur Verfügung, die über reine Funktionstests hinausgehen. CI ermöglicht es somit, ein Softwareprojekt automatisiert häufiger und intensiver zu testen, als es manuell möglich wäre. So können Teams funktionale Fehler oder Qualitätsmängel beheben, bevor fehlerhafte Software zum Kunden ausgeliefert wird.
Eine Fortführung der kontinuierlichen Integration ist die kontinuierliche Auslieferung, englisch Continuous Delivery. Dabei wird die kontinuierliche Integration noch um die Auslieferung der Software zum Kunden erweitert – also in diesem Fall das Deployment eines Projekts auf den Webserver. Im Idealfall werden mit dem Einsatz von CI beziehungsweise CD also alle glücklich: sowohl der Entwickler, weil Änderungen vollautomatisiert zum Kunden ausgeliefert werden, als auch der Kunde, der von besser funktionierender Software und kürzeren Entwicklungszyklen profitiert.