Unit Testing in .NET

Roadmap

  1. Prerequisite Terminology and Background Information
  2. How to do Unit Testing
    • Roy Osherove's TDD Kata 1 - String Calculator
    • Roy Osherove's TDD Kata 2 - Interactions
    • Dependency Injection Demonstrations
    • Unit Testing Tool Demonstrations
  3. How to do Unit Testing WELL

Terminology

Unit Test

Unit

Dependency Injection

Mock

Test Driven Development

Three Laws of TDD

  1. You are not allowed to write any production code until you have written a failing unit test
  2. You are not allowed to write more of a unit test than is sufficient to fail

    Not compiling is failing

  3. You are not allowed to write more production code than is sufficient to pass the currently failing unit test

Code Katas

All about PRACTICE

Repeated software development tasks with the end goal of making small improvements each times

Do one task at a time

The trick is to learn to work incrementally

s

Kata 2:

Interactions

Continuation of the String Calculator Kata

Support interaction with the user by allowing the results to be written to the console

How to do Unit Testing Well

Good Unit Test Practices

Better Unit Test Practices

Bad Unit Test Practices

Benefits of Unit Testing

What Makes Code Hard to Test?

Good Unit Test Practices

Focused

Repeatable

Consistent

Fully Isolated

Easy to Implement

Easy to Run

Automated

Fast

Better Unit Test Practices

Cover All Code Paths


                        private void outputLine( int result )
                        {
                            if ( Console != null )
                            {
                                Console.WriteLine( result.ToString() );
                            }
                        }
                    

Better Unit Test Practices

Cover Edge Cases and Non-Standard Cases


                   [Test]
                   public void Add_WhenGivenNumbersThatOverflowInt32_DoesSomething()
                   {
                       // Arrange (Given)
                       var stringCalculator = new StringCalculator();

                       // Act (When)
                       var result = stringCalculator.Add( 
                            string.Format( "{0},{1}", int.MaxValue, 1 ) );

                       // Assert (Then)
                       // WHAT SHOULD HAPPEN?
                   }
                    
Gangnam Style Article

Better Unit Test Practices

Long, Descriptive Names


                   public void Add_WhenPassedNegativeNumbers_ThrowsException()
                   

Better Unit Test Practices

Clean, Recognizable Structure


                   // Arrange (Given)
                   var stringCalculator = new StringCalculator();

                   // Act (When)
                   TestDelegate testDelegate = delegate 
                   { 
                        stringCalculator.Add( "-1,2" ); 
                   };

                   // Assert (Then)
                   Assert.Throws( testDelegate );
                   

Bad Unit Test Practices

Dependencies on External Resources


                   var networkPath = @"\\Server\Vendor Portal\Invoices\";
                   

                   using ( var connection = 
                        new SqlConnection( "server=localhost\\SQLExpress;" +
                                            "Database=default;" +
                                            "integrated security=true;" ) )
                   

                   private void sendCommandToExternalApplication( 
                        object sender, EventArgs e )
                   {
                       foreach ( var process in _processes )
                       {
                           var result = SetForegroundWindow( process.MainWindowHandle );

                           if ( result != 0 ) 
                               SendKeys.SendWait( "{F12}" );
                           else
                               throw new Exception( 
                                    string.Format( "Could not find program {0}", 
                                    programName ) );
                       }
                   }
                   

                   public void PostToWebService( ServiceRequest serviceRequest )
                   {
                       var serviceClient = new ServiceClient();
                                
                       var serviceResponse = serviceClient.PostRequest( serviceRequest );

                       validateResponse( serviceResponse );
                   }
                   

Bad Unit Test Practices

Dependencies on Objects With Non-Deterministic Results


                   DateTime.Now;
                   

                   var newDate = date.Add( new TimeSpan( 1000 ) );
                   

Bad Unit Test Practices

Dependencies on Environment


                   public ConfigurationInfo GetApplicationConfiguration()
                   {
                       var filename = Environment.GetEnvironmentVariable( 
                            "PROGRAM_CONFIGURATION_LOCATION" );

                       if ( File.Exists( filename ) )
                       {
                           return new ConfigurationInfo( filename );
                       }
                       else 
                       {
                           filename = Environment.CurrentDirectory 
                                + "\\Configuration\\CustomConfig.txt";
                           return new ConfigurationInfo( filename );
                       }
                   }
                   

Bad Unit Test Practices

