Writing Unit Tests

  • Why do we write unit tests?

    • Improve code quality

    • Fewer reported defects

    • Make checking code faster

    • Tell us when we've broken something

    • Tell us when our work is done

    • Allow others to check our code

    • Encourage modular design

    • Keep behaviour constant during refactoring Functions as a spec (think TDD)

  • The law of diminishing returns most definitely applies here

    • Testing everything is infeasible. Don't be unrealistic.

    • 70% code coverage is actually pretty decent for most codebases.

    • First, test the common stuff.

    • Next, test the common exception-case stuff.

    • Then test the critical stuff.

    • Add other tests as appropriate.

  • When to write a unit test

    • First :)

    • Use a unit test to provide a framework for writing your code

    • If you find yourself running up an entire application more than once or twice to test a particular

    • method you've written, wrap it in a unit test and use that test to invoke it directly

    • ^R ^T is your friend

    • When someone comes to you with a bug report, write a test to reproduce the bug.

Key Principles for Unit Tests

  • Each and every test must be able to be run in isolation

    • Tests should the environment up for themselves and clean up afterwards

      • Use your ClassInitialize, ClassCleanup, TestInitialize and TestCleanup attributes if you're in MSTest-land, and the equivalents for NUnit, XUnit etc.
    • Tests should never rely on being executed in any particular order (that's part of the meaning of "unit")

    • Tests should not rely overmuch on their environment

      • Don't depend on files' being anywhere

      • Don't hard-code paths. This will bite you.

    • If a class depends on another class that depends on another class that you can't easily instantiate in your unit test, this suggests that your classes need refactoring. Writing tests should be easy. If your classes make it hard, fix your classes first.

  • Tests should be cheap to write

    • Don't worry about exception-handling - if an unexpected exception is thrown, the test fails. Don't bother catching it and manually asserting failure.

    • Be as explicit as you can

      • Don't allow for variations in your output unless you absolutely have to.

      • If there are going to be different outputs, ideally there should be different tests

  • Tests should be numerous and cheap to maintain

    • Each test should test one (perhaps two or three, but generally just one) behaviour

    • It's much better to have lots of small tests that check individual functionality rather than fewer, complex tests that test many things.

    • When a test breaks, we want to know exactly where the problem is, not just that there's a problem somewhere in a call stack seven classes deep.

  • Tests should be disposable

    • When the code it tests is gone, the test should be dropped on the floor.

    • If it's a simple, obvious test, it will be simple and obvious to identify when this should happen.

  • Tests need not be efficient

Useful Stuff

  • Private Accessors

    • These allow you to call private methods and access private variables from another class

    • This deliberately breaks OO principles

    • Use it for testing implementation-specific stuff, but depend on concrete types when you do.

  • clip_image001

  • ASPX page testing

    • You can automate all sorts of stuff with respect to ASPX pages

    • Button clicks

    • Form inputs

    • clip_image002

    • This does not replace regression test automation (e.g. Selenium, Mercury et al), but should be used by individual developers when writing new ASPX pages and ASCX controls.

    • Don't run the application and click stuff manually

    • Write a unit test and tell it to click stuff automatically

  • Testing web service methods

    • The MSTest framework will get confused if you try to actually invoke things via HTTP.

    • It's better to just call the web endpoints directly in-process.

  • Using the unit testing framework for integration testing

    • Have a couple of common-case "unit" tests that actually represent an end-to-end use case of your application.

Note for young players: if you get an invalid cast exception such as...

Test method Test.Zap.CubeModel.DigitalSearchTreeTest.TestInsert threw exception: System.InvalidCastException: Unable to cast object of type 'Node`1[System.Char,System.Char]' to type 'Node`1[System.Char,System.Char]'..

...it may be that your type is a nested class or similar and does not have public visibility.