views:

40

answers:

3

I have an application service (ie. large method) responsible for coordinating the interaction between several business objects. Essentially it takes a DTO from one system which contains customer information and an invoice, and translates it and imports it into a different system based on various business rules.

public void ProcessQueuedData()
    {
       var queuedItems = _importServiceDAL.LoadQueuedData();

       foreach (var queuedItem in queuedItems)
       {
           var itemType = GetQueuedImportItemType(queuedItem);

           if (itemType == QueuedImportItemType.Invoice)
           {
               var account = _accountDAL.GetAccountByAssocNo(queuedItem.Assoc);
               int agentAccountID;

               if (!account.IsValid)
               {
                   agentAccountId = _accountDAL.AddAccount(queuedItem.Assoc);
               }
               else
               {
                   agentAccountId = account.AgentAccountID;
               }

               /// Do additional processing TBD
           }
       }
    }

For the unit tests, is it correct to assume the entire process within the service should be tested on a granular step by step basis, similar to the following?

ImportService_ProcessQueuedData_CallsDataAccessLayer_ToLoadQueue

ImportService_ProcessQueuedData_WithQueuedItemToProccess_ChecksIfAccountExists

ImportService_ProcessQueuedData_WithInvoice_CallsDALToCreateAccountIfOneDoesNotExist

Here's a typical test:

    [TestMethod()]
    public void ImportService_ProcessQueuedData_WithInvoice_CallsDALToCheckIfAgentAccountExists()
    {
        var accountDAL = MockRepository.GenerateStub<IAccountDAL>();
        var importServiceDAL = MockRepository.GenerateStub<IImportServiceDAL>();

        importServiceDAL.Stub(x => x.LoadQueuedData())
            .Return(GetQueuedImportItemsWithInvoice());

        accountDAL.Stub(x => x.GetAccountByAssocNo("FFFFF"))
            .IgnoreArguments()
            .Return(new Account() { AgentAccountId = 0 });

        var importSvc = new ImportService(accountDAL, importServiceDAL);

        importSvc.ProcessQueuedData();

        accountDAL.AssertWasCalled(a => a.GetAccountByAssocNo("FFFFF"), o => o.IgnoreArguments());
        accountDAL.VerifyAllExpectations();
    }

My problem is that I end up doing so much setup in each of these tests it is becoming brittle. Is this the correct approach, and if so what are some pointers to avoid duplicating all of this setup within each granular test?

A: 

I don't know a lot about your particular application, so I can't really make any specific recommendations. That said, this kind of process-oriented testing sounds like it could be a good candidate for employing Model-based testing techniques. I'm familiar with the ModelJUnit tool (fair disclosure: I was employed as a developer of this tool some time ago) for Java, however there appears to be an equivalent tool called NModel for C#, and an accompanying book, Model-based Software Testing and Analysis in C#. The reason I say this is that perhaps you can randomise walks through the process, placing your setup code all in one place and allowing the abstract test generation to do most of the rest of the hard work for you.

Gian
Thanks Gian... Do you suggest NModel b/c the perceived number of possible input conditions is vast? If the combinations of input types was more limited would you still recommend this approach? The overall process I am trying to test only really has 6 conditional branches and a known number of conditions that come into play in this if-then logic. Thanks again for your insight.
njcoder
It's more that it appears to be a sequential process, with the possibility for error conditions to be caught at each step, and perhaps the possibility for multiple different paths through these actions. This is where model-based testing really shines - you simply encode all of the states in your model and let the test system figure out what the possible paths are.
Gian
A: 

Personally I would try to test all pieces of code, but not necessarily each section as its own test. One test checks that an Invoice with a valid account goes through. A second test checks that an Invoice with an invalid account creates a new account. Of course I would mock out the dals so no data is added to the database. This also allows mocking exceptions and cases where no operations should be taken (nothing in the queue, or no invoices perhaps.)

Pedro
A: 

I agree with Pedro. Your first example test (ImportService_ProcessQueuedData_CallsDataAccessLayer_ToLoadQueue) isn't necessary, because the other tests will implicitly test that LoadQueuedData was called -- if it wasn't, they would have no data to operate on. You want to have a test for every path, but you don't need a separate test for every line of code in the method. If there are six paths through the conditional branching, you want six tests.

If I wanted to get really fancy, I might also consider leveraging object polymorphism to reduce the number of if statements and simplify the tests. For example, you could have a different "handler" for every QueuedImportItemType, and put the logic for what to do with an item of that type there, instead of your big Process method. Splitting up the logic of iteration and of what to do with each type of item makes them easier to test separately.

Tegan Mulholland