Dependencies on Other Tests


                   [Test]
                   public void Insert_WhenGivenNewInvoice_AssignsNewIdentity()
                   {
                       // Arrange (Given)
                       var invoice = new Invoice
                       {
                           Customer = "ABC Works",
                           Amount = 856.82
                       };

                       // Act (When)
                       _inMemoryInvoiceRepository.Insert( invoice );

                       // Assert (Then)
                       Assert.AreEqual( invoice.Id, 1 );
                   }
                   

Benefits of Unit Testing

The Code Works

Immediate Feedback

Isolated Testing

Less Debugging

Fewer Bugs

Testable Code

Unit Tests are First-Class Citizens

Production Code is

Flexible

Maintainable

Reusable

What Makes Hard-to-Test Code?

Long Methods Performing Multiple Operations


                    public Beer BoilWort()
                    {
                        if ( !WortIsReadyToBoil() )
                            throw new Exception( "Mashing failed to produce usable wort" );
                        if ( !HopsForWortAreInInventory() )
                            throw new Exception( "No hops for boil" );
                        if ( !BrewKettleAvailable() )
                            throw new Exception( "No brew kettle ready for boil" );
                        if ( !YeastIsInInventory() )
                            throw new Exception( "No yeast available for fermentation" );
                        if ( !FermentationVesselReady() )
                            throw new Exception( "No vessel available for fermentation" );

                        boilMash();
                    }
                    

                    [Test]
                    public void BoilWort_WhenGivenGoodWort_ReturnsReadyForFermentation()
                    {
                        // Arrange (Given)
                        var wortMock = new Mock();
                        wortMock.Setup( w => w.ReadyForBoil().Returns( true ) );

                        var hopsInventoryManagerMock = new Mock();
                        hopsInventoryManagerMock.Setup( hi => 
                            hi.GetHopsForWort().Returns( new Hops() ) );

                        var brewKettles = new List();
                        brewKettles.Add( new Mock.Of( isReady: true ) );
                        ...
                    }
                    

                    [Test]
                    public void BoilWort_WhenGivenGoodWort_ReturnsReadyForFermentation()
                    {
                        ...
                        var yeastInventoryManagerMock = new Mock();
                        yeastInventoryManagerMock.Setup( yi => 
                            yi.GetYeastForFermenation().Returns( new Yeast() ) );

                        var fermentationVessels = new List();
                        fermentationVessels.Add( new Mock.Of( isReady: true ) );
                        ...
                    }
                    

                    [Test]
                    public void BoilWort_WhenGivenGoodWort_ReturnsReadyForFermentation()
                    {
                        ...
                        var boilStep = new BoilStep( wort, 
                            hopsInventoryManagerMock.Object, 
                            brewKettles, 
                            yeastInventoryManagerMock.Object,
                            fermentationVessels )

                        // Act (When)
                        var actualResult = boilStep.BoilWort();

                        // Assert (Then)
                        Assert.AreEqual( actualResult, Recipes.IPA.PostBoilWort )
                    }
                    

What Makes Hard-to-Test Code?

Tall Architectures


                    public class Mill
                    {
                        public MashTun MashTun { get; set; }
                    }

                    public class MashTun
                    {
                        public Boiler Boiler { get; set; }
                    }

                    public class Boiler
                    {
                        public Fermenter Fermenter { get; set; }
                    }

                    public class Fermenter
                    {
                        public Packager Packager { get; set; }
                    }

                    public class Packager
                    {
                        ...
                    }
                    

                    public class Mill
                    {
                        public Beer BrewBeer()
                        {
                            var malt = millGrains();
                            return MashTun.MashMalt( malt );
                        }
                    }

                    public class MashTun
                    {
                        public Beer MashMalt( Malt malt )
                        {
                            var wort = mash( malt );
                            var lauteredWort = lauterWort( wort );
                            return Boiler.BoilWort( lauteredWort );
                        }
                    }

                    public class Boiler
                    {
                        public Beer BoilWort( Wort wort )
                        {
                            var wort = boil( wort );
                            var cooledWort = coolWort( wort );
                            return Fermenter.FermentBeer( cooledWort );
                        }
                    }

                    public class Fermenter
                    {
                        public Beer FermentBeer( Wort wort )
                        {
                            var beer = ferment( wort );
                            var filteredBeer = filter( beer );
                            return package( filteredBeer );
                        }
                    }
                    

What Makes Hard-to-Test Code?

