Runar Ovesen Hjerpbakk

Software Philosopher

Highlights and Insights: NDC 2013 Conference, Day 3

Aka. the day of automated testing.

Sharing C# across Windows, Android and iOS using MvvmCross

MvvmCross is an excellent framework for data binding ++ on Xamarin's supported platforms. Stuart Lodge gave a talk explaining the motivation, history, philosophy and current state of the framework.

MvvmCross makes it easy to implement the MVVM-pattern on iOS, Android, Windows Phone, Mac, Win RT and others, and facilitates code sharing of the ViewModel logic across all the supported platforms. With the help of PCL (Portable Class Libraries), you can achieve code sharing in the upper 80%s of your code base. Even more if you use little custom UI. The view itself cannot be shared, and that is as it should be.

MvvmCross solution structure

Properly decoupled view models are easy to test and with the code shared across all your platforms, the tests will have high value.

MvvmCross enables cross platform development with native UI and performance, together with easily testable code. I've used MvvmCross myself and highly recommend it to everybody interested in multi-platform development. For an introduction, check out the MvvmCross N+1 video series.

Succeeding with TDD: Pragmatic Techniques for effective mocking

Venkat Subramaniam gave an introduction to mocking in C# and helpful pointers when working with test-driven development (TDD).

  • If your code is not testable, your design sucks. Separation of concerns, dependency inversion and the single responsibility principle helps with both design and testability.
  • TDD takes you from unknown to known, one small step at the time.
  • Start with your needs and map the world to you.
  • Use mocks sparingly and keep them simple! Never make a mock more complex. If you hear "Do we need to write unit tests for the mock?" you have failed in this regard. An example:
[TestClass]
public class MockExample
{
    /// <summary>
    /// An interface for an imaginary stock service.
    /// </summary>
    public interface IStockService
    {
        double QuoteForTicker(string ticker);
    }

    /// <summary>
    /// Class representing an application model.
    /// Utilizes the IStockService in its implementation.
    /// </summary>
    public class SampleApplicationModel
    {
        private readonly IStockService stockService;

        public SampleApplicationModel(IStockService stockService)
        {
            this.stockService = stockService;
        }

        public string CurrentTicker { get; set; }
        public string LatestQuote { get; set; }

        public void GetQuote()
        {
            var quote = stockService.QuoteForTicker(CurrentTicker);
            LatestQuote = quote.ToString();
        }
    }
   
    [TestMethod]
    public void GetQuoteMustSetLatestQuoteForTicker()
    {
        // Simple mock, using an appropriate tool
        var stockServiceFake = new Mock<IStockService>();
        stockServiceFake.Setup(s => s.QuoteForTicker("AAPL")).Returns(700D);
        var model = new SampleApplicationModel(stockServiceFake.Object) { CurrentTicker = "AAPL" };

        // Mock with internal logic
        //var model = new SampleApplicationModel(new StockServiceFake()) { CurrentTicker = "AAPL" };

        model.GetQuote();

        Assert.AreEqual("700", model.LatestQuote);
    }

    [TestMethod]
    [ExpectedException(typeof(InvalidOperationException))]
    public void GetQuoteLFailsMustSetErrorMessageAsQuoteValue()
    {
        // Simple mock, using an appropriate tool
        var stockServiceFake = new Mock<IStockService>();
        stockServiceFake.Setup(s => s.QuoteForTicker("AAPL")).Throws(new InvalidOperationException());
        var model = new SampleApplicationModel(stockServiceFake.Object) { CurrentTicker = "AAPL" };

        // Mock with internal logic
        //var model = new SampleApplicationModel(new StockServiceFake(true)) { CurrentTicker = "AAPL" };

        model.GetQuote();
    }

    /// <summary>
    /// Complicated mock. Hard to understand, must be maintained
    /// together with the interface it's implementing.
    /// </summary>
    private class StockServiceFake : IStockService
    {
        private readonly bool throwException;

        public StockServiceFake(bool throwException = false)
        {
            this.throwException = throwException;
        }

        public double QuoteForTicker(string ticker)
        {
            if (throwException) 
            {
                throw new InvalidOperationException();
            }

            return 700D;
        }
    }
}

I would have stressed more that you don't start with TDD, you start with unit tests. Understanding how to write relevant and well designed tests are a precursor for a successful application of TDD.

C# 5

Jon Skeet, best known from Stack Overflow and as an author of the books C# in Depth, talked about async await in C# 5.

His focus was somewhat different from Venkat's previous talk. Rather than starting with C# threading basics, Jon made the case for async and await using metaphors and code examples. As we've previously learned, asynchrony does not equal parallelism.

C# in its current iteration has many different APIs for dealing with asynchrony. Your code will be easier to develop, understand and extend if you stick with one of these paradigms. Switching points between synchronous and asynchronous code can be painful.

Jon stressed the need for giving the user early feedback. For example, show items in a list as they come in. Don't wait for all the items before giving the user the option to work with the data. Also, validate input as early as possible so that exceptions caused by bad input do not happing during the asynchronous operations.

Testing was the last topic:

  • Keep tests for plain business logic and asynchronous operations separate.
  • Take control of time and consider using an abstraction so that the ordering of execution can be controlled from the tests.
  • Test for different permutations of completion order for the different asynchronous tasks. The tests should still pass even if the code is run synchronously.

C# 5 lets you focus on the difficult bits... And that's good!