Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts

2013-10-31

The kinds of tests we write

When starting work on version v3 of our software PriceOn, we decided to take unit testing seriously. This was basically our CTO's idea, and I am very happy that we managed to adopt it as a default coding practice in virtually no time. The reason it went so smoothly for us is just that all three of us backend developers were highly motivated to master it, and we had some previous experiences trying it and failing when not taking things seriously.

What we deliver is a service API reachable via HTTP. This API is consumed by our frontend clients: website and mobile apps for iOS and Android. We code in Microsoft's C# - some would say non typical choice for a startup, but we decided to stick with the thing we know best, as there are far more important challenges than picking 'cool' language. So let me just explain some points related to how/why we perform testing.

Simple integration tests that ping service via HTTP

These primarily test that the  wiring is correct and service does not crash on typical request. Just a simple service ping, which checks that server returns 200 OK, no business logic testing here.


[Theory, NinjectData]
public void FollowCardApidPing(ICanSave<Card> repo, FollowCardReq req, Card card)
{
    req.UserId = MockedUserId;
    repo.Save(card.CardId, card);
    AssertPostTakesAndReturns<FollowCardReq, FollowCardRes>(UrlRegistry.FollowCard.Mk(new { userId = MockedUserId, cardId = card.CardId }), req);
} 
Some details:

  •  we use custom NinjectData attribute which resolves test parameters by first looking at Ninject kernel specifically configured for tests, and if that fails, creating something with AutoFixture.
  • AssertPostTakesAndReturns is a method of base class integration tests derive from. This class Launches in-process instance of ServiceStack which hosts our services, so that we can interact with them via http, and that is what this Assert method does.
  • Currently, this is integration test just in a sense that it launches http server and tests everything via http interface. All problematic dependencies within service are replaced with in-memory implementations. We may consider changing them to real ones sometime in the future when reliable performance of 3rd party software starts to weigh in.


High-level unit tests that fake only external dependencies

These are the majority of tests we write. We test service logic by trying to mock out as little dependencies as possible. Similar to Vertical Slice Testing by @serialseb. The unit tests get System Under Test (SUT) in fully wired up state, with only external dependencies such as Database, Timer, Filesystem faked. As in production, unit tests resolve dependencies from IOC container which is just slightly tweaked from production configuration to inject lightweight implementations for external services.
The most prevalent external dependency is a database. How do we deal with it? We have IRepository interface, and then we have InMemoryRepository : IRepository. For each test, we seed this InMemoryRepository with relevant data, and inject into SUT.


[Theory, NinjectData(With.NullUser)]
public void QueryingShops_SingleSellerExists_ReturnsThatSeller(ProductAdminService sut, ShopsReq req)
{
    const string seller = "Mercadona";
    var repo = SetupData(new[] {Product.CreateGoodProduct().WithSeller(seller)}, p => p.Id);
    sut.ProductRepo = repo;   
   
    var res = sut.Get(req);
   
    res.Items.Select(u=> u.Seller).Should().BeEquivalentTo(new[]{seller});
}


  • As with integration tests, we resolve our dependencies from Ninject and AutoFixture, with just external dependencies faked. In this case, sut is taken from Ninject, and req is some randomly generated request made by AutoFixture. We may have to tune req according to test case, but in this case it is empty object, so nothing to be done there.
  • Our InMemoryRepository with data for the test is injected into the SUT. This is more stable than faking response from repository directly.
  • Repository is injected into sut via property setter. As we are resolving sut from IOC container, we already have default repository implementation, but we have to swap it with the one containing our predefined data.

"Real" unit tests in specific places with nontrivial business logic

We write these just in the places we feel logic is not trivial and may tend to change. This usually happens when we have to evolve API method to support more interesting scenarios, and it practically boils down to extracting domain specific code into isolated easily testable units. For easiest testability, it is very nice to isolate complex logic from all dependencies.

