Writing good unit tests AgileKZN – November 2012 © Chillisoft 2012 1
May 11, 2015
Writing good unit testsAgileKZN – November 2012© Chillisoft 2012
1
“legacy code is simply code without tests” - Michael Feathers
2
3
What is a Unit Test?
My System
Acceptance test Acceptance test
Acceptance testAcceptance test
Integration test
Integration test
Integration test
Integration test
Integration test
Integration test
Integration test
Integration test
Integration test
Integration test
Integration test
Integration test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test Unit
test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test Unit
test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit testUnit
testUnit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
4
Unit tests are:
1. Small
2. Fast
5
1. Setup2. Exercise3. Verify4. Teardown
The four-phase test
6
My System
Acceptance test Acceptance test
Acceptance testAcceptance test
Integration test
Integration test
Integration test
Integration test
Integration test
Integration test
Integration test
Integration test
Integration test
Integration test
Integration test
Integration test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test Unit
test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test Unit
test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit testUnit
testUnit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
Unit test
7
My System
Unit test
8
My System
SUTUnit test
Fixture
1. Setup
2. Exercise
3. Verify
9
My System
Unit test 4. Teardown
1. Setup
2. Exercise
3. VerifyBoom!
10
Trustworthy
Readable
Maintainable
Note: Thanks to Roy Osherove for this model
11
12
Trustworthy
13
Easy to run
14
Automatically independently verified
15
Same result every time
SUT
Fixture
Unit test
In memory
16
test chaining
17
Peter’s principle for writing unit tests:
use a minimal fresh transient
fixture per test
18
use a minimal fresh transient
fixture per test
the smallest possible fixture you can get away with using
19
use a minimal fresh transient
fixture per test
brand new objects every time, where you can
20
use a minimal fresh transient
fixture per test
objects that are chucked after each test, left to the garbage collector
21
use a minimal fresh transient
fixture per test
the test should create its own fixture
High coverage
Do TDD!
22
23
Readable
24
Well named
use a test naming convention, eg:
Method_ShouldXX()
Method_GivenXX_ShouldYY()
Method_WhenXX_ShouldYY()
25
[Test]public void TestDelimitedTableNameWithSpaces(){
//...}
[Test]public void Generate_GivenTableNameWithSpaces_ShouldDelimitTableName(){
//...}
26
Standardised test structure
[Test] public void Append_GivenEmptyString_ShouldNotAddToPrintItems() { // Arrange var document = CreatePrintableDocument(); // Act document.Append(""); // Assert Assert.AreEqual(0, document.PrintItems.Count); }
no teardown
27
Standardised project structure
testcase class per class
test project per project
helpers and bootstrappers
keep integration tests separate
28
Self-contained test
no invisible setup
no teardown
no irrelevant details
29
[Test]public void TestEncryptedPassword(){ Assert.AreEqual(encryptedPassword, encryptedConfig.Password); Assert.AreEqual(encryptedPassword, encryptedConfig.DecryptedPassword); encryptedConfig.SetPrivateKey(rsa.ToXmlString(true)); Assert.AreEqual(password, encryptedConfig.DecryptedPassword);}
invisible setup
30
irrelevant details
[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());}
31
use a minimal fresh transient
fixture per test
32
Maintainable
33
Be strict:
Less than 5 lines for Arrange
One line for Act
One logical Assert
Test one thing
34
long, multi-phased test[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 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);}
35
Isolate your SUT
SUT
FixtureUnit test
36
[Test]public void GetCurrentCredentialsString_ShouldReturnDeviceIDFromDeviceState(){ //Arrange var deviceState = Substitute.For<IDeviceState>(); var id = RandomValueGen.GetRandomGuid(); deviceState.GetDeviceID().Returns(id); var credentialsProvider = new CredentialsProvider(deviceState); //Act var result = credentialsProvider.GetCurrentCredentialsString(); //Assert Assert.That(result, Is.EqualTo(id.ToString()) );}
37
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); }
38
Care for your test code
Refactor and use test patterns:
• Factory method
• Custom asserts
• Fluent builders
39
Test public interfaces only
“Use the front
door”
40
further reading
xUnit Test Patterns: Refactoring Test CodeGerard Meszaros
The Art of Unit TestingRoy Osherove
41
Working effectively with legacy codeMichael Feathers
Growing object oriented software, guided by testsSteve Freeman and Nat Pryce
even further
reading
42
thanks!
@pwiles
http://
developmentthoughts.wordpress.com/http://www.chillisoft.co.za