Linq provider : an attempt… part 6

Jammin’ Jammin’

Just a month ago, I’ve had the opportunity to get feedback from @cyriux and Jérôme during the monthly @ArollaFr’s Code Jam, and we made important changes in the code.

The concept of a Code Jam is simple : we meet once a month to practice our coding skills, emphasizing practices such as TDD and pair-programming.
For more details (in French) : http://www.arolla.fr/evenements-2/jams-de-code/

June’s theme was “Testing things that are difficult to test”. The session started with a “Lightning talk” by @cyriux, describing several situations where testing can be quite complicated, as for instance :

  • When time is involved
  • When parallelization and multi-threading is involved
  • When architecture is involved (network bandwidth, fault-tolerant systems)
  • Testing security (SQL injection, various kinds of exploits)
  • When the host is difficult to emulate (as example: how to test an eclipse plug-in ?)
  • Testing with non-trivial outputs/inputs: images, sounds, documents, reports, big messages…
  • Testing with large configuration problems: realistic search index, social algorithms, time series for financial analytics…

After this introduction, we were looking for a practical example of such a situation, and I suggested we could take a look at my implementation of this Linq provider. I haven’t gone very far yet, but my goal with this Linq to web service provider is to make the service return filtered data based on the Linq query. This means that I want the filtering to happen inside the web service, and not on the client side. And this is of course a behaviour that I want to test:

After some discussion, @cyriux pointed to me that there were several false assumptions in the way I was testing things. I assumed I would be testing the provider by using it against a service that I had built myself… but I had no need for an actual web service implementation ! The service and the provider are two separate features, and mustn’t be unit-tested at the same time.

I have not completely given up the idea of implementing the service, but it is now a separate objective that has nothing to do with the provider implementation. And the fact that the service is a web service is just an implementation detail. My vision of the way to organize the project is now the following :

The solution is now only composed of 3 assemblies :

  • PeopleFinder, which defines the service interface (i.e. the contract) and POCOs used as simple Data Transfer Objects,
  • LinqToService, which contains the actual implementation of the Linq provider,
  • UnitTests, which contains the tests and a “lambda-based” implementation of the PeopleFinder interface.

This organisation allows us to test only the behaviour of the provider, providing our own implementation of the service. To do so, we introduced a PeopleFinderLocator class. This might change in the future, but is quite out of scope here…

Given a query expression, the Linq provider’s goal is just to give the service the good parameters, and perform additional work that the service cannot handle. Here, the service knows how to filter the people based on their age. Let’s consider the following query :

var query = new QueryableServiceData<Person>()
    .Where(p => p.Age >= 36);

When executing it, the provider must call the service with the appropriate parameter, and mustn’t do any additional work except handling the result back to the caller. The situation should be the following :

And here is a test corresponding to this situation :

[TestMethod]
public void GivenAProviderWhenIFilterOnAgeGT36ThenItFilters()
{
    LambdaBasedPeopleFinder peopleFinder =
        new LambdaBasedPeopleFinder(p => p.Age >= 36);
    PeopleFinderLocator.Instance = peopleFinder;

    QueryableServiceData<Person> people =
        new QueryableServiceData<Person>();

    var query = people
        .Where(p => p.Age >= 36);

    var results = query.ToList();

    Assert.IsTrue(results.All(p => p.Age >= 36));
    Assert.AreEqual(
        new IntCriterion()
            {
                FilterType = NumericFilterType.IsGreaterThan,
                Value = 36
            },
            peopleFinder.PassedCriteria.Age);
}

In the previous test, we use a LambdaBasedPeopleFinder implementation of the service, that actually filters some sample data based on a predicate. In some way we rely on this implementation because we test that the results are correctly filtered. But we can even test that the provider doesn’t do the service’s work !

If we adapt the previous test by replacing the lambda by p => true, the results returned by the service will not be filtered, and we can test that the provider correctly delegates the work to the service, and doesn’t filter the data.

[TestMethod]
public void WhenIFilterOnAgeGT36ThenTheProviderDoesntFilter()
{
    LambdaBasedPeopleFinder peopleFinder =
        new LambdaBasedPeopleFinder(p => true);
    PeopleFinderLocator.Instance = peopleFinder;

    QueryableServiceData<Person> people =
        new QueryableServiceData<Person>();

    var query = people
        .Where(p => p.Age >= 36);

    var results = query.ToList();

    Assert.IsFalse(results.All(p => p.Age >= 36));
    Assert.AreEqual(
        new IntCriterion()
        {
            FilterType = NumericFilterType.IsGreaterThan,
            Value = 36
        },
        peopleFinder.PassedCriteria.Age);
}

Finally, testing the provider was not a complicated question. My problem was not “how to test”, but rather “what to test”.

To finish, here are the next things that I want to do about this project :

  • Use SpecFlow to transform all my tests and use the Gherkin syntax,
  • Handle more complex queries, list all the type of nodes that can be found and handle them, ensuring the correctness of the returned data. If needed, any unhandled node type would result in using a blank SearchCriteria object and performing all the work using Linq to Objects,
  • Implement the service, even if it’s not necessary, just for the sake of it,
  • Get stuck manipulating expression tress,
  • Push the whole thing to a Github repository,
  • Get help from anyone motivated.
This entry was posted in LINQ and tagged , , , . Bookmark the permalink.

Comments are closed.