660 likes | 983 Views
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.
E N D
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.
a good unit test runs fast helps us localise problems - Michael Feathers, again
“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] publicclassTestCalculator { [Test] publicvoid Sum() { var calculator = newCalculator(); var sum = calculator.Sum(5, 4); Assert.AreEqual(9, sum); } }
standardise test structure [Test] publicvoidAppend_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 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
conditionals if (result == 5) ...
long test methods [Test] publicvoidTestDeleteFlagsSetContactPerson() { ContactPersonmyContact = newContactPerson(); Assert.IsTrue(myContact.Status.IsNew); // this object is new myContact.DateOfBirth = newDateTime(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 the Database 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] publicvoidTestEncryptedPassword() { Assert.AreEqual(encryptedPassword, encryptedConfig.Password); Assert.AreEqual(encryptedPassword, encryptedConfig.DecryptedPassword); encryptedConfig.SetPrivateKey(rsa.ToXmlString(true)); Assert.AreEqual(password, encryptedConfig.DecryptedPassword); }
huge fixture [TestFixtureSetUp] publicvoidTestFixtureSetup() { SetupDBConnection(); DeleteAllContactPersons(); ClassDef.ClassDefs.Clear(); newCar(); CreateUpdatedContactPersonTestPack(); CreateSaveContactPersonTestPack(); CreateDeletedPersonTestPack(); } [Test] publicvoidTestActivatorCreate() { Activator.CreateInstance(typeof (ContactPerson), true); }
too much information [Test] publicvoidTestDelimitedTableNameWithSpaces() { ClassDef.ClassDefs.Clear(); TestAutoInc.LoadClassDefWithAutoIncrementingID(); TestAutoIncbo = newTestAutoInc(); ClassDef.ClassDefs[typeof (TestAutoInc)].TableName = "test autoinc"; DeleteStatementGenerator gen = newDeleteStatementGenerator(bo, DatabaseConnection.CurrentConnection); varstatementCol = gen.Generate(); ISqlStatement statement = statementCol.First(); StringAssert.Contains("`test autoinc`", statement.Statement.ToString()); }
external dependencies these are probably integration tests
too many asserts [Test] publicvoidTestDeleteFlagsSetContactPerson() { ContactPersonmyContact = newContactPerson(); Assert.IsTrue(myContact.Status.IsNew); // this object is new myContact.DateOfBirth = newDateTime(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 the Database 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.
s….l….o….w t….e….s.…t….s …
test-only code in production if (testing) { //... } #if TEST //... #endif