Deep Inheritance Hierarchies


                    public class Shape { }
                        
                    public class Triangle : Shape { }

                    public class IsoscelesTriangle : Triangle { }

                    public class RightIsoscelesTriangle : IsoscelesTriangle { }
                    

                    public class RightIsoscelesTriangle : IsoscelesTriangle
                    {
                        public override void Draw()
                        {
                            drawTriangle();
                            base.Draw();
                        }
                    }
                    

                    public RightIsoscelesTriangle : IShape
                    {
                        public void Draw()
                        {
                            drawTriangle();
                        }
                    }
                    

What Makes Hard-to-Test Code?

Complex Conditional Logic


                    public bool CheckIfAllProcessesCompleted()
                    {
                    var firstProcessCompleted = false;
                    var secondProcessCompleted = false;
                    var allCompleted = false;

                    if ( _runFirstProcess.Checked )
                    {
                        if ( _firstProcess.Completed )
                        {
                            firstProcessCompleted = true;
                        }
                        else
                        {
                            firstProcessCompleted = false;
                            allCompleted = false;
                        }
                    }
                    else
                    {
                        firstProcessCompleted = true;
                    }

                    if ( _runSecondProcess.Checked )
                    {
                        if ( _secondProcess.Completed )
                        {
                            secondProcessCompleted = true;

                            if ( firstProcessCompleted )
                            {
                                allCompleted = true;
                            }
                            else
                            {
                                allCompleted = false;
                            }
                        }
                        else
                        {
                            allCompleted = false;
                        }
                    }
                    else
                    {
                        secondProcessCompleted = true;

                        if ( firstProcessCompleted )
                        {
                            allCompleted = true;
                        }
                        else
                        {
                            allCompleted = false;
                        }
                    }

                    return allCompleted;
                    }
                    

                    public bool CheckIfAllProcesesCompleted()
                    {
                        return processes.Where( p => p.Checked )
                                        .All( p => p.Completed );
                    }
                    

What Makes Hard-to-Test Code?

High Degree of Coupling


                    public class InvoiceSender 
                    {
                        private Invoice _invoice;

                        private DatabaseWriter _databaseWriter = new DatabaseWriter();
                        private AccountingServiceWriter _accountingServiceWriter = 
                            new AccountingServiceWriter();
                        private CustomerPortalWriter _customerPortalWriter = 
                            new CustomerPortalWriter();
                        
                        public InvoiceSender( Invoice _invoice )
                        {
                            _invoice = invoice;
                        } 
                    }
                    

                    public class InvoiceSender 
                    {
                        public void SendToDatabase()
                        {
                            _databaseWriter.InsertInvoice( _invoice );
                        }

                        public void SendToAccountingService()
                        {
                            _accountingServiceWriter.SendInvoice( _invoice );
                        }

                        public void SendToCustomerPortal()
                        {
                            _customerPortalWriter.SendInvoice( _invoice );
                        }         
                    }
                    

                    public void SendInvoice( IExportInvoice invoiceExporter )
                    {
                        invoiceExporter.Send( _invoice );
                    }
                    

What Makes Hard-to-Test Code?

Procedural Code and Static Methods


                    public static void SendMemberRenewalNotifications()
                    {
                        var members = MemberManager.GetMembersForRenewal();

                        foreach ( var member in members )
                        {
                            if ( member.CommunicationPreference == 
                                CommunicationPreference.Email )
                                MemberCommunicator.SendEmailToMember( 
                                    member, RenewalNofitication.EmailContent );

                            if ( member.CommunicationPreference == 
                                CommunicationPreference.SMS )
                                MemberCommunicator.SendSMSToMember( 
                                    member, RenewalNofitication.SMSContent );

                            if ( member.CommunicationPreference == 
                                CommunicationPreference.Phone )
                                MemberCommunicator.CallMember( 
                                    smember, RenewalNofitication.PhoneScript );
                        }
                    }
                    

                    public class MemberNotifier
                    {
                        private IEnumerable _members;

                        public MemberNotifier( IEnumerable members )
                        {
                            _members = members;
                        }


                        public void SendRenewalNotification( 
                            IMemberCommunicator memberCommunicator )
                        {
                            memberCommunicator.SendRenewalNotification( _members );
                        }
                    }
                    

Benefits of Writing Testable Code

Short, Clean Methods

That only perform one function

Flat architectures

Loose coupling

With well composed objects

And dependencies with variable implementations

Clear Boundaries