TechEd 2008 notes: Advanced Unit Testing Topics

He repeated a lot of his favorite patterns that I already took notes about in his earlier session, “Design and Testability“. If you haven’t read that one yet, go check it out.

Advanced Unit Testing Topics
Ray Osherove
Typemock
Blog: http://iserializable.com/

The Art of Unit Testing (book he’s working on)

Assumption for this session: You have some experience with writing tests

Mocking 101

  • Replace dependencies with fakes so you can test something
  • If the Storage class calls a non-virtual void method on EmailSender, you can’t test that interaction.
  • Pass an interface instead of a class. “Dependency injection”. Then we can write a FakeEmailSender and pass that in, and it can do whatever we want.
    • Do nothing
    • Set a flag saying “I got asked to send the e-mail”
  • Can create the fake manually or with a mock-object framework
  • Fake vs. mock
    • Fake: fake to make the system more testable. They won’t fail your test. (In another session, I heard this called a “stub”, with “fake” as a top-level term for both stubs and mocks.)
    • Mock: something we will assert against. These can be used to detect conditions where your test should fail.
  • More complex scenario: force EmailSender to throw an exception, so you can test how StorageManager interacts with ILogWriter.
    • FakeEmailSender, FakeLogWriter — where the LogWriter is a mock (you’ll assert that it actually got asked to log the error), and the e-mail sender is a fake (you don’t assert against it, it just replaces the real one)
    • Usually, in a test, you’ll only have one mock, and maybe many fakes

Side note: Test names should be extremely readable. Imagine that the person reading your code is a serial killer who knows where you live. Don’t make him mad.

Mock-object libraries

TypeMock:

[Test]
[VerifyMocks]
public void Typemock_Store_StringContainsStar_WritesToLog()
{
    ILogger log = RecorderManager.CreateMockedObject(typeof(ILogger));
    // Tell the mock object what you expect to happen in the future
    using (var r = new RecordExpectations())
    {
        log.Write("*");
        // Tell it to check parameter values, rather than just expecting
        // the method to be called
        r.CheckArguments();
    }
    // Now run the code under test
    var sm = new StorageManager(log);
    sm.Store("*");
}
  • The assert isn’t in the code — it’s implicit in the [VerifyMocks] attribute. Can also do it explicity through a method call.

RhinoMocks:

[Test]
public void Typemock_Store_StringContainsStar_WritesToLog()
{
    MockRepository mocks = new MockRepository();
    ILogger log = mocks.CreateMock();
    using (mocks.Record())
    {
        log.Write("*");
        // RhinoMocks checks arguments by default.
    }
    // Now run the code under test
    var sm = new StorageManager(log);
    sm.Store("*");
}
  • Looking back at that, I wonder if I missed something, because I don’t see any sort of VerifyMocks() call in that code snippet. I probably missed copying it down from the slide.

What happens when you can’t extract an interface?

  • Derive from the class under test

What happens when you can’t change the design?

  • Can cry to our managers
  • Typemock lets you mock things that are static or private, without modifying the design.
    • So suppose the Storage class instantiates the Logger itself, and you can’t change it. How do you break the dependency so you can test the code?
[Test]
[VerifyMocks]
public void Typemock2_Store_StringContainsStar_WritesToLog()
{
    using (var r = new RecordExpectations())
    {
        new Logger().Write("*");
        r.CheckArguments();
    }
    var sm = new StorageManager2();
    sm.Store("*");
}
  • Yep, it can record, and later fake out, a new call.
  • Uses the .NET Profiling API to do this. Deep black magic.

Mock object frameworks save time and coding, but sometimes fake objects are the simpler solution

Testing ASP.NET WebForms

  • Integration testing: use tools that invoke the browser and check stuff out
  • To test it in-process, and fake out the HttpContext and all that: Ivonna (which uses Typemock underneath)
    • Lets you set values on controls, and then process postbacks in-process

Extending the test framework with extension methods

  • Create a domain-specific language on your objects
"abc".ShouldMatch("\\w");
List<int> ints = new List<int> { 1, 2, 3, 4, 5, 6 };
ints.ShouldContain(4);
3.ShouldBeIn(ints);
5.ShouldBeIn(ints);
  • Make a static class with static methods
  • First parameter has this
  • Call asserts from the extension method
  • Import the namespace in order to be able to use the extension methods
public void ShouldBeIn(this object o, IEnumerable items)

Testing (and Mocking) LINQ Queries

  • LINQ is not a test-enabling framework by default
  • Use Typemock, then duplicate the LINQ query in your test (to record the calls)
  • The problem is, you’re over-specifying your tests, because you have to know what query is being run underneath — you’re specifying the internal behavior
  • So, avoid doing this unless you have to
  • May be better to abstract away the queries
  • What about projections? E.g. selecting an anonymous type?
    • Typemock can record anonymous types. He already had a demo for this.
    • Can be really, really weird.
using (RecordExpectations rec = new RecordExpectations())
{
    var p1 = new { Name = "A", Price = 3 };
    rec.CheckArguments();
    string a = p1.Name;
    rec.Return("John");
}
var target = new { Name = "A", Price = 3 };
Assert.AreEqual("John", target.Name);
  • The above actually passes. Yes. Look closer. They’re mocking the return value of the anonymous type, so even though you construct it with “A”, it returns “John”. Like I said above, deep black magic.

Testing WCF

  • Again, maybe the best thing is not to unit-test it. Consider an integration test instead.
  • Integration test: create a ServiceHost, ChannelFactory
    • Still might want to use Typemock for things like the security context
    • Be pragmatic about things. If it’s one line of code to use Typemock, vs. introducing lots of interfaces, maybe it’s worthwhile (as long as it’s maintainable).
  • Worth making a WCFTestBase<T, TContract> to pull out common code.
    • If you do integration testing, get used to base test-fixture classes to remove duplication.

Database-related testing

  • To mock or not to mock the database?
    • Mock:
      • Tests will be fast, no configuration, don’t need a real database.
      • But the database itself also has logic. Keys, indexes, integrity rules, security, triggers, etc.
  • Team System has a tool for testing against databases, but it’s not really “there” yet.
  • Can do an integration test against the database.
  • If you change external data, roll back between tests. Good solution: lightweight transactions with TransactionScope. Create in SetUp (with TransactionScopeOption.RequiresNew), and call Dispose in TearDown.
    • Will work even if the test creates its own transaction (as long as it doesn’t also do RequiresNew).

Put integration tests in a separate project, so it’s easy to run the unit tests (which are fast) all the time, without being slowed down by the integration tests (which are slow, require configuration, etc.)

Testing events

  • Hook the event as usual
  • Hook it with an anonymous delegate
  • Do your asserts inside that anonymous delegate
  • Make an eventFired variable and set it true from inside the delegate
  • Assert eventFired in the main test body
  • Yay closures!

Musical postlude

Leave a Reply

Your email address will not be published. Required fields are marked *