@Hyperion Statt Mercurial würde ich persönlich Git nutzen (auch wegen Github

), doch das ist sicherlich Geschmacksfrage. Mit Mercurial machst Du aber sicherlich nichts falsch, ebenso wie mit NAnt und CruiseControl. Beides sind Standardwerkzeuge in .NET, NAnt ist eigentlich ohne Alternative. CruiseControl ließe sich durch Jenkins ersetzen, doch dessen .NET-Unterstützung ist vergleichsweise schlecht. Für Python ist Jenkins allerdings hervorragend geeignet, insbesondere in Verbindung mit virtualenv und tox.
.NET hat kein eingebautes Unit-Testing-Framework. Ich nehme an, Du beziehst Dich auf MSTest. Das ist nicht Teil der BCL, sondern kommt von VisualStudio, mithin benötigst Du entweder TFS als CI-System, oder eine Visual-Studio-Installation auf dem Build-Server (und jedem Build-Knoten). Im Vergleich zu NUnit oder xUnit.NET ist MSTest ohnehin reichlich primitiv, der einzige Vorteile ist die Integration in Visual Studio, doch mit Addins wie TestDriven.NET lässt sich ähnlicher Komfort auch für andere Testbibliotheken erreichen. Die nächste Version von Visual Studio hat ohnehin bessere Unterstützung für Testbibliotheken von Drittanbietern.
Ob xUnit.net besser ist als NUnit, kann ich nicht beurteilen. Allerdings beziehen sich die auf der Seite von xUnit aufgezählten Unterschiede größtenteils auf ältere NUnit-Versionen, und haben keine Gültigkeit mehr für NUnit 2.6. In dieser Version ist die Assert-basierte Test-API durch eine ersetzt, die auf Constraints aufsetzt und Erwartungen an Objekte sehr knapp und präzise ausdrücken kann. Das sieht dann in etwa so aus (die Typdeklaration in der ersten Zeile dient der Verdeutlichung, in "echtem" Code würde ich da "var" nutzen):
Code: Alles auswählen
IEnumerable<MyViewModel> models = GetMyTestFixture()
var areModelsValid = Is.Unique
& Has.No.Null
& !Is.Empty
& Has.All.Property("MyCoolProperty").GreaterThan(10)
& Has.Exactly(1).Property("MyOtherProperty").EqualTo("Hello world");
Assert.That(models, areModelsValid);
Aktuelle NUnit-Versionen nutzen auch nicht mehr so viele Attribute, insbesondere nicht "ExpectedException". Stattdessen nutzt man Lambda-Ausdrücke, um Ausnahmen zu prüfen, was wiederum mit Constraints geschieht:
Code: Alles auswählen
Assert.That(() => MyMethodThatThrowsArgumentException(foo), Throws.ArgumentException.With.Property("ParamName").EqualTo("foo"));
Ich habe mir xUnit.net nie so genau angesehen, als dass ich diese Beispiele in dessen API schreiben könnte. Ich bin mit NUnit sehr zufrieden, gerade die Constraints-API finde ich sehr gelungen, da sie die Wiederverwendung gerade zu forciert. Man kann in Hilfsmethoden höchst komplexe Constraints generieren, und die in jedem Test wiederverwenden, entweder direkt in einem Assert, oder kombiniert mit zusätzlichen Constraints. Ein großer Vorteil gegenüber Methoden, die einfach nur Asserts aneinander reihen, ist dabei, dass NUnit immer alle Constraints auswertet. Sprich, der Test bricht nicht einfach bei der ersten, nicht erfüllten Erwartung ab, sondern läuft immer komplett durch, so dass man selbst bei sehr komplexen Tests immer genau weiß, welcher Teil gepasst hat, und welcher nicht. Eine derartige API würde ich mir auch in einer Testbibliothek für Python wünschen.
"moq" habe ich nie verwendet. Die API sieht zwar nett aus, jede dahingehende Überlegung würde aber vom Alter des Projekts und der letzten Veröffentlichung gestoppt. Ich nutze RhinoMocks, dass zumindest noch halbwegs aktiv ist. Die API ist ähnlich. Allerdings habe ich bis jetzt selten Mocks in Tests verwendet. Exzessiven Gebrauch von Mocking halte ich für ein deutliches Zeichen für eine zu enge Kopplung der betreffenden Klassen. An dieser Stelle sei Ninject erwähnt, eine IoC-Bibliothek für .NET, mit der man Objekte sehr gut entkoppeln und auf höherer Ebene mit wenigen Methoden-Aufrufen wieder zusammen stecken kann. Die API ist sehr einfach, die Konfiguration kommt völlig ohne XML aus, und die Auswirkungen auf den Quelltext sind gering, in normalem Quelltext zeigt sich Ninject gar nicht, wenn man es richtig nutzt.
Die Meinung, dass eine solide Build-Umgebung an Wert kaum überschätzt werden kann, teile ich. Steht ein CI-System, neigt man dazu, nach und nach den ganzen Entwicklungs-, Test- und Release-Prozess um das CI-System herum zu bauen, so dass Entwicklungsrichtlinien (i.e. Quelltext im "master" muss immer kompilieren und alle Tests bestehen) technisch durchgesetzt und Arbeitsabläufe wann immer möglich vom CI-System automatisiert werden. Letztlich schützt man damit die Qualität des Quelltexts (frei nach der Einsicht, dass Tests nur dann etwas nützen, wenn man sie auch ausführt), erreicht schnellere Release-Zyklen, und reduziert die Arbeit der Entwickler.
Es ist letztlich einfach bequem, ein CI-System zu haben. Man muss nicht immer penibel darauf achten, ja alle Tests manuell auszuführen, man kann sich einfach darauf verlassen, dass Tests immer ausgeführt werden. Man kann in seinem eigenen Branch ruhig was kaputt machen, dass CI-System beschwert sich per E-Mail, bevor richtig Schaden entsteht, weil der kaputte Quelltext im Release gelandet ist. Man braucht auch nicht vor jedem Release zig Skripte ausführen, um alle Assemblies zu kompilieren, alle Tests auszuführen, alles in einen Installer zu verpacken und auf den Deployment-Server zu kopieren, man kann einfach den letzten Build aus dem CI-System ziehen, von dem man ja dank des CI-Systems weiß, dass er alle Tests besteht, und diesen auf die Deployment-Server kopieren, oder gleich automatisch vom CI-System dorthin kopieren lassen.
Man darf zwar den Aufwand der Konfiguration nicht unterschätzen, aber es zahlt sich aus. CI-Systeme sind ja auch kein Alles-oder-Nichts-System, sondern lassen sich Schritt für Schritt in den Entwicklungsprozess integrieren.