Sie sind auf Seite 1von 53

Continuous Integration

Release 1

STE

17.03.2014

Inhaltsverzeichnis

Einleitung 1.1 Versionsverwaltung . . . . 1.2 Unit Testing . . . . . . . . 1.3 Metriken . . . . . . . . . . 1.4 Refactoring . . . . . . . . . 1.5 Automatische Builds . . . . 1.6 Continuous Integration (CI)

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

1 1 1 2 2 2 2 3 3 4 7 8 15 15 19 19 21 22 29 29 30 45 45 45 49

Runde 1 2.1 Infrastruktur . . . . . . . . . . . . . . . . 2.2 Das Programm . . . . . . . . . . . . . . . 2.3 Entwickeln unter einer Versionsverwaltung 2.4 Erstellen von Unit-Tests . . . . . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

Runde 2 3.1 Automatische Builds . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Runde 3 4.1 CI Server Jenkins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2 Jobs anlegen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3 Jobs Kongurieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Runde 4 5.1 Refactoring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Aufteilen umfangreicher Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Runde 5 6.1 OOP-Refactoring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Methoden herausnehmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Suche

ii

KAPITEL 1

Einleitung

Dieses Tutorial soll einen Einstieg in die Aspekte Versionsverwaltung Unit Testing Metriken Refactoring Automatisierte Build Continuous Integration bieten. Es sind mittlerweile Grundelemente einer jeden (agilen) Softwareentwicklung und fllen den Begriff mit Inhalt.

1.1 Versionsverwaltung
Nicht nur in Teams und verteilten Standorten ist eine Verwaltung des Quellcodes von auerordentlicher Bedeutung. Das Nachvollziehen von nderungen, das eventuell notwendige Rckgngigmachen von nderungen gehrt heute zu jedem ordentlichen Programmierer dazu. In unserem Beispiel werden wir mit Hilfe der zentralen Softwareverwaltung Subversion die grundlegenden Prinzipien kennenlernen. Dazu gehrt z.B. auch die Absprache, an welchen Quellcode-Dateien ein Entwickler gerade arbeitet das Vermeiden von Konikten

1.2 Unit Testing


TDD (Test Driven Development) ist eine Vorgehensweise, die die Entwicklung des Programmcodes eng an das Testen des Quellcodes bindet. In einer extremen Sichtweise wird sogar der Test vor dem Schreiben des Quellcodes gelegt. Der Test deniert somit die Spezikation des Pichtenheftes. Unit Testing hat zum Ziel, den Quellcode stndig, wiederholbar und automatisiert zu testen. Jede nderung im bestehenden Quellcode kann sofort durch einen Test validiert werden; der Entwickler enthlt somit ein sofortiges Feedback ber seine nderungen. Das Tutorial verwendet als Unit-Testing-Tool das Open-Source-Programm NUnit; die Vorgehensweise kann aber leicht an andere Unit-Testing-Frameworks angepasst werden.

Continuous Integration, Release 1

1.3 Metriken
Metriken messen bestimmte Eigenschaften von Quellcode, z.B. Lines of Code, Zyklomatische Komplexitt, Vererbungstiefe, Kopplungsgrad von Objekten,... Mit diesen Werten sind anschlieend bestimmte Aussagen ber die Qualitt des Codes mglich. Schlechter Code kann anschlieend refaktorisiert werden.

1.4 Refactoring
Bestehender Quellcode ist hug noch nicht optimal. Dies uert sich z.B. in zu langen Methoden Lagerung des Quellcodes am falschen Ort Nicht-Beachten von allgemein anerkannten Programmierprinzipien schlechten Metriken Refactoring beschreibt Vorgehensweisen, wie man solche Probleme erkennt und behebt. Weiterhin muss natrlich sichergestellt sein, dass nach einem Refactoring der Quellcode immer noch die gleichen Testergebnisse zurckliefert.

1.5 Automatische Builds


Das Erstellen eines Programms bedeutet hug mehr Arbeitsschritte als das reine Compilieren des Quellcodes. So mssen evtl. weitere Compiliervorgnge angestoen werden Tests aufgerufen weren Datenbanken durch Skripte angepasst werden, Ausgabeordner fr das Compilat erstellt werden, Setup-Programme aufgerufen werden API-Dokumentationen aktualisiert werden. All diese Dinge knnten auch manuell angestoen werden. Hug werden aber diese Aufgaben nicht zeitnah ausgefhrt oder vielleicht sogar vergessen. Build-Tools knnen diese Aufgaben automatisieren, indem Sie einmal erstellt werden und dann lediglich aufgerufen werden. Sie dokumentieren damit gleichzeitig den rmeninternen Softwareentwicklungsprozess. In unserem Tutorial verwenden wir die Build-Tool MSBUILD und NAnt.

1.6 Continuous Integration (CI)


CI fhrt die oben genannten Einzelschritte unter einer einheitlichen Oberche zusammen und bereitet die Ausgabe der entsprechenden Teilvorgnge in einer optisch ansprechenden Weise auf. In unserem Tutorial verwenden wir den Open Source CI Server Jenkins;

Kapitel 1. Einleitung

KAPITEL 2

Runde 1

2.1 Infrastruktur
Der Aufbau der verwendeten Software in unserem Tutorial ist wie folgt: Visual Studio Zur Arbeit mit dem Subversion-Server muss in VStudio ein SVN-Plugin (ANKH-SVN) installiert werden. Sie nden das entsprechende Programm im Order CI auf dem tauschlaufwerk. Subversion-Server Der entsprechende Quellcode ist pro Gruppe in einem SVN-Repository gespeichert. Jede Gruppe hat ihr entsprechendes Login und hat RW-Zugriff auf das Repository. Nachdem Sie das obige Plugin installiert haben, erstellen Sie in Visual Studio ein neues Projekt aus den Subversion-Server heraus und speichern es auf dem lokalen Rechner.

Continuous Integration, Release 1

Weitere Tools werden zu gegebener Zeit in Visual Studio hinzugefgt.

