XUnit Test Patterns writing good unit tests Peter Wiles.

Post on 23-Dec-2015

221 views 1 download

Transcript of XUnit Test Patterns writing good unit tests Peter Wiles.

xUnit Test Patternswriting 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

http://www.versionone.com/state_of_agile_development_survey/11/

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%

Management practices

Technical practices

the state of agile practices

“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?

runs fast

helps us localise

problems

a good unit test

- Michael Feathers, again

when is a unit test not

really a unit test?

- Robert C Martin

“What makes a clean test?

Three things. Readability,

readability and readability.”

good unit tests are

FRIENDS

fastlightning fast, as in 50 to 100 per second

no IOall in process, in memory

robustwithstanding changes in unrelated areas of code

isolated

independent

not dependant on external factors, including time

examplescommunicating 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

deterministic

robustfast

independentexamplesnecessary

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 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]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 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

testsrepeated 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

inversionaka 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

xUnit Test Patterns: Refactoring Test CodeGerard Meszaros

The Art of Unit TestingRoy Osherove

Working effectively with legacy codeMichael Feathers

Growing object oriented software, guided by testsSteve Freeman and Nat Pryce

even further

reading

thanks!

@pwiles

peter.wiles@chillisoft.co.za

http://

developmentthoughts.wordpress.com/