Skip Navigation LinksHome > SSW Standards > SSW Rules > Rules To Better Unit Tests

What others have to say about us
See what people think about this product I've been putting together Development Guidelines for my employer and in the process have reviewed many published standards (in the .Net arena) from around the world. In each category, the suggestions at SSW are always among the best. See what people think about this product
- Leon Bambrick,
 

Do you agree with them all? Are we missing some? Email me your tips, thoughts or arguments. Let me know what you think.

Red star Indicates important rule

  1. Do you know what unit tests to write and how many?Red star

    People aim for 100% Unit Test Coverage but in the real world this is 100% impractical. Actually it seems that the most popular metric in TDD (Test Driven Development) is to aim for 100% of methods to be unit tested. However in the real world this goal is rarely, if ever, achieved. Unit tests are created to validate and assert that public and protected methods of a class meet an expected outcome based on varying input. This includes both good and bad data being tested, to ensure the method behaves as expected and returns the correct result or traps any errors.

    Generally, private methods should not have unit tests written for them as they are not exposed to other objects outside the original class. These private methods are likely to be refactored (eg. changed, renamed) over time and will require the unit tests to be updated and this becomes a maintenance nightmare. So how do private methods get tested? Private methods should be tested by the unit tests on the public and protected methods calling them and this will indirectly test the private method behaves as intended.

    Eg. You would test correct input such as 12/3 = 4 plus bad input such as 12/4 <> 4 and that 12/0 does not crash the application, and instead a DivideByZero Exception is thrown and handled gracefully.
    Eg. Methods returning a Boolean value need to have both true and false test cases.

    Unit tests should be written for:

    • Dependencies - e.g. DLLs Run time errors (JIT) - see below
    • Dependencies - e.g. Database Schema, Datasets, Web Services - see below
    • Fragile Code - e.g. Regular Expressions - see below
    • When errors can be difficult to spot - e.g. Rounding, arithmetic, calculations - see below
    • Performance - e.g. Slow forms, Time critical applications - see below

    Unit tests should not be written:

    • When code has been generated from Code Generators eg. SQL database functions Customer.Select, Customer.Update, Customer. Insert, Customer. Delete)
    • When unit tests become bigger than the original function eg. When you know to insert items into a database in the SetUp to test a function that uses the database
    • For Private methods because these will be tested by the public functions calling them, and they are likely to be change or refactored.
  2. Do you follow the standard naming conventions for Unit Tests?Red star

    Test Object Recommended Style Example
    Project Name UnitTests UnitTests
    Test Fixture Name [Type]Tests OrdersTests, CustomerTests, DeveloperTests
    Test Case [Function]Test ConstructorTest, InsertDataTest, LoginTest
    Set Up SetUp  
    Tear Down TearDown  
    Figure: Good Naming for a Unit Test Project (Good)

    Sample Code:

    using System;
    using System.Collections;
    using System.Data;
    using System.Data.SqlClient;
    using NUnit.Framework;
    using SSW.NetToolKit.BusinessService;
    using SSW.NetToolKit.DataAccess;

    namespace SSW.NETToolkit.UnitTests
    {

    /// <summary>
    /// Unit Tests Class. Functionally Tests the Business Rules
    /// </summary>
    [TestFixture]

    public class CustomerTests
    {
        [SetUp]
        public void SetUp()
        {
            // Initialize the tests e.g. add entries into database
        }
        [TearDown]
        public void TearDown()
        {
            // Finalize tests e.g. remove entries that were added in SetUp()
        }

        [Test]
        public void OrderTotalTest()
        {
            BusinessRules business = new BusinessRules();
            decimal calculatedGrandTotal = business.CalculateOrderGrandTotal( 10248 );

            // Calculate the grand total for a simple example order items.
            Assert.AreEqual(440, calculatedGrandTotal, "Calculated grand total didn't match the expected.");

            // Example with discounts
            calculatedGrandTotal = business.CalculateOrderGrandTotal( 10260 );
            Assert.AreEqual(1504.65m, calculatedGrandTotal, "Calculated grand total didn't match the expected.");
        }

        [Test]
        public void RoundingTest()
        {
            BusinessRules business = new BusinessRules();

            // test round up
            Assert.AreEqual(149.03, business.ApplyRounding(149.0282m), "Incorrect rounding rules applied for round up condition.");

            // test round down
            Assert.AreEqual(149.02, business.ApplyRounding(149.0232m), "Incorrect rounding rules applied for round down condition.");

            // test no rounding needed
            Assert.AreEqual(149.02, business.ApplyRounding(149.02m), "Incorrect rounding rules applied for no rounding condition.");

            // test border condition
            Assert.AreEqual(149.02, business.ApplyRounding(149.025m), "Incorrect rounding rules applied for border condition.");
        }
    }

    Test Generation Settings
    Figure: This rule is consistent with the Visual Studio default

    We have a program called SSW .Net Toolkit that implements this.

    Tip: You can create a test project using the Unit Test Wizard: Test > Add New Test

    Add New Test
    Figure: Unit Test Wizard 1
    Create Unit Tests
    Figure: Unit Test Wizard 2
  3. Do you have unit tests outside the project - not inside?Red star

    Unit tests should be written outside the project because:

    • Separation of duties - the guys writing the unit tests should not have knowledge of the internals of the project - they should see it as a black box
    • Most developers don't want the unit tests deployed - even with conditional compilation you leave references behind
    • The projects will be smaller without the unit tests
    Figure: Bad Structure;
    Figure: Bad Structure - each layer for UnitTests are related to each other, meaning it's not easy to maintain;
    Figure: Good Structure - each layer for UnitTests are isolated from each other, meaning it's easier to maintain (adding new project and test, removing a project and its test cases);
  4. Do you write unit tests for Dependencies - e.g. DLLs?Red star

    Dependant code is code that relies on other factors like methods and classes inside a separate DLL. Because of the way the .NET works assemblies are loaded as required by the program (this is what we call the JIT compiler). Thus, when a DLL goes astray, you will only find out at run time when you run a form/function that uses that DLL. These run time errors can occur when you have not packaged DLLs in your release or if the versions are incompatible. Such errors cause the following exceptions:

    • An unhandled exception ("System.IO.FileNotFoundException") occurred in SSW.NETToolkit.exe.
    • System.IO.FileLoadException The located assembly's manifest definition with name 'SSW.SQLDeploy.Check' does not match the assembly reference.

    These errors can be fixed by writing a Unit Test to check all referenced assemblies in a project.

    Sample Code:

    [Test]
    public void ReferencedAssembliesTest()
    {
        // Get the executing assembly
        Assembly asm = Assembly.GetExecutingAssembly();
        // Get the assemblies that are referenced
        AssemblyName[] refAsms = asm.GetReferencedAssemblies();
        // Loop through and try to load each assembly
        foreach( AssemblyName refAsmName in refAsms)
        {
            try
            {
                Assembly.Load(refAsmName);
    
         	}
            catch(FileNotFoundException)
            {
                // Missing assembly
                Assert.Fail(refAsmName.FullName + " failed to load");
            }
        }
    }
    Figure: This code is a unit test for checking that all referenced assemblies are able to load.

    We have a program called SSW .Net Toolkit that implements this.

  5. Do you *not* write unit tests for Database Dependencies - e.g. Database Schema, Datasets?Red star

    If we did our Data Access layer manually we should be testing this. Instead we recommend using:

    1. LINQ (VS2008) or Code Generators (VS2005)
    2. Use a _regenerate.bat
    3. Add a unit test to create the database and check that "reconcile" works

      We have a program called SSW SQL Deploy that implements this.

    Figure: The dataset and the database schema are not consistent (Bad)
    Figure: The dataset and the database schema are consistent (Good)

    As long as you have a _regenerate.bat file your schema will not get out of sync.

  6. Do you write unit tests for Web Service Dependencies?Red star

    With web services you should:

    1. Use a _regenerate.bat to regenerate the proxy classes (See our Rules To BetterWindows Forms for more information.)
    2. Compare old WSDL with new WSDL to pick up changes and email the diff file to developers
    3. Minimum Tests - Call the web service to check it’s alive
    4. Performance Test - If it takes longer than 4 seconds to call that methods – it fails

  7. Do you test Fragile Code - e.g. Regular Expressions?Red star

    Fragile code is code that often breaks and are difficult to understand. An example of this is with Regular Expressions.

    When using regular expressions, because of their rather cryptic syntax, should always be associated with at least two sample tests - one that passes the regular expression and one that fails. These regular expressions should be stored in a central location, i.e. a database or a class file.

    A Unit Test should be written such that it loops through each of these regular expressions and tests it against the supplied test cases.

    Sample Code:

    public bool ValidateEmail(string txtEmail)
    {
       RegexList regexList = new RegexList();
       Regex r = new Regex("^[\w-]+(?:\.[\w-]+)*@(?:[\w-]+\.)+[a-zA-Z]{2,7}$") // checks email
       Match m = r.Match(txtEmail);
       return m.Success;
    }	
    
    Figure: A function to validate email addresses
    [Test]
    public void GoodEmailRegexTest()
    {
       string txtEmail = "joeblogs@blogsville.com";
       Assert.IsTrue(ValidateEmail(txtEmail));
    }
    
    Figure: Tests a good case
    [Test]
    public void BadEmailRegexTest()
    {
       string txtEmail = "joeshmo.com.au";
       Assert.IsFalse(ValidateEmail(txtEmail));
    }
    
    Figure: Tests a bad case
  8. Do you have tests for difficult to spot errors - e.g. Arithmetic, Rounding, Calculations?Red star

    By difficult to spot errors we mean errors that do not give the user a prompt that an error has occurred. Things such as: Arithmetic, Rounding or Calculations should have unit tests written for them.

    Sample Code:

    DataAccess |Utilities.cs
    
    using System;
    using System.Collections.Generic;
    using System.Text;
    
    namespace DataAccess {
          public class Utilities {
                              // A business rule – it should be unit tested
                public decimal CalculateTotal(List items) {
                      decimal total = 0.0M;
                      foreach (MyItem i in items) {
                            total += i.UnitPrice * (1 - i.Discount);
                      }
                      return total;
                }     
          }
    }
    		
    Figure: Function to calculate a total for a list of items

    For a function like this, it might be simple to spot errors when there are one or two items, but if you were to calculate the total for 50 items, then the task of spotting an error isn't so easy. That's why a unit test should be written so that you know when the function doesn't work.

    Sample Test:

    DataAccess.Tests | UtilitiesTest.cs
    
    using System;
    using System.Collections.Generic;
    using System.Text;
    using NUnit.Framework;
    namespace DataAccess.Tests {
    
          [TestFixture()]
          public class UtilitiesTests {
    
                [Test()]
                public void CalculateTotalSimpleTest() {
                      List<myitem> items = new List<MyItem>();
                      items.Add(new MyItem(12.50M));
                      items.Add(new MyItem(4.75M));
                      items.Add(new MyItem(9.05M));
                      items.Add(new MyItem(6.55M));
                      items.Add(new MyItem(20.12M));
     
                      decimal expected = 52.97M;
                      Utilities u = new Utilities();
                      decimal actual = u.CalculateTotal(items);
    
                      Assert.AreEqual(expected, actual);
                }
    
                [Test()]
                public void CalculateTotalWithDiscountTest() {
                      List<myitem> items = new List<myitem>();
                      items.Add(new MyItem(12.50M, 0.1M));
                      items.Add(new MyItem(4.75M));
                      items.Add(new MyItem(9.05M));
                      items.Add(new MyItem(6.55M));
                      items.Add(new MyItem(20.12M));
     
                      decimal expected = 51.72M;
                      Utilities u = new Utilities();
                      Assert.IsNotNull(u);
    
                      decimal actual = u.CalculateTotal(items);
                      Assert.AreEqual(expected, actual);
                }
          }
    }
    
    DataAccess | MyItem.cs
    
    using System;
    using System.Collections.Generic;
    using System.Text;
    
    namespace DataAccess {
          public class MyItem {
                public MyItem(decimal unitPrice) {
                      _unitPrice = unitPrice;
                }
    
                public MyItem(decimal unitPrice, decimal discount) : this(unitPrice) {
                      _discount = discount;
                }
    
                private decimal _unitPrice;
                private decimal _discount;
    
                public decimal Discount {
                      get { return _discount; }
                      set { _discount = value; }
                }
    
                public decimal UnitPrice {
                      get { return _unitPrice; }
                      set { _unitPrice = value; }
                }
          }
    }
    		
    Figure: Test calculate total by checking something we know the result of. (Note: it doesn’t need a failure case because it isn’t a Regex.)

    We have a program called SSW .Net Toolkit that implements this.

  9. Do you have tests for Performance? Red star

    Typically there are User Acceptance Tests that need to be written to measure the performance of your application. As a general rule of thumb, Forms should not load in more than 4 seconds. This can be done using NUnit.

    Sample Code:

    
        public abstract class FormTestBase<F>
            where F : Form, new()
        {
            protected TimeSpan _ExpectedLoadTime = TimeSpan.FromSeconds(4);
    
            [Test]
            public void LoadTest()
            {
                Stopwatch stopwatch = new Stopwatch();
    
                stopwatch.Start();
    
                F testForm = new F();
                testForm.Show();
                testForm.Close();
    
                stopwatch.Stop();
    
                Console.WriteLine("Form [{0}] took {1:#,##0.0} seconds to open", typeof(F), stopwatch.Elapsed.TotalSeconds);
                Assert.IsTrue(stopwatch.Elapsed < _ExpectedLoadTime, 
                    string.Format("Loading time ({0:#,##0.0} seconds) exceed the expected time ({1:#,##0.0} seconds).", 
                    stopwatch.Elapsed.TotalSeconds, _ExpectedLoadTime.TotalSeconds));
            }
        }
        
        
        [TestFixture]
        public class LoginFormTests : FormTestBase<LoginForm>
        {
        }
        
        [TestFixture]
        public class MainFormTests : FormTestBase<MainForm>
        {
        }
    		
    Figure: This code tests that the LoginForm and MainForm load in under 4 seconds

    We have a program called SSW .Net Toolkit that implements this.

  10. Do you run Unit Tests with the right click of your mouse?Red star

    You need to be able to use a right click menu to run the unit tests. You have two options:

    1. Resharper (Recommended)
    2. Test Driven .NET

    Both integrate with Visual Studio, NUnit and other programs to make testing your software easy.

    Resharper
    Figure: Run your unit tests from your right click menu using Resharper (Recommended)
    TestDriven .NET
    Figure: Run your unit tests from your right click menu using Test Driven .NET
  11. Do you know when functions are too complicated?

    In general you should always be looking to simplify your code (e.g. heavily nested case statements). As a minimum look for the most complicated method you have and check that it needs simplifying.

    In VS 2008, there is inbuilt support for Cyclomatic Complexity analysis.
    1. Go to Developer > Code Metrics > Generate for Solution
      Code Metrics
      Figure: Cyclomatic Complexity analysis tool
    2. Look at the largest Cyclomatic Complexity number and refactor.
      Results from Cyclomatic analysis
      Figure: Results from Cyclomatic analysis these metrics give an indication on how complicated functions are.

  12. Menu - Do you have a standard 'Help' menu that includes a way to run your unit tests?

    Thanks Adam for coming to our user group in Boston and presenting your session “Rules to Healthier Code”
    While I had used most of your best tips I’d never thought to put them in concert as you have. In concert I mean together.. I have used and demo’d NUNIT, FxCop. MBUnit and almost the full range of third party tools you suggest. I tend to use them when I need to fix a particular problem.
    BUT I never thought to use all of them together. Like your tip to include NUnit with the finished product! Just brilliant! It still makes me laugh at the simplicity of it. I get the flash of a cool validation tool and user validation for the work I’ve already done in NUnit. Its good marketing, good salesmanship and good engineering! Its just elegant.. I might include it with my setup as part of the install! “Click here to validate your install” It’ll force me to be more diligent about NUnit, but now there is a better reason for the diligence.
    Again thanks for the great presentation Wednesday. I’ll be re-working the concept into my own UG programs here in New Hampshire.

    Pat Tomey
    www.4square.net

    Your standard help menu should include an option to run your Unit Tests. Everybody knows the importance of Unit tests for the middle tier. However, Unit Tests are also important to capture problems that occur on other peoples' machines so that users can perform a quick check when a product is not behaving correctly. This is important for troubleshooting during support calls and enables your customers to do a Health Check on the product.

    And yes, there are many tests that can be written that will pass on the developers PC – but not on the users PC. e.g. Ability to write to a directory, missing dlls, missing tables in the schema etc.

    Note: Adding this option requires you to include NUnit in your setup.exe (See Include all the files needed in our Wise Standard)

    Figure: Standard Help menu should give you an option to Run Unit Tests to check the users' environment (Good)
    Figure: Obviously the red indicates that there is a problem with a Unit Test (Good)

    We have a rule Do you know the six items every Help menu needs?

    We have a program called SSW .Net Toolkit that implements this.

  13. Do you consider testing your fragile javascript?

    You can also write unit tests for Javascript code. With AJAX becoming the norm, more javascript is being written, and since there isn’t good intelligense support for it, it is prone to errors.

    You can write unit tests for javascript using:
    1. JsUnit
      Code Metrics
      Figure: TestRunner
    2. AJAX toolkit test harness
      Results from Cyclomatic analysis
      Figure: AJAX toolkit test harness

  14. Do you use NUnit (even over Visual Studio Team System)?

    Unit tests shouldn't only for developers, it should be able to run on customer's machines as well. Based on this consideration, we still NUnit in order to allow our users to run available tests on their machine. This will help us collect more useful information on client machines and give our products more confidence and professional look.

    As NUnit provides a great UI for this purpose, we recommend you to use NUnit even you've already been working on VSTS.

    Figure: NUnit UI
  15. Did you know Visual Studio Team System does the same thing (but we don't recommend it)?

    Visual Studio Team System has unit testing integrated into the IDE. There is a new Test View window and a Test Results window. You can run the unit tests from the right click menu in Visual Studio Team System.

    Figure: New Test View window (run tests from right click)
    Figure: New Test Results window

    You can write unit tests much the same way as in NUnit with a few changes in syntax:

    Test Attribute NUnit Visual Studio Team System
    Test Fixture [TestFixture] [TestClass]
    Set Up [SetUp] [TestInitialize]
    Tear Down [TearDown] [CleanUp]
    Test [Test] [TestMethod]

    VSTS is a great integrated toolset and there are many features of it you need. However because there is no existing GUI to run these tests outside of VSTS (like you can with nUnit) there is no way of having this menu option in your application.
    Therefore we *don't recommend* it if you also want to Run Unit Tests from the Help Menu

  16. Do you write unit tests for your database configuration?

    We recommend you have test data for your database configuration and reconcile form rather than testing it by opening the UI and doing the configuration manually.

    Figure 1: Get user's confirmation
    Figure 2: Create test database
    Figure 3: Reconcile

    Sample Code:

    ///  <summary> 
    /// Create a temp database and do reconcile then drop it 
    ///</summary>
    ///<param name="Config">DBConfig Config</param>
    ///<returns>string</returns> 
    public static string ConfigAndReconcileTester(DBConfig Config) 
    
            {  
                try {
    
                    _deployForm = new DatabaseDeployForm(Config);
    
                    _deployForm.IsATest = true;
    
                    if(_deployForm.ShowDialog() != DialogResult.OK)
    
                    {
    
                        return "Failed to create test database";
    
                    }
    
                    Config.IsReconcile = true;
    
                    DialogResult reconciled = new 
                    SqlDeployHelper(Config).BeginProcess();              
    
    
                    if(reconciled == DialogResult.OK)
    
                    {
    
                        return SUCCESS_INFO;
    
                    }          
    
                    return "Reconciliation failed";
    
                }
    
                finally
    
                {
    
                    DropTestDB(Config);
    
                }
    
    }
                    
    Imports System
    
    Imports NUnit.Framework
    
    Imports SSW.Framework.Data
    
    Imports SSW.Framework.UnitTests
    
    Import SSW.Framework.Data.SqlServer.WindowsUI
    
    Imports SSW.SQLAuditor.WindowsUI
    
    
    
    Namespace Functional
    
    <TestFixture()> _
    
    Public Class DataBaseConfigureTests
    
    Private Const STR_SSWSQLAuditorNorthwindTest As String = "SSWSQLAuditorNorthwindTest"
    
    
    
    Private config As DBConfig = New DBConfig()
    
    
    
    
    <SetUp()> _
    
    Public Sub Setup()
    
    'configurations for database deploy form
    
        config.ConnectionBuilder.ConnectionString = String.Empty
    
        config.CreateScriptsPath = System.IO.Path.Combine(Application.StartupPath + "\..\..\", _
    
      "DatabaseSQLScripts")
    
        config.UpgradeScriptsPath = config.CreateScriptsPath  
    
        config.SampleScriptsPath = System.IO.Path.Combine(config.CreateScriptsPath, "Samples")
    
        config.NewDatabaseName = STR_SSWSQLAuditorNorthwindTest
    
        config.IsDatabaseNameEnforced = True
    
    
        config.ShouldCreateSamples = True
    
        config.IsNew = True
    
        config.IsSampleDatabaseNameEnforced = True
    
        config.SampleDatabaseName = STR_SSWSQLAuditorNorthwindTest  
    
        config.DatabaseNamePlaceholder = "[DatabaseName]"  
    
    End Sub
    
    
    
    <Test()> _
    
    Public Sub ConfigureTest()
    
        Dim testResult As String = String.Empty
    
    
    
        'get user's confirmation to continue test  
    
        If MessageBox.Show("This will create the sample database and reports and then delete them." _
    
        + Environment.NewLine + " Do you want to continue?", "Continue", MessageBoxButtons.OKCancel, _
    
        MessageBoxIcon.Exclamation) = DialogResult.Cancel Then
    
    	Return
    
        End If
    
      testResult = DatabaseConfigTester. ConfigAndReconcileTester(config)
    
        Assert.AreEqual(testResult, DatabaseConfigTester.SUCCESS_INFO, testResult)
    
    End Sub
    
    
    
    <TearDown()> _
    
    Public Sub RestoreToDefault()
    
    End Sub
    
    End Class
    End Namespace
    
  17. Do you write unit tests for your reporting service configuration?

    We also recommend you have test data for your reporting service configuration. However, before configuring the reporting service, you should specify a data source for the reports using your database deploy form.

    Figure: Reporting service config test

    Sample Code:

    /// 
                            <summary> 
                                /// Properties ApplicationName, ReportDirectoryName,
                                    ReportDirectoryName, ///
                                        ReportRdlFolderPathAlternative, ReportManagerUrl and ReportServerWebServiceUrl /// in rsConfig should
                                                be configed before start testing. /// </summary> ///
                                                        <param name="dbConfig"></param> ///
                                                            <param name="rsConfig"></param> 
                                                                    public static void
                                                                            StartTest(DBConfig dbConfig,RSConfig rsConfig) { (new DatabaseDeployForm(dbConfig)).ShowDialog(); 
                                                                                    //get a data source from database deploy form rsConfig.DataSourceConnectionString
                                                                            = dbConfig.ConnectionBuilder.ConnectionString;
                                                                                rsConfig.DataSourceName =
                                                                                    dbConfig.ConnectionBuilder.DatabaseName; //pop up a reporting
                                                                                        service config form PublishReportsForm prForm 
                                                                                            = new PublishReportsForm(new ReportSetupControl(),rsConfig); prForm.IsAUnitTest = 
                                                                                                            true; Assert.AreEqual(prForm.ShowDialog(),DialogResult.OK,"Cancled by user"); } } 


    usingSystem;usingSystem.Windows.Forms;
    usingSystem.Collections.Generic;
    usingSystem.Text;
    usingNUnit.Framework;
    usingSSW.Framework.Data;
    usingSSW.Framework.UnitTests;
    usingSSW.Framework.Data.SqlServer.WindowsUI;
    usingSSW.Framework.ReportingServices.WindowsUI;
    usingSSW.LinkAuditor.Common;
    namespace
       SSW.LinkAuditor.UnitTests.Functional { [TestFixture]
       publicclassReportingServiceConfigTests { 
       privateconststring STR_SSWLinkAuditor = "SSWLinkAuditor"; private DBConfig _dbConfig = new
       DBConfig(); RSConfig _rsConfig = new
       RSConfig(); [SetUp] public
       void SetUp()
       { //configurations for database deploy form
       _dbConfig.ConnectionBuilder.ConnectionString = String.Empty; _dbConfig.CreateScriptsPath 
       = System.IO.Path.Combine(Application.StartupPath
       + @"\..\Database", "Create Scripts"); _dbConfig.UpgradeScriptsPath
       = _dbConfig.CreateScriptsPath;
       _dbConfig.SampleScriptsPath =
       _dbConfig.CreateScriptsPath; _dbConfig.NewDatabaseName 
       = STR_SSWLinkAuditor; _dbConfig.IsDatabaseNameEnforced
       = false; _dbConfig.ShouldCreateSamples
       = false; _dbConfig.IsNew
       = true; _dbConfig.IsSampleDatabaseNameEnforced
       = false; _dbConfig.SampleDatabaseName
       = STR_SSWLinkAuditor;
       _dbConfig.DatabaseNamePlaceholder = "[DatabaseName]"; //configurations for reporting
       service config form _rsConfig.ApplicationName 
       = "SSW Link Auditor"; _rsConfig.ReportDirectoryName 
       = _rsConfig.ApplicationName 
       + " Reports"; _rsConfig.ReportRdlFolderPath 
       = AppDomain.CurrentDomain.BaseDirectory + 
       @"\Report\RS2000"; _rsConfig.ReportRdlFolderPathAlternative 
       = AppDomain.CurrentDomain.BaseDirectory + 
       @"\Reports\RS2005"; _rsConfig.ReportManagerUrl 
       = "http://localhost/Reports"; _rsConfig.ReportServerWebServiceUrl 
       = "http://localhost/reportserver/ReportService.asmx"; }
           [Test] public
        void ConfigTest()
        {  ReportingServiceConfigTester.StartTest(_dbConfig,_rsConfig);
    
         } } }
    
    
    
  18. Do you write unit tests to validate your web links?

    If you store your URL references in the application settings, you can create unit tests to validate them.
    Figure: URL for link stored in application settings

    Sample Code: How to test the URL
    	[Test]
            public void urlRulesToBetterInterfaces()
            {
                HttpStatusCode result = WebAccessTester.GetWebPageStatusCode(Settings.Default.urlRulesToBetterInterfaces);
                Assert.IsTrue(result == HttpStatusCode.OK, result.ToString());
            }
    	
    Sample Code: Method used to verify the Page
    	 public class WebAccessTester
        {        
            public static HttpStatusCode GetWebPageStatusCode(string url)
            {
                HttpWebRequest req = ((HttpWebRequest)(WebRequest.Create(url)));
                req.Proxy = new WebProxy();
                req.Proxy.Credentials = CredentialCache.DefaultCredentials;
                HttpWebResponse resp = null;
                try
                {
                    resp = ((HttpWebResponse)(req.GetResponse()));
                    if (resp.StatusCode == HttpStatusCode.OK)
                    {
                        if (url.ToLower().IndexOf("redirect") == -1 && url.ToLower().IndexOf(resp.ResponseUri.AbsolutePath.ToLower()) == -1)
                        {
                            return HttpStatusCode.NotFound;
                        }
                    }
                }
                catch (System.Exception ex)
                {
                    while (!(ex == null))
                    {
                        Console.WriteLine(ex.ToString());
                        Console.WriteLine("INNER EXCEPTION");
                        ex = ex.InnerException;
                    }
                }
                finally
                {
                    if (!(resp == null))
                    {
                        resp.Close();
                    }
                }
                return resp.StatusCode;
            }
        }
        
  19. Do you isolate your logic and remove dependencies on instances of objects?

    If there are complex logic evaluations in your code, we recommend you isoloate them and write unit tests for them.

    Take this for example:

    while ((ActiveThreads > 0 || AssociationsQueued > 0) && (IsRegistered || report.TotalTargets <= 1000 ) && (maxNumPagesToScan == -1 || report.TotalTargets < maxNumPagesToScan) && (!CancelScan))
    Figure: This complex logic evaluation can't be unit tested.

    Writing a unit test for this piece of logic is virtually impossible – the only time it is executed it during a scan, and there are lots of other things happening at the same time meaning the unit test will often fail and you won’t be able to identify the cause anyway.

    We can update this code to make it testable though …

    • Update the line to this:
    • while (!HasFinishedInitializing (ActiveThreads, AssociationsQueued, IsRegistered, 
          report.TotalTargets, maxNumPagesToScan, CancelScan))
      		
      Figure: Isolate the complex logic evaluation.

      We are using all the same parameters – however now we are moving the actual logic to a separate method.

    • Now create the method:
    • private bool HasFinishedInitializing(int ActiveThreads, int AssociationsQueued, bool IsRegistered, 
          int TotalAssociations, int MaxNumPagesToScan, bool CancelScan)
      {
         return (ActiveThreads > 0 || AssociationsQueued > 0) && (IsRegistered || TotalAssociations <= 1000 )
             && (maxNumPagesToScan == -1 || TotalAssociations < maxNumPagesToScan) && (!CancelScan);		
      }
      		
      Figure: Function of the complex logic evaluation.

    The critical thing is that everything the method needs to know is passed in … it mustn’t go out and get any information for itself and mustn’t rely on any other objects being instantiated. A good way to enforce this is to make each of your logic methods static. It has to be completely self contained.

    The other thing we can do now is actually go and simplify / expand out the logic so that it’s a bit easier to digest.

    private bool HasFinishedInitializing(int ActiveThreads, int AssociationsQueued, bool IsRegistered, 
        int TotalAssociations, int MaxNumPagesToScan, bool CancelScan)
    {
       //Cancel
       if (CancelScan)     
       { return true; }
    
       //only up to 1000 links if it is not a registered version
       if (!IsRegistered && TotalAssociations > 1000) 
       { return true; }
    
       //only scan up to the specified number of links
       if (MaxNumPagesToScan != -1 && TotalAssociations > MaxNumPagesToScan) 
       { return true; }
    
       //not ActiveThread and the Queue is full
       if(ActiveThreads <= 0 && AssociationsQueued <= 0) 
       { return true; }
    
       return false;
    }		
    Figure: Simplify the complex logic evaluation.

    The big advantage now is that we can unit test this code easily in a whole range of different scenarios!

    [Test]
    public void HasFinishedInitializingLogicTest()
    {
       Validator validator = new Validator();
    
        //Set up scenario A
       int activeThreads = 2;
       int associationsQueued = 20;
       bool isRegistered = false;
       int totalAssociations = 1200;
       int maxNumPagesToScan = -1;
       bool cancelScan = false;
    
       bool actual = (bool)Reflection.InvokeMethod("HasFinishedInitializing", validator,
           new object[] {activeThreads, associationsQueued, isRegistered,
           totalAssociations, maxNumPagesToScan, cancelScan});
       Assert.IsTrue(actual, "HasFinishedInitializing LogicTest A failed.");
    
    
       //Set up scenario B
       activeThreads = 2;
       associationsQueued = 20;
       isRegistered = true;
       totalAssociations = 1200;
       maxNumPagesToScan = -1;
       cancelScan = false;
               
       actual = (bool)Reflection.InvokeMethod("HasFinishedInitializing", validator,
           new object[] {activeThreads, associationsQueued, isRegistered,
           totalAssociations, maxNumPagesToScan, cancelScan});
       Assert.IsFalse(actual, "HasFinishedInitializing LogicTest B failed.");
       }	
    		
    Figure: Write unit test for complex logic evaluation.
  20. Do you isolate your logic from your IO to increase the testability?

    If your method is consist of logic and IO, we recommend you isoloate them to increase the testability of the logic.

    Take this for example (and see how we refactor it):

    public static List GetFilesInProject(string projectFile)
    {
        List<string> files = new List<string>();
    
        TextReader tr = File.OpenText(projectFile);
    
        Regex regex = RegexPool.DefaultInstance[RegularExpression.GetFilesInProject];
        MatchCollection matches = regex.Matches(tr.ReadToEnd());
    
        tr.Close();
    
        string folder = Path.GetDirectoryName(projectFile);
    
        foreach (Match match in matches)
        {
            string filePath = Path.Combine(folder, match.Groups["FileName"].Value);
    
            if (File.Exists(filePath))
            {
                files.Add(filePath);
            }
        }
    
        return files;
    }
    Bad - The logic and the IO are coded in a same method.

    While this is a small concise and fairly robust piece of code, it still isn't that easy to unit test. Writing a unit test for this would require us to create temporary files on the hard drive, and probably ending up requiring more code than the method itself.

    If we start by refactoring it with an overload, we can remove the IO dependency and extract the logic further making it easier to test:

    public static List<string> GetFilesInProject(string projectFile)
    {
        string projectFileContents;
        using (TextReader reader = File.OpenText(projectFile))
        {
            projectFileContents = reader.ReadToEnd();
            reader.Close();
        }
    
        string baseFolder = Path.GetDirectoryName(projectFile);
    
        return GetFilesInProjectByContents(projectFileContents, baseFolder, true);
    }
    
    public static List<string> GetFilesInProjectByContents(string projectFileContents, string baseFolder, bool checkFileExists)
    {
        List<string> files = new List<string>();
    
        Regex regex = RegexPool.DefaultInstance[RegularExpression.GetFilesInProject];
        MatchCollection matches = regex.Matches(projectFileContents);
    
        foreach (Match match in matches)
        {
            string filePath = Path.Combine(baseFolder, match.Groups["FileName"].Value);
    
            if (File.Exists(filePath) || !checkFileExists)
            {
                files.Add(filePath);
            }
        }
    
        return files;
    }
    Good - The logic is now isolated from the IO.

    The first method (GetFilesInProject) is simple enough that it can remain untested. We do however want to test the second method (GetFilesInProjectByContents). Testing the second method is now too easy:

    [Test]
    public void TestVS2003CSProj()
    {
        string projectFileContents = VSProjects.VS2003CSProj;
        string baseFolder = @"C:\NoSuchFolder";
    
        List<string> result = CommHelper.GetFilesInProjectByContents(projectFileContents, baseFolder, false);
        Assert.AreEqual(15, result.Count);
        Assert.AreEqual(true, result.Contains(Path.Combine(baseFolder, "BaseForm.cs")));
        Assert.AreEqual(true, result.Contains(Path.Combine(baseFolder, "AssemblyInfo.cs")));
    }
    
    [Test]
    public void TestVS2005CSProj()
    {
        string projectFileContents = VSProjects.VS2005CSProj;
        string baseFolder = @"C:\NoSuchFolder";
    
        List<string> result = CommHelper.GetFilesInProjectByContents(projectFileContents, baseFolder, false);
        Assert.AreEqual(6, result.Count);
        Assert.AreEqual(true, result.Contains(Path.Combine(baseFolder, "OptionsUI.cs")));
        Assert.AreEqual(true, result.Contains(Path.Combine(baseFolder, "VSAddInMain.cs")));
    }
    Good - Different test cases and assertions are created to test the logic.
  21. Do you reply "DONE + Added a unit test so it can't happen again"?

    When you encounter a bug in your application you should never let the same bug happen again. The best way to do this is to write a unit test for the bug.
    See our Rules to Better Email to implement a good 'DONE' email.

  22. Do you know the right version and config for Nunit?

    There are multiple version of NUnit and .NET Framework, the following introduce to you how to use them correctly.
    • if your application was built with .NET Framework 1.1, so NUnit 2.2.0 which was built with .NET Framework 1.1 is the best choice if you compact it into the installation package, and then you don't need any additional config - it will auto use .NET Framework 1.1 to reflect your assembly;
      • If there is only .NET Framework 2.0 on the client side, how to make it works?
        Just add the yellow into nunit-gui.exe.config (it is under the same folder as nunit-gui.exe), which tell NUnit to reflect your assembly with .NET Framework 2.0;

        ...
        <startup>
        <supportedRuntime version="v2.0.50727" />
        <supportedRuntime version="v1.1.4322" />
        <supportedRuntime version="v1.0.3705" />
        <requiredRuntime version="v1.0.3705" />
        </startup>
        ...

    • if your application was built with .NET Framework 2.0, then you may get choices:
      • NUnit 2.2.7 or higher (built with .NET framework 2.0) (recommended)
        Then you don't need any extra configuration for NUnit, just follow the default;
      • NUnit 2.2.0 or lower (built with .NET Framework 1.1)
        Then you need to add the yellow statement (see above in this section);
  23. Do you have a zsValidate page to test your website dependencies?

    There are two kinds of errors, coding errors and deployment errors, coding errors should be find during development by compiling or debugging, while deployment errors should be find by a validation test.
    Refer to the following rules for details:
    See SSW Rules - Do you have a zsValidate page to make sure your website is healthy?
    See SSW Rules - Do you have a Validation Page for your web server?

  24. Do you have a Unit test for your send mail code?

    The code below could help you test your send mail code:

    DotNetOpenMailProvider provider = new DotNetOpenMailProvider();
    NameValueCollection configValue = new NameValueCollection();
    configValue["smtpServer"] = "127.0.0.1";
    configValue["port"] = "8081";
    provider.Initialize("providerTest", configValue);
    
    TestSmtpServer receivingServer = new TestSmtpServer();
    try
    {
        receivingServer.Start("127.0.0.1", 8081);
        provider.Send("phil@example.com", 
                    "nobody@example.com", 
                    "Subject to nothing", 
                    "Mr. Watson. Come here. I need you.");
    }
    finally
    {
        receivingServer.Stop();
    }
    
    // So Did It Work?
    Assert.AreEqual(1, receivingServer.Inbox.Count);
    ReceivedEmailMessage received = receivingServer.Inbox[0];
    Assert.AreEqual("phil@example.com", received.ToAddress.Email);
                            
    Figure: This code could help you validate the send mail code.

Acknowledgments

Adam Cogan
Eric Phan
Mark Liu
Troy Magennis