xUnit Test Patterns writing good unit tests Peter Wiles introduction what makes a good unit test? writing good unit tests signs of bad unit tests designing testable software further reading the state of agile practices Management practices Technical practices Daily standup – 78% Iteration planning – 74% Release planning – 65% Burndown – 64% Retrospectives – 64% Velocity – 52% Unit testing – 70% Continuous Integration – 54% Automated builds – 53% Coding standards – 51% Refactoring – 48% Test driven development – 38% http://www.versionone.com/state_of_agile_development_survey/11/ “continuous attention to technical excellence and good design enhances agility” http://agilemanifesto.org/principles.html theory: good unit tests are important. “legacy code is simply code without tests” - Michael Feathers no tests = ? agility bad tests = worse agility good tests = good agility you can’t be truly agile without automated tests tests do not replace good, rigorous software engineering discipline, they enhance it. what makes a good unit test? a good unit test runs fast helps us localise problems - Michael Feathers, again when is a unit test not really a unit test? “What makes a clean test? Three things. Readability, readability and readability.” - Robert C Martin good unit tests are FRIENDS fast lightning fast, as in 50 to 100 per second no IO all in process, in memory robust withstanding changes in unrelated areas of code isolated independent not dependant on external factors, including time examples communicating how to use the class under test readability is key necessary where removing a test would reduce coverage deterministic the result is the same every time specific each test testing one thing fast robust independent examples necessary deterministic specific writing good unit tests some advice use an xUnit framework [TestFixture] public class TestCalculator { [Test] public void Sum() { var calculator = new Calculator(); var sum = calculator.Sum(5, 4); Assert.AreEqual(9, sum); } } write your tests first standardise test structure [Test] public void Append_GivenEmptyString_ShouldNotAddToPrintItems() { // Arrange var document = CreatePrintableDocument(); // Act document.Append(""); // Assert Assert.AreEqual(0, document.PrintItems.Count); } be strict Less than 5 lines for Arrange One line for Act One logical Assert (less than 5 lines) no teardown bare minimum setup use project conventions testcase class per class test project per project helpers and bootstrappers use a test naming convention Method_ShouldXX() Method_GivenXX_ShouldYY() Method_WhenXX_ShouldYY() use a minimal fresh transient fixture per test use a minimal fresh transient fixture per test the smallest possible fixture you can get away with using use a minimal fresh transient fixture per test brand new objects every time, where you can use a minimal fresh transient fixture per test objects that are chucked after each test, left to the garbage collector use a minimal fresh transient fixture per test the test should create its own fixture signs of bad unit tests conditionals if (result == 5) ... long test methods [Test] public void TestDeleteFlagsSetContactPerson() { ContactPerson myContact = new ContactPerson(); Assert.IsTrue(myContact.Status.IsNew); // this object is new myContact.DateOfBirth = new DateTime(1980, 01, 20); myContact.FirstName = "Bob"; myContact.Surname = "Smith"; myContact.Save(); //save the object to the DB Assert.IsFalse(myContact.Status.IsNew); // this object is saved and thus no longer // new Assert.IsFalse(myContact.Status.IsDeleted); IPrimaryKey id = myContact.ID; //Save the objectsID so that it can be loaded from t Assert.AreEqual(id, myContact.ID); myContact.MarkForDelete(); Assert.IsTrue(myContact.Status.IsDeleted); myContact.Save(); Assert.IsTrue(myContact.Status.IsDeleted); Assert.IsTrue(myContact.Status.IsNew); } invisible setup [Test] public void TestEncryptedPassword() { Assert.AreEqual(encryptedPassword, encryptedConfig.Password); Assert.AreEqual(encryptedPassword, encryptedConfig.DecryptedPassword); encryptedConfig.SetPrivateKey(rsa.ToXmlString(true)); Assert.AreEqual(password, encryptedConfig.DecryptedPassword); } huge fixture [TestFixtureSetUp] public void TestFixtureSetup() { SetupDBConnection(); DeleteAllContactPersons(); ClassDef.ClassDefs.Clear(); new Car(); CreateUpdatedContactPersonTestPack(); CreateSaveContactPersonTestPack(); CreateDeletedPersonTestPack(); } [Test] public void TestActivatorCreate() { Activator.CreateInstance(typeof (ContactPerson), true); } too much information [Test] public void TestDelimitedTableNameWithSpaces() { ClassDef.ClassDefs.Clear(); TestAutoInc.LoadClassDefWithAutoIncrementingID(); TestAutoInc bo = new TestAutoInc(); ClassDef.ClassDefs[typeof (TestAutoInc)].TableName = "test autoinc"; DeleteStatementGenerator gen = new DeleteStatementGenerator(bo, DatabaseConnection.CurrentConnection); var statementCol = gen.Generate(); ISqlStatement statement = statementCol.First(); StringAssert.Contains("`test autoinc`", statement.Statement.ToString()); } external dependencies these are probably integration tests too many asserts [Test] public void TestDeleteFlagsSetContactPerson() { ContactPerson myContact = new ContactPerson(); Assert.IsTrue(myContact.Status.IsNew); // this object is new myContact.DateOfBirth = new DateTime(1980, 01, 20); myContact.FirstName = "Bob"; myContact.Surname = "Smith"; myContact.Save(); //save the object to the DB Assert.IsFalse(myContact.Status.IsNew); // this object is saved and thus no longer // new Assert.IsFalse(myContact.Status.IsDeleted); IPrimaryKey id = myContact.ID; //Save the objectsID so that it can be loaded from t Assert.AreEqual(id, myContact.ID); myContact.MarkForDelete(); Assert.IsTrue(myContact.Status.IsDeleted); myContact.Save(); Assert.IsTrue(myContact.Status.IsDeleted); Assert.IsTrue(myContact.Status.IsNew); } duplication between tests repeated calls to constructors is duplication – refactor this. test chaining s….l….o….w t….e….s.…t….s … no automated build process debugging test-only code in production #if TEST //... #endif if (testing) { //... } randomly failing tests designing testable software use dependency injection aka dependency inversion aka inversion of control Upon construction, give an object everything it needs to do everything it needs to do. use a layered architecture stub/substitute the underlying layers use a testable UI pattern: Model-View-Presenter Model-View-Controller Model-View-ViewModel choose libraries that allow for unit testing. or, build an anti-corruption layer (adapter) test from the outside in check out the BDD talks at CodeLab! further reading The Art of Unit Testing Roy Osherove xUnit Test Patterns: Refactoring Test Code Gerard Meszaros even further reading Working effectively with legacy code Michael Feathers Growing object oriented software, guided by tests Steve Freeman and Nat Pryce thanks! @pwiles peter.wiles@chillisoft.co.za http://developmentthoughts.wordpress.com/