[Fact]
public void RegularPrice_IsPriceThatRepeatsMostDays()
{
    var date = new DateTime(2000, 1, 1);

    var sut = new Offer();
    sut.Prices.Add(date, new PriceMark(){Price = 1, ValidFrom = date, ValidTo = date.AddDays(10)});
    sut.Prices.Add(date.AddDays(11), new PriceMark() { Price = 2, ValidFrom = date.AddDays(11), ValidTo = date.AddDays(13) });
    sut.Prices.Add(date.AddDays(14), new PriceMark() { Price = 2, ValidFrom = date.AddDays(14), ValidTo = date.AddDays(16) });

sut.RegularPrice.ShouldBeEquivalentTo(1);
}


  • This is fairly obvious code, our SUT does not require any dependencies, just plain business logic. Breeze to test.

2010-05-24

C# Switch statement & enums

I wish i could write something like this:

void foo(MyEnum e)

{

  switch(e) complete

  {
    case MyOption1:

      bar(); break;

    case MyOption2:

      baz(); break;

  }

}

and expect compiler error is MyEnum also contains MyOption3. Of course, I can add

default: throw new Exception();

but this is not always appropriate when the code is not covered by automated tests. In my case, that’s how it is exactly. Some code similar to above is executed only on rare occasions, so there is little probability that the problem will be spotted by manual testing. And when it is in production, it would rather fail silently than throw unhandled exception.

Of course is is considered a bad practice to litter code with such switch statements everywhere, and bar() and baz() would better be virtual functions on polymorphic objects.

2008-06-14

var in C#

So this looks like yet another flame-provoking topic. Use var or not? Many say this is matter of taste, and that this is analogous to weak typing vs strong typing - both exist successfully.

var i = short.MaxValue;
i++;

Oh well, nobody uses other integer types beside int today anyway. Similar to var, eh? "I don't care what are limits for this type, all that i am interested is that it contains integers". In case of int, i agree, this is most often the best choice, it eliminates the need of typecasting on arithmetic oparations with different types, and few (k)bytes spared are probably not worth headaches choosing between "short" and "int". The similarity is that both reduce the need of thinking.
Matters with var are a bit different. In fact, it does not change semantics of the program, it just makes it a little bit less verbose, ie eliminate some "syntactic noise". So for me this is all about how much redundancy do we need in our code? If there is too much, the code is bloated and it is difficult to find what you need. If, on the other hand, there is too little, it can be hard to understand intent of the code. Talking about verbosity, i always remember code contracts. For me, code contracts look like very nice idea, and although i have never used them, i feel they will appeal to me much more than unit tests, when i have a chance to use them.
Personally, i never use var when not necessary(with anonymous types). Who says it saves typing? Gee, are you programming in notepad or what? In VS IDE, you write first three letters of type name and hit space, 80% you have the right type... Only thing i can imagine you save here is code size, and that's why i will probably use var in my homemade 100-loc programs. For some reason i just feel offended when i see peer programmers writing something like

var client = order.Client;

But again, i feel this is just something you have to get used to. I feel i may change with time, and will be eagerly using this feature everywhere i can, until i start abusing it, which will cause other problems, until i settle on a golden middle, or well, be forced there by some coding standard rules. On a side note, similar feature is proposed for C++0x (auto), a language i have deep trust in.

2008-06-08

VB, C#

Recently at workplace i heard our projects manager muttering something like "THEY ARE USING VB?? gosh, they must be total newbies", and eveyone around nodding in approval; well not exactly the same wording, but thats what i heard between the lines. So whats wrong with VB?
I myself grew in environment where VB was like "american thing", "not for real programmers", etc. And so probably that is the reason i never really tried it - well, except macros in VS. But still there is one thing i believe - having seen many opinions, and also from my own experience - c# is quite similar to VB. Haven't you heard developers migrating from VB to C#, and not that many complaints - this must say something. Needless to say, here write programs in C#, and well, i guess almost everyone here thinks it is the coolest language in the world. Of course, we can extend this further and say all OO languages are so similar -and of couse they have many similarities. IMO, VB and C# aren't so drastically different to make one "cool", and the other "ugly". If you dont't like VB keywords, this is exactly if you wouldn't like those C-style {}. I think this is not enough a difference, and i haven't heard any fundamental ones - all just a matter of taste.
And to think of it, is C++/CLI that different from C#? For me it looks like old good standard C++ plus subset of C# with different keywords. They all depend on same CLR, which i guess was designed mostly having one language in mind - C#. I think this has some implications on language, which is to target CLR, despite of what they say how CLR is so flexible and can support any language. If not for anything else, then for interop between .NET languages.