2.2 Das Programm


Das Beispielprogramm ist sehr einfach gehalten. Das Programm berechnet die Kundengebhren eines ktiven Kunden in einem Videoladen und druckt diese aus. Es erhlt dafr die Filme, die ein Kunde ausgeliehen hat, sowie die Dauer der Ausleihe. Es berechnet dann die Gebhren und identiziert die Art des Films (normal, Kinder, Neuheit). Zustzlich berechnet das Programm Rabattpunkte, die davon abhngen, ob der Film eine Neuheit ist.

Kapitel 2. Runde 1

Continuous Integration, Release 1

//Movie.cs using System; namespace Before { public enum PriceCodes { Regular, NewRelease, Childrens } public class Movie { private string m_Title; private PriceCodes m_PriceCode; public Movie(string title, PriceCodes priceCode) { m_Title = title; m_PriceCode = priceCode; } public PriceCodes PriceCode { get {return m_PriceCode;} set {m_PriceCode = value;} } public string Title { get {return m_Title;} } } } //Rental.cs using System; namespace Fowler { public class Rental { private Movie m_Movie; private int m_DaysRented; public Rental(Movie movie, int daysRented) { m_Movie = movie; m_DaysRented = daysRented; } public int DaysRented { get {return m_DaysRented;} } public Movie Movie { get {return m_Movie;} } } }

2.2. Das Programm

Continuous Integration, Release 1

//Customer.cs using System; using System.Collections; namespace Fowler { public class Customer { private string m_Name; private ArrayList m_Rentals = new ArrayList(); public Customer(string name) { m_Name = name; } public string Name { get {return m_Name;} } public void AddRental(Rental arg) { m_Rentals.Add(arg); } //public string Statement() { //See the next page } } }

Die Statement-Methode in der Klasse Customer fhrt nun alle Berechnungen durch. Folgendes Sequenzdiagramm zeigt den prinzipiellen Ablauf:

public string Statement() { double totalAmount = 0; int frequentRenterPoints = 0; IEnumerator rentals = m_Rentals.GetEnumerator(); string result = "Rental record for " + m_Name + "\n"; while ( rentals.MoveNext() ) {

Kapitel 2. Runde 1

Continuous Integration, Release 1

double thisAmount = 0; Rental each = (Rental)rentals.Current; switch(each.Movie.PriceCode) { case PriceCodes.Regular: thisAmount += 2; if (each.DaysRented > 2) { thisAmount += (each.DaysRented - 2) * 1.5; } break; case PriceCodes.NewRelease: thisAmount += each.DaysRented *3; break; case PriceCodes.Childrens: thisAmount += 1.5; if (each.DaysRented > 3) { thisAmount = (each.DaysRented - 3) * 1.5; } break; } frequentRenterPoints++; if ((each.Movie.PriceCode == PriceCodes.NewRelease) && (each.DaysRented > 1)) { frequentRenterPoints ++; } result += "\t" + each.Movie.Title + "\t" + thisAmount.ToString() + "\n"; totalAmount += thisAmount; } result += "Amount owed is " + totalAmount.ToString() + "\n"; result += "You earned " + frequentRenterPoints.ToString() + " frequent renter points."; return result; }

2.3 Entwickeln unter einer Versionsverwaltung


Die Entwicklung mit/ohne Versionsverwaltung unterscheidet sich zunchst nicht wesentlich. Es ist lediglich zu beachten, dass jegliche nderungen zunchst nur an der lokalen Kopie ausgehrt werden. Ein nderung im zentralen Versions-Repository muss durch den Entwickler mit Hilfe eines Commits angestoen werden. Bei einer Arbeit im Team wei der einzelne Entwickler nicht, welche teile des Quellcodes zur Zeit von seinen Kollegen bearbeitet wird. Um sein lokales Projekt auf dem aktuellen Stand zu halten, muss er deshalb selbts hg Updates aus dem zentralen Repository in seine lokale Kopie einspielen. Hat er in der Zwischenzeit gleiceh Quellode-Teile wie ein anderer Entwickler bearbeitet, fhrt dies automatisch zu Konikten im Quellcode, die dann manuell gelt werden mssen. Diese rgernisse fhren im Alltag aber eher dazu, dass sich Entwickler genau absprechen, wer welche Codeteile bearbeitet. Es ist damit eher von Vortiel als von Nachteil.

2.3. Entwickeln unter einer Versionsverwaltung

Continuous Integration, Release 1

In unserem Beispiel wollen wir in den bestehenden Code einige Unit-Tests einbauen.

2.4 Erstellen von Unit-Tests


NuGet Informieren Sie sich ber die NuGet-Pakteverwaltung von VisualStudio Das Test-Framework NUnit kann auf verschiedene Weise installiert werden. Ein fr das weitere Fortschreiten des Projektes sinnvolles Vorgehen ist es, mit Hilfe des NuGet-Paketmanagers die notwendigen Dateien automatisch in das VStudio-Projekt zu integrieren. Neben der Installation des NUnit-Frameworks muss dies als Verweis in die Solution mit eingebunden werden. Ein using-Statement erlaubt anschlie0end den Zugriff auf die Funktionalitten des TestFrameworks. Fr das Ausfhren der Tests ist allerdings dennoch die manuelle Installation des Nunit-Frameworks notwendig. Bemerkung: Beschreiben Sie die wesentliche Funktionsweise eines Unit-Test-Frameworks.

Kapitel 2. Runde 1

Continuous Integration, Release 1

Zum Testen der korrekten Funktionalitt sollen einige Demo-Objekte von Filmen, Kunden und Ausleihvorgngen 2.4. Erstellen von Unit-Tests 9

Continuous Integration, Release 1

erzeugt werden. Anschlieend wird in einem ersten Test berprft, ob die richtigen Daten zurckgeliefert werden.
using System; using NUnit.Framework; namespace StartingPoint { /// <summary> /// Unit tests for StartingPoint project. /// </summary> [TestFixture] public class UnitTests { /* Fields */ // Movies Movie m_Cinderella; Movie m_StarWars; Movie m_Gladiator; // Rentals Rental m_Rental1; Rental m_Rental2; Rental m_Rental3; // Customers Customer m_MickeyMouse; Customer m_DonaldDuck; Customer m_MinnieMouse; /* Methods */ [SetUp] public void Init() { // Create movies m_Cinderella = new Movie("Cinderella", PriceCodes.Childrens); m_StarWars = new Movie("Star Wars", PriceCodes.Regular); m_Gladiator = new Movie("Gladiator", PriceCodes.NewRelease); // Create m_Rental1 m_Rental2 m_Rental3 rentals = new Rental(m_Cinderella, 5); = new Rental(m_StarWars, 5); = new Rental(m_Gladiator, 5);

// Create customers m_MickeyMouse = new Customer("Mickey Mouse"); m_DonaldDuck = new Customer("Donald Duck"); m_MinnieMouse = new Customer("Minnie Mouse"); } [Test] public void TestMovie() { // Test title property Assert.AreEqual("Cinderella", m_Cinderella.Title); Assert.AreEqual("Star Wars", m_StarWars.Title); Assert.AreEqual("Gladiator", m_Gladiator.Title);

10

Kapitel 2. Runde 1

Continuous Integration, Release 1

// Test price code Assert.AreEqual(PriceCodes.Childrens, m_Cinderella.PriceCode); Assert.AreEqual(PriceCodes.Regular, m_StarWars.PriceCode); Assert.AreEqual(PriceCodes.NewRelease, m_Gladiator.PriceCode); }

Der entscheidende Test wird aber das berprfen der Statement()-Methode sein. Bemerkung: Aufgabe berlegen Sie, wie man die Statement-Methode testen knnte.
[Test] public void TestCustomer() { // Test Name property Assert.AreEqual("Mickey Mouse", m_MickeyMouse.Name); Assert.AreEqual("Donald Duck", m_DonaldDuck.Name); Assert.AreEqual("Minnie Mouse", m_MinnieMouse.Name); // Test AddRental() method - set up for test m_MickeyMouse.AddRental(m_Rental1); m_MickeyMouse.AddRental(m_Rental2); m_MickeyMouse.AddRental(m_Rental3); /* At this point, the structure of the program begins getting in the * way of testing. Rentals are imbedded in the Customer object, but * there is no property to access them. They can only be accessed * internally, by the Statement() method, which imbeds them in the * text string passed as its return value. So, to get these amounts, * we will have to parse that value. */ // Test the Statement() method string theResult = m_MickeyMouse.Statement(); // Parse the result char[] delimiters = "\n\t".ToCharArray(); string[] results = theResult.Split(delimiters); /* The results[] array will have the following elements: [0] = junk * [1] = junk * [2] = title #1 * [3] = price #1 * [4] = junk * [5] = title #2 * [6] = price #2 * [7] = junk * [8] = title #3 * [9] = price #3 * [10] = "Amount owed is x" * [11] = "You earned x frequent renter points." * * We will test the title and price elements, and the total * and frequent renter points items. If these tests pass, then * we know that AddRentals() is adding rentals to a Customer * object properly, and that the Statement() method is * generating a statement in the expected format. */

2.4. Erstellen von Unit-Tests

11

Continuous Integration, Release 1

// Test the title and price items Assert.AreEqual("Cinderella", results[2]); Assert.AreEqual(3, Convert.ToDouble(results[3])); Assert.AreEqual("Star Wars", results[5]); Assert.AreEqual(6.5, Convert.ToDouble(results[6])); Assert.AreEqual("Gladiator", results[8]); Assert.AreEqual(15, Convert.ToDouble(results[9])); }

2.4.1 Ausfhren der Tests


In unserem Beispiel wurden die Tests innerhalb des Projektes deniert. In greren Projekten wrde ein eigenes Testprojekt deniert werden, welches das zu testende Projekt referenziert. Mit der Installation des NUnit-Frameworks wird ein Testprogramm mitgeliefert; es bendet sich im bin-Verzeichnis. Nach dem erfolgreichen Compilieren des Quellcodes und dem Aufruf von nunit erscheint folgende grasche Oberche.

Bei erfolgreichen Tests frbt sich der Balken grn.

12

Kapitel 2. Runde 1

Continuous Integration, Release 1

2.4.2 Commit der nderungen


Fgen Sie die nderungen des Quellcodes auch in das zentrale Repository hinzu, indem sie im Projektexplorer von VStudio CommitChanges anklicken.

2.4. Erstellen von Unit-Tests

13

Continuous Integration, Release 1

Der SVN-Client analysiert die nderungen und trgt sie im zentralen Repository ein.

14

Kapitel 2. Runde 1

KAPITEL 3

Runde 2

3.1 Automatische Builds


Bisher erfolgt ein neuer Build des Programms durch Drcken der Taste F5 innerhalb von VisualStudio auf dem Entwicklerrechner. Dies ist allerdings kein deniertes Vorgehen, da in Abhngigkeit von Betriebssystem, .NET-Plattform und Versionsstnden von Fremd-Libraries unterschiedliche Rahmenbedingungen herrschen knnen. Alle Aufgaben, die fr einen erfolgreichen Buildvorgang vorzunehmen sind, sind am besten in Skripten zu zentralisieren. Fr diese Aufgabe wurden bereits vor mehreren Jahrzehnten eigene Programme entwickelt. Wichtige Vertreter sind ANT, NANT, Maven, .... Microsoft verfgte lange Jahre ber eine proprietre Build-Engine, bis Sie sich vor vielen Jahen entschloss, mit Hilfe von MSBUILD einen von VisualStudio getrennten Build-Mechanismus zu entwickeln, der auch fr Aufgaben auerhalb von VisualStudio verwendet werden kann. In unserem Beispiel werden wir aufgrund vorhandener Kennnisstnde das Tool NANT benutzen, auch wenn sich natrich fr ein VisualStudio-Projekt msbuild besser eignen wrde. Intern bergibt VisualStudio seine Build-Aufgaben an msbuild.

3.1.1 NANT
NANT ist ein OpenSource.Projekt und lehnt sich in seiner Arbeitsweise an das aus der Java-Welt bekannte ANT an. Gesteuert wird NANT mit Hilfe eines Kongurationsles default.build, in dem alle Aufgaben deniert werden, die NANT ausfhren soll. Die Aufgaben (task) werden dabei mit Hilfe von targets organisiert.
<?xml version="1.0" encoding="utf-8" ?> <project name="Startingpoint"> <target name="build"> <exec program="msbuild.exe" basedir="c:\Windows\Microsoft.NET\Framework64\v4.0.30319\" commandline="Pfad zur Solution\startingpoint.sln"/> </target>

</project>

Der Aufruf von nant build fhrt dann zum Ausfhren des Buildvorganges.

15

Continuous Integration, Release 1

3.1.2 MSBUILD
Die Arbeit mit msbuild gestaltet sich noch einfacher. Der Buildprozess kann dadurch gestartet werden, dass man dem Konsolenprogramm msbuild.exe den Pfad zur Solution bergibt.
c:\Windows\Microsoft.NET\Framework64\v4.0.30319\msbuild.exe Pfad_zur_Solution.sln

3.1.3 NUnit-Integration
Grundstzlich kann NUnit sowohl in NANT als auch in MSBUILD integriert werden. In unserem Beispiel werden wir die Integration mit Hilfe des NANT-Tools zeigen. Eine Kongurationsdatei von NANT kann verschiedene Tasks besitzen, die auch selbstndig voneinander ausgerufen werden knnen.

<?xml version="1.0" encoding="utf-8" ?> <project name="Startingpoint"> <target name="build"> <exec program="msbuild.exe" basedir="c:\Windows\Microsoft.NET\Framework64\v4.0.30319\" commandline="C:\temp\fowler\startpoint\startingpoint.sln"/> </target> <target name="test"> <exec program="c:\Program Files (x86)\NUnit 2.6.3\bin\nunit-console.exe" commandline="C:\temp\fowler\startpoint\bin\Debug\StartingPoint.exe /xml=C:\temp\fowler\ </target> </project>

Der Parameter /xml gibt den Dateinamen an, in der NUnit seine Ausgaben schreiben soll. Diese Datei wird von uns spter noch ausgewertet werden. Der Aufruf des Test-Targets erfolgt dann wie folgt.

16

Kapitel 3. Runde 2

Continuous Integration, Release 1

Microsoft Windows [Version 6.3.9600] (c) 2013 Microsoft Corporation. Alle Rechte vorbehalten. C:\temp\fowler\StartPoint>c:\temp\nant\bin\NAnt.exe test NAnt 0.92 (Build 0.92.4543.0; release; 09.06.2012) Copyright (C) 2001-2012 Gerry Shaw http://nant.sourceforge.net Buildfile: file:///C:/temp/fowler/StartPoint/default.build Target framework: Microsoft .NET Framework 4.0 Target(s) specified: test

test: [exec] [exec] [exec] [exec] [exec] [exec] [exec] [exec] [exec] [exec] [exec] [exec] [exec] [exec] [exec] [exec] NUnit-Console version 2.6.3.13283 Copyright (C) 2002-2012 Charlie Poole. Copyright (C) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov. Copyright (C) 2000-2002 Philip Craig. All Rights Reserved. Runtime Environment OS Version: Microsoft Windows NT 6.2.9200.0 CLR Version: 2.0.50727.8000 ( Net 3.5 ) ProcessModel: Default DomainUsage: Single Execution Runtime: net-3.5 ... Tests run: 3, Errors: 0, Failures: 0, Inconclusive: 0, Time: 0,21134 9783145239 seconds Not run: 0, Invalid: 0, Ignored: 0, Skipped: 0

BUILD SUCCEEDED Total time: 3.2 seconds.

3.1.4 Metrics-Integration
Installation In VStudio 2012 fehlte das Konsolenprogramm metrics.exe. Es muss in das Verzeichnis c:Program Files (x86)Microsoft Visual Studio 11.0Team ToolsStatic Analysis ToolsFxCop kopiert werden Der Aufruf erfolgt mit metrics /f:metrics.exe /o:C:tempMetricsresult.xml

3.1.5 Was soll das ?


Berechtigterweise muss man sich fragen, warum man all diesen Aufwand treibt, wenn man doch all diese Dinge auch ber VisualStudio htte erreichen knnen. Die Antwort liegt in Tatsache, dass man heutzutage als Entwickler kein Einzelkmpfer ist. Der Build und die darum liegenden Ttigkeiten mssen zentralisiert und automatisiert werden. Es ist Zeit fr den CI Server.

3.1. Automatische Builds

17

Continuous Integration, Release 1

18

Kapitel 3. Runde 2

KAPITEL 4

Runde 3

4.1 CI Server Jenkins


Jenkins (http://jenkins-ci.org) ist ein Fork des CI-Servers Hudson. Es gibt ihn fr verschiedene Betriebssysteme, darunter auch Windows. Das msi-Paket sollte man als Administrator installieren; ein Installationspfad auerhalb von Program Files hat sich zumindest auf meinem Windows 8 - System als ntzlich erwiesen.

Jenkins ist ber Plugins exibel kongurierbar; in unserem Falle werden wir folgende Plugins installieren:

19

Continuous Integration, Release 1

NUnit: Zum Rendern der NUnit-Ausgabe MSBUILD: Zum Builden von Solution-Files NANT: Zum Anstoen weiterer Funktionalitten VStudio Code Metrics: Zum Erzeugen einfacher Metriken per metrics.exe Diese Plugins mssen global installiert und konguriert werden.

20

Kapitel 4. Runde 3

Continuous Integration, Release 1

Weitere Kongurationen werden dann innerhalb eines Projekte vorgenommen. Weiterhin mssen noch Benutzer mit entsprechenden Zugriffsberechtigungen auf jeweilige Projekte angelegt werden.

4.2 Jobs anlegen


Das Anlegen eines Projektes erfolgt in Jenkins unter Neues Item anlegen

4.2. Jobs anlegen

21

Continuous Integration, Release 1

4.3 Jobs Kongurieren


In unserem beispiel sollen die bisher lokal ausgefhrten Bild-Schritte nun durch Jenkins realisiert werden. Dazu sind die Schritte Buildverfahren sowie Post-Build-Aktion zu kongurieren.

22

Kapitel 4. Runde 3

Continuous Integration, Release 1

Buildverfahren denieren dabei die Aktionen, die ausgefhrt werden mssen. Post-Build-Aktionen denieren dabei, was mit den Ergebnissen der Build-Verfahren geschehen soll. In unserem Beispiel werden die Ergebnisse der NANT und Metrics-Ausgaben (XMl-Dateien) ber einen Publisher grasch aufbereitet und in die bersichtsseite des jeweiligen Buildvorgangs dann integriert. Voraussetzung dafr war natrlich das Installieren der jeweiligen PublisherPlugins in Jenkins zuvor.

Falls alles klappt, sieht anschlieend die Startseite eines Projektes wie folgt aus.

4.3. Jobs Kongurieren

23

Continuous Integration, Release 1

Nach dem Klicken auf einen jeweiligen Buildvorgang werden Details dargestellt.

4.3.1 Aufgaben:
Erstellen API-Dokumentation Das Programm doxygen (http://www.doxygen.org) erstellt aus dem Quellcode eines Programms eine APIDokumentation. Der Ablauf der lokalen Erstellung ist wie folgt: Denieren eines Kongurationsles mit Hilfe eines GUI-Tools (doxywizard)

24

Kapitel 4. Runde 3

Continuous Integration, Release 1

Erstellen eines API-Dokumentation durch Aufruf von

4.3. Jobs Kongurieren

25

Continuous Integration, Release 1

Beachte In Jenkins muss zur Ausgabe der HTML-Publisher-Plugin instaliert werden Aufgabe Erstellen sie eine lokal ablauffhige Lsung. Passen Sie dann die entsprechende Konguration an und implementieren Sie diese auf dem CI Server Jenkins. Doxygen ist auf dem CI Server installiert. Zum Ablauf der Erstellung muss natrlich ein neues Target innerhalb des NANT-Buildles erstellt werden.

Erstellen Installer Innosetup hnlich wie bei Doxygen kann man mit Hilfe des Programms Innnosetup einen Installer erstellen lassen. Der Ablauf ist prinzipiell der gleich wie bei doxygen. Mit Hilfe eines Wizards wird ein Kongurationsle erstellt, welches dann ber ein Konsolenprogramm aufgerufen wird. Dieser Aufruf muss dann in gleicher Weise auf dem CI Server abgebildet werden. Das Konsolenprogramm fr den Aufruf des installers heit iscc.exe. Dies erwartet als Kommandozeilenparameter die bergabe eines Kongurationsles. Die Vorgehensweise auf der CI-Server-Seite ist die gleiche wie beim Erstellen der Doku.

26

Kapitel 4. Runde 3

Continuous Integration, Release 1

Aufgabe Erstellen sie eine lokal ablauffhige Lsung. Passen Sie dann die entsprechende Konguration an und implementieren Sie diese auf dem CI Server Jenkins. Innosetup (iscc.exe) ist auf dem CI Server installiert. Zum Ablauf der Erstellung muss natrlich ein neues Target innerhalb des NANT-Buildles erstellt werden. Darstellen von Artefakten Hug haben Build-Prozesse auch Ergebnisse in Dateiform. Bei einem Installer wird z.B. am Ende eine Datei setup.exe erzeugt, die man gerne innerhalb des Ci-Servers dann anzeigen wrde. Jenkis kennt fr diese Aufgabe die sog. Artefact-Plugins, die solche Dateien dann in der Projektoberche anklickbar machen. In unserem Falle wurde das Artifact Deployer Plugin verwendet, um die Datei setup.exe zu verlinken. 4.3. Jobs Kongurieren 27

Continuous Integration, Release 1

Aufgabe Bilden sie die Ausgabe der Datei setup.exe mit Hilfe des Plugins ab.

28

Kapitel 4. Runde 3

KAPITEL 5

Runde 4

5.1 Refactoring
Zitat Refactoring (auch Refaktorierung oder Restrukturierung) bezeichnet in der Software-Entwicklung die manuelle oder automatisierte Strukturverbesserung von Quelltexten unter Beibehaltung des beobachtbaren Programmverhaltens. Dabei sollen die Lesbarkeit, Verstndlichkeit, Wartbarkeit und Erweiterbarkeit verbessert werden, mit dem Ziel, den jeweiligen Aufwand fr Fehleranalyse und funktionale Erweiterungen deutlich zu senken. Refactoring ist ein zentraler Bestandteil der Agilen Softwareentwicklung. Dort wird meist von kontinuierlichem Refactoring [1] oder kompromisslosem Refactoring [2] gesprochen. Refactoring ist in der agilen Softwareentwicklung wie Kodieren oder Modultesten ein integraler Bestandteil des Softwareentwicklungsprozesses und nicht auf bestimmte Zeiten bzw. Phasen beschrnkt. Quelle: http://de.wikipedia.org/wiki/Refactoring Refactoring wird hauptschlich auf unschne Stellen im Code (siehe Code-Smell) angewandt. Diese sind z.B. mit Hilfe von schlechten Metrikwerten als solche zu erkennen. Dabei wird der Quelltext eines Computerprogramms umgestaltet, wobei die tatschliche Programmfunktion unverndert bleiben soll. Die Umgestaltung des Quelltextes erfolgt meist nach folgenden Gesichtspunkten: Lesbarkeit bersichtlichkeit Verstndlichkeit Erweiterbarkeit Vermeidung von Redundanz Testbarkeit Folgendes sind besonders hug eingesetzte Refactorings: nderung eines Symbolnamens. Verschieben eines Symbols in ein anderes Modul, z. B. eine Methode in eine andere Klasse. Aufteilung eines Moduls (z. B. Paket, Klasse, Methode) in mehrere kleinere Module oder Zusammenlegung kleinerer Module zu einem greren. Im weitesten Sinne auch die Umformatierung eines Quelltextes, z. B. mit einem Beautier.

29

Continuous Integration, Release 1

Bei genderten Geschftsprozessen bei Darstellung mittels der Unied Modeling Language UML kann mittels Refactoring der Programmcode gendert werden. Dadurch wird eine robuste und stabile Systemarchitektur geschaffen, da unbersichtliche nderungen nicht im Code initiiert werden mssen. Im weiteren Verlauf wollen wir nun den Quelltext hinsichtlich eines mglichen Refactorings untersuchen.

5.2 Aufteilen umfangreicher Methoden


Ausgangspunkt unserer berlegungen ist, dass die Statement-Methode der Klasse Customer um eine html-Ausgabe erweitert werden soll. Das notwendige Verndern der Methode kann deshalb mit weiteren Optimierungen verbunden werden.

5.2.1 Methoden herausnehmen


Regel Extract Method Eine Reduzierung der Komplexitt einer Methode besteht hug daran, logische Teile innerhalb einer Methode zu nden, diese auszulagern und damit die Komplexitt der Methode zu verringern. Aufgabe Welche Teile des Statement-Methode wrden sich dazu anbieten ?

30

Kapitel 5. Runde 4

Continuous Integration, Release 1

5.2. Aufteilen umfangreicher Methoden

31

Continuous Integration, Release 1

In unserem Beispiel wurde die switch-Anweisung in eine eigene Methode ausgelagert. Wenn korrekte Datentypen bei der bergabe von Parametern deniert werden, darf diese Codenderung anschlieend keine negativen Auswirkungen auf diese Tests haben.

Die Codemetriken fr diese Methode ndern sich ebenfalls in die positive Richtung.

32

Kapitel 5. Runde 4

Continuous Integration, Release 1

5.2.2 Umbenennen
Regel Renaming Nachdem die Tests keine Fehler liefern, kann die neue Version noch zum Umbenennen von einigen Variablen genommen werden. Moderne IDEs untersttzen den Entwickler dabei weitgehend und verstndliche Namen erhhen die Nachvollziehbarkeit des Quellcodes.

Ein Neukompilieren sowie das Ausfhren der Tests besttigt, dass es keine Fehler gegeben hat.

5.2.3 Methoden verschieben


Regel Move Methods Beim Betrachten der eben erstellten Methode amountFor() fllt auf, dass diese Informationen aus der Rental-Klasse benutzt, aber eigentlich berhaupt keine Informationen aus der Customer-Klasse. Es fehlt damit die Kohsion zwischen Methode und Instanzattributen und ein entsprechendes Metrik-Tool wrde hier sicherlich einen schlechten LCOM-Metrik errechnen. Die Methode wre deshalb besser in der rental-Klasse aufgehoben. man kopiert deshalb zunchst die Methode in die entsprechende Klasse und passt Sie anschlieend wie folgt an.

5.2. Aufteilen umfangreicher Methoden

33

Continuous Integration, Release 1

public double GetCharge() { double result = 0; // Determine amounts for each line switch (m_Movie.PriceCode) { case PriceCodes.Regular: result += 2; if (DaysRented > 2) { result += (DaysRented - 2) * 1.5; } break; case PriceCodes.NewRelease: result += DaysRented * 3; break; case PriceCodes.Childrens: result += 1.5; if (DaysRented > 3) { result += (DaysRented - 3) * 1.5; } break; } return result; }

In der Klasse Customer ist der Quellcode der amountFor-Methode durch folgende Vereinfachung anzupassen.
private double amountFor(Rental aRental) { return aRental.GetCharge(); }

Falls es immer noch fehlerfrei luft, kann die amountFor-Methode vernachlssigt werden. Die Umleitung auf die Rental-Klasse kann wieder direkt in der Statement()-Methode erfolgen.

Die vorhandenen UnitTests sollten noch immer funktionieren und auch die CodeMetrik sollte keine signikanten nderungen erfahren haben.

34

Kapitel 5. Runde 4

Continuous Integration, Release 1

Das abschlieende Klassendiagramm hat sich vom Ausgangspunkt wie folgt gendert.

UnitTests Durch die neue ffentliche Methode in Rental mssten fr getcharge() neue UnitTests geschrieben werden. Sie knnen diese unter UnitTest3.cs in ihr Projekt integrieren.

5.2.4 Eliminiere Temp-Variablen

5.2. Aufteilen umfangreicher Methoden

35

Continuous Integration, Release 1

Regel Replace Temp with Query Temporre Variablen sind ein weiteres Indiz fr Optimierungen. Sie berleben hug im Produktivcode und nach einiger Zeit ist man sich deren Notwendigkeit unsicher. In unserem beispiel kann die Deklaration und Benutzung von thisAmount in der while-Schleife durch each.getCharge() ersetzt werden.
while ( rentals.MoveNext() ) { Rental each = (Rental)rentals.Current; // Determine amounts for each line // Add frequent renter points frequentRenterPoints++; // Add bonus for a two-day new-release rental if ((each.Movie.PriceCode == PriceCodes.NewRelease) && (each.DaysRented > 1)) { frequentRenterPoints ++; } // Show figures for this rental result += "\t" + each.Movie.Title + "\t" + each.GetCharge().ToString() + "\n"; totalAmount += each.GetCharge(); }

Die Optimierung hinsichtlich der Lesbarkeit wird in diesem Beispiel natrlich durch eine doppelte Berechnung hinterfragbar. Dieser Nachteil wre aber durch eine nderung in der Klasse Rental ausgleichbar. Darauf wird spter noch eingegangen

5.2.5 Auslagern von Methoden


Regel Move Methods Auch die Rabattpunkte sind ein Kandidat fr das Auslagern in eine andere Methode.

36

Kapitel 5. Runde 4

Continuous Integration, Release 1

Bei der Implementierung der Methode ist zu beachten, dass die frequentrenterPoints-Variable evtl. zweimal erhht wird. Dies ist in der Methode entsprechend zu implementieren.
class Customer { //.... while ( rentals.MoveNext() ) { Rental each = (Rental)rentals.Current; frequentRenterPoints += each.GetFrequentRenterPoints(); // Show figures for this rental

class Rental { .... public int GetFrequentRenterPoints() { if (Movie.PriceCode == PriceCodes.NewRelease && DaysRented > 1) return 2; else return 1; }

Weitere Tests sichern die Funktionalitt der ausgelagerten Methode.


[Test] public void TestNewPublicMethod_RentalGetFrequentRenterPointsNewReleasePrice() { Rental _RentalSuperman1 = new Rental(new Movie("Superman", PriceCodes.NewRelease),1); Assert.AreEqual(1, _RentalSuperman1.GetFrequentRenterPoints()); Rental _RentalSuperman3 = new Rental(new Movie("Superman", PriceCodes.NewRelease),3); Assert.AreEqual(2, _RentalSuperman3.GetFrequentRenterPoints()); } [Test] public void TestNewPublicMethod_RentalGetFrequentRenterPointsChildrensPrice() {

5.2. Aufteilen umfangreicher Methoden

37

Continuous Integration, Release 1

Rental _RentalSuperman1 = new Rental(new Movie("Superman", PriceCodes.Childrens),1); Assert.AreEqual(1, _RentalSuperman1.GetFrequentRenterPoints()); Rental _RentalSuperman3 = new Rental(new Movie("Superman", PriceCodes.Childrens),3); Assert.AreEqual(1, _RentalSuperman3.GetFrequentRenterPoints()); } [Test] public void TestNewPublicMethod_RentalGetFrequentRenterPointsRegularPrice() { Rental _RentalSuperman1 = new Rental(new Movie("Superman", PriceCodes.Regular),1); Assert.AreEqual(1, _RentalSuperman1.GetFrequentRenterPoints()); Rental _RentalSuperman3 = new Rental(new Movie("Superman", PriceCodes.Regular),3); Assert.AreEqual(1, _RentalSuperman3.GetFrequentRenterPoints()); }

Das Klassendiagramm ndert sich entsprechend.

Die Metriken fr das Projekt haben anschlieend folgende Werte:

38

Kapitel 5. Runde 4

Continuous Integration, Release 1

5.2.6 Code-Verbesserungen
Innerhalb der Statement-Methode kann eine weitere Verbesserung dadurch erreich werden, dass man die Java-Like enumerator - while-Schleife durch ein foreach-Konstrukt ersetzt.

5.2. Aufteilen umfangreicher Methoden

39

Continuous Integration, Release 1

Die Statement()-Methode sieht nun wie folgt aus:


public string Statement() { double totalAmount = 0; int frequentRenterPoints = 0; string result = "Rental record for " + m_Name + "\n"; foreach ( Rental each in m_Rentals ) { // Determine amounts for each line // Add frequent renter points frequentRenterPoints += each.GetFrequentRenterPoints(); // Show figures for this rental result += "\t" + each.Movie.Title + "\t" + each.GetCharge().ToString() + "\n"; totalAmount += each.GetCharge(); } // Add result result return } footer lines += "Amount owed is " + totalAmount.ToString() + "\n"; += "You earned " + frequentRenterPoints.ToString() + " frequent renter points."; result;

5.2.7 Temporre Variablen ersetzen


Regel Replace temp with Query Wie schon weiter oben erwhnt, knnen temporre Variablen ein Problem darstellen. Sie sind nur nutzbar in der der eigenen Methode Keine Wiederverwendbarkeit In unserem Beispiel bieten sich die Variablen totalAmount sowie frequentRentalPoints zum Refaktorisieren an.

Da totalAmount innerhalb einer Schleife errechnet wurde, muss diese Schleifenstruktur innerhalb der Methode getTotalCharge() nachgebildet werden.

40

Kapitel 5. Runde 4

Continuous Integration, Release 1

private double GetTotalCharge() { double result = 0; foreach ( Rental each in m_Rentals ) { result += each.GetCharge(); } return result; }

Die gleiche Vorgehensweise ist fr frequentrenterPoints nachzuholen. Aufgabe Erstellen Sie eine Methode GetTotalFrequentRenterPoints und fhren Sie die Refaktorisierung durch.

Lsung
public string Statement() { string result = "Rental record for " + m_Name + "\n"; foreach ( Rental each in m_Rentals ) { // Show figures for this rental result += "\t" + each.Movie.title + "\t" + each.GetCharge().ToString() + "\n"; } // Add footer lines result += "Amount owed is " + GetTotalCharge().ToString() + "\n"; result += "You earned " + GetTotalFrequentRenterPoints().ToString() + " frequent renter points."; return result; } private int GetTotalFrequentRenterPoints() { int result = 0; foreach ( Rental each in m_Rentals ) { result += each.GetFrequentRenterPoints(); } return result; }

bersetzen Sie die folgenden Aussagen sinngem

5.2. Aufteilen umfangreicher Methoden

41

Continuous Integration, Release 1

It is worth stopping to think a bit about the last refactoring. Most refactorings reduce the amount of code, but this one increases it. Thats because Java 1.1 requires a lot of statements to set up a summing loop. Even a simple summing loop with one line of code per element needs six lines of support around it. Its an idiom that is obvious to any programmer but is a lot of lines all the same. The other concern with this refactoring lies in performance. The old code executed the while loop once, the new code executes it three times. A while loop that takes a long time might impair performance. Many programmers would not do this refactoring simply for this reason. But note the words if and might. Until I prole I cannot tell how much time is needed for the loop to calculate or whether the loop is called often enough for it to affect the overall performance of the system. Dont worry about this while refactoring. When you optimize you will have to worry about it, but you will then be in a much better position to do something about it, and you will have more options to optimize effectively. These queries are now available for any code written in the customer class. They can easily be added to the interface of the class should other parts of the system need this information. Without queries like these, other methods have to deal with knowing about the rentals and building the loops. In a complex system, that will lead to much more code to write and maintain.

5.2.8 HTML-Statement
Nach all den Refaktoriesierungen ist es nun einfach, eine HTML-Statement-Methode zu schreiben, da Sie viele interne Implementierungen wiederverwenden kann.
public string HTMLStatement() { string result = "<H1>Rental record for <EM>" + m_Name + "</EM></H1><P>\n"; foreach (Rental each in m_Rentals) { result += each.Movie.title + ": " + each.GetCharge().ToString() + "<BR>\n"; } // Add footer lines result += "<P>Amount owed is <EM>" + GetTotalCharge().ToString() + "</EM><P>\n"; result += "You earned <EM>" + GetTotalFrequentRenterPoints().ToString() + "</EM> frequent renter points.<P>"; return result; }

5.2.9 Neue Unit-Tests


Die HTML-Statement-Methode maht natrlich auch neue Unit-Tests notwendig.
[Test] public void TestHTMLstatement() { Customer _LiverLips = new Customer("Liver Lips"); _LiverLips.AddRental(new Rental(new Movie("Enforcer", PriceCodes.Childrens), 10)); _LiverLips.AddRental(new Rental(new Movie("Apollo 13", PriceCodes.NewRelease), 3)); _LiverLips.AddRental(new Rental(new Movie("Pulp Fiction", PriceCodes.Regular), 4)); Assert.AreEqual("Liver Lips", _LiverLips.Name); Assert.AreEqual ("<H1>Rental record for <EM>Liver Lips</EM></H1><P>\nEnforcer: 12<BR>\nApollo 13: " + "9<BR>\nPulp Fiction: 5<BR>\n<P>Amount owed is <EM>26</EM><P>\n"

42

Kapitel 5. Runde 4

Continuous Integration, Release 1

+ "You earned <EM>4</EM> frequent renter points.<P>" , _LiverLips.HTMLStatement()); _LiverLips.AddRental(new Rental(new Movie("The Good, The Bad, And The Ugly" , PriceCodes.Childrens), 10)); Assert.AreEqual ("<H1>Rental record for <EM>Liver Lips</EM></H1><P>\nEnforcer: 12<BR>\nApollo 13: " + "9<BR>\nPulp Fiction: 5<BR>\nThe Good, The Bad, And The Ugly: 12<BR>\n<P>" + "Amount owed is <EM>38</EM><P>\nYou earned <EM>5</EM> frequent renter points.<P>" , _LiverLips.HTMLStatement()); }

5.2.10 Vergleich vorher/nachher


Obwohl natrlich auch in anderen Klassen Vernderungen vorgenommen wurden, ist ein Vergleich der Statement()Methode zu Beginn und zum jetzigen Zeitpunkt interessant. Man kann in der Versionsverwaltung durch ein Compare von Head und Revision 1 erreichen.

5.2. Aufteilen umfangreicher Methoden

43

Continuous Integration, Release 1

44

Kapitel 5. Runde 4

KAPITEL 6

Runde 5

6.1 OOP-Refactoring
Nachdem die ersten Optimierungen eher im Bereich der strukturierten Programmierung gelegen sind, kann man auch Optimierungen durch Vernderungen objektorientierter Elemente bewirken

6.2 Methoden herausnehmen


Das switch-Statement der get_Charge()-Methode basiert auf Attributen einer anderen Klasse (Movie). Es wre damit besser in der Movie-Klasse aufgehoben.

45

Continuous Integration, Release 1

public class Movie { ... public double GetCharge(int daysRented) { double result = 0; // Determine amounts for each line switch(this.PriceCode) { case PriceCodes.Regular: result += 2; if (daysRented > 2) { result += (daysRented - 2) * 1.5; } break; case PriceCodes.NewRelease: result += daysRented *3; break; case PriceCodes.Childrens: result += 1.5; if (daysRented > 3) { result += (daysRented - 3) * 1.5; } break; } return result; } }

Die Methode bentigt allerdings die Informationen ber die Mietdauer(daysRented). Dies ist beim Aufruf aus Movie heraus zu bergeben.
public class Rental { ... public double GetCharge() { return Movie.GetCharge(DaysRented); } }

Aufgabe Verschieben sie analog dazu die GetFrequentRenterPoints-Methode

Lsung
classe Movie { .... public int GetFrequentRenterPoints() { if (this.PriceCode == PriceCodes.NewRelease && DaysRented > 1) return 2; else return 1; } }

public class Rental { ...

46

Kapitel 6. Runde 5

Continuous Integration, Release 1

public int GetFrequentRenterPoints() { return Movie.GetFrequentRenterPoints(DaysRented); } }

6.2.1 Polymorphie
Die Movie-Objekte errechnen die Gebhren in Abhngigkeit von ihrem PriceCode. Dies wre eigentlich ein idealer Ansatz fr Vererbung.

Diskussion Diskutieren Sie die oben gettigte Aussage Eine Lsung, die die oben mglichen Nachteile ausschliet, wre die Anwendung des Strategy- bzw. State-Pattern. State-Pattern Erstellen Sie ein Klassendiagramm, welches das State-Pattern auf die Movie-Klasse im Zusammenhang mit der Preisermittlung anwendet.

6.2. Methoden herausnehmen

47

Continuous Integration, Release 1

6.2.2 Umbenennen 6.2.3 Methoden verschieben

48

Kapitel 6. Runde 5

KAPITEL 7

Suche

search

49

Das könnte Ihnen auch gefallen