3

There seems to be a ton of advice for this sorta thing in the context of a GUI application. I think my particular scenario is different enough to warrent me asking. To sum up my question how do you test events?

Now for the long winded explanation. I've worked with Point of Service hardware for a little while now. This means that I had to write a few OPOS Service Objects. After a few years of doing that I managed to write my first COM visible C# service object and put it into production. I tried my best to unit test the entire project but found it rather difficult, but was able to come up with good unit tests for most all of the Service Object's interface implementation. The hardest part was the Event's part. Granted this scenario was the biggest and most common one that I faced, but I've come across similar scenarios in other applications of mine where testing for an event just seemed awkward. So to set the scene the Common Control object (CO) has at most 5 events that a person can subscribe too. When the CO calls the method Open OPOS finds the Service Object (SO) and creates an instance of it, then calls it's OpenService method. The third parameter of the SO is a reference to the CO. Now I don't have to define the entire CO, I only have to define the call back method for those 5 events. An example of the msr's definition is this

[ComImport, InterfaceType(ComInterfaceType.InterfaceIsDual), Guid("CCB91121-B81E-11D2-AB74-0040054C3719")]
internal interface MsrControlObject
{
    [DispId(1)]
    void SOData(int status);

    [DispId(2)]
    void SODirectIO(int eventNumber, ref int pData, ref string pString);

    [DispId(3)]
    void SOError(int resultCode, int resultCodeExtended, int errorLocus, ref int pErrorResponse);

    [DispId(4)]
    void SOOutputCompleteDummy(int outputId);

    [DispId(5)]
    void SOStatusUpdate(int data);
} 

and my OpenService method would have this line of code

public class FakeMsrServiceObject : IUposBase
{
    MsrControlObject _controlObject;
    public int OpenService(string deviceClass, string deviceName, object dispatchObject)
    {
        _controlObject = (MsrControlObject)dispatchObject;
    }
    //example of how to fire an event 
    private void FireDataEvent(int status)
    {
        _controlObject.SODataEvent(status);
    }
}

So I thought to myself for better testing, lets make a ControlObjectDispatcher. It will allow me to enqueue events, then fire them to the CO when conditions are correct. This is where I'm at. Now I know sorta how to test drive the implementation of it. But it just feels wrong. Lets take the DataEvent as an example. 2 conditions have to be met for a DataEvent to be fired. First the boolean property DataEventEnabled must be true, and the other boolean property FreezeEvents must be false. Also all events are strictly FIFO. So.. a Queue is perfect. And since I've written this before I know what the implementation will be. But writing a test for it that instills confidence to a new person to the project is difficult. Consider this pseudo code

[Test]
public void WhenMultipleEventsAreQueuedTheyAreFiredSequentiallyWhenConditionsAreCorrect()
{
    _dispatcher.EnqueueDataEvent(new DataEvent(42));
    _dispatcher.EnqueueStatusUpdateEvent(new StatusUpdateEvent(1));

    Sleep(1000);
    _spy.AssertNoEventsHaveFired();
    _spy.AssertEventsCount(2);

    _serviceObject.SetNumericProperty(PIDX_DataEventEnabled, 1);

    _spy.AssertDataEventFired();
    _spy.AssertStatusUpdateEventFired();

    _serviceObject.GetnumericProperty(PIDX_DataEventEnabled).Should().BeEqualTo(0, "because firing a DataEvent sets DataEventEnabled to false");
}

Everyone reading this hear could wonder (without knowing the implementation) How do i know that say after 1 minute that this event fires? How do I know that that crazy Robert Snyder person didn't use a for loop and forget to exit the FireDataEvent routine after the iterations were all up? You really don't. Granted you could test for a minute.. but that defeats the purpose of a unit test.

So to sum up again... How does a person write a test for events? Events can fire whenever.. and they can sometimes take longer to process and fire then expected. I've seen in my integration tests for the first implementation of this where if I didn't sleep for say 50ms before asserting that an event was called then the test would fail with something like. expected data event to have fired, but was never fired Are their any test frameworks built for events? Are their any common coding practices that cover this?

4

1 回答 1

2

It’s a bit unclear if you’re looking to do unit testing or integration testing for your events, since you talk about both. However, given the tags on the question, I’m going to assume your primary interest is from a unit testing perspective. From a unit testing perspective it doesn’t make much difference if you are testing an event, or a normal method. The goal of the unit is to test individual chunks of functionality in isolation, so whilst having a sleep in an integration test might make sense (although I’d still try to avoid it and use some other kind of synchronisation where possible), in a unit test I’d take it as a flag that the functionality being tested hasn’t been isolated sufficiently.

So, for me, there’s two slices of event driven testing. Firstly you want to test that any events your class fires are fired when the appropriate conditions are met. Secondly you want to test that any handlers for the events perform the expected actions.

Testing the handlers behave as expected should be similar to any other test that you would right. You setup the handler to the expected state, set your expectations, call into it as if you were the event generator and then verify any relevant behaviour.

Testing that events are fired is essentially the same, setup the state that you would expect to fire an event, and then verify that an appropriately populated event is fired and any other state change takes place. So, looking at your pseudo code, I would say that you have at least two tests:

// Setup (used for both tests)
// Test may be missing setup for dispatcher state == data event disabled.
    _dispatcher.EnqueueDataEvent(new DataEvent(42));
    _dispatcher.EnqueueStatusUpdateEvent(new StatusUpdateEvent(1));

//    Sleep(1000);    // This shouldn’t be in a unit test.

// Test 1 – VerifyThatDataAndStatusEventsDoNotFireWhenDataEventDisabled
    _spy.AssertNoEventsHaveFired();
    _spy.AssertEventsCount(2);

// Test 2 – VerifyThatEnablingDataEventFiresPendingDataEvents
    _serviceObject.SetNumericProperty(PIDX_DataEventEnabled, 1);

    _spy.AssertDataEventFired();
    _spy.AssertStatusUpdateEventFired();

// Test 3? – This could be part of Test 2, or it could be a different test to VerifyThatDataEventsAreDisabledOnceADataEventHasBeenTriggered.
    _serviceObject.GetnumericProperty(PIDX_DataEventEnabled).Should().BeEqualTo(0, "because firing a DataEvent sets DataEventEnabled to false");

Looking at the test code, there isn’t anything to suggest that you’ve implemented the actual serviceObject using any kind of for loop, or that a minute of testing would have any impact on the behaviour of the serviceObject. If you weren’t thinking about it from the perspective of event programming would you really be considering if calling into SetNumericProperty would result in you using ‘a for loop and forgeting to exit the FireDataEvent routine after the iterations were all up?’ It seems like if that’s the case then either SetNumericProperty wouldn’t return or you have a non-linear implementation and you’re possibly testing the wrong thing in the wrong place. Without seeing your event generation code it’s hard to advise on that though…</p>

Events can fire whenever… and they can sometimes take longer to process and fire then expected

Whilst this may be true when your application is running, it shouldn’t be true when you’re doing unit testing. Your unit tests should trigger events for defined conditions and test that those events have been triggered and been generated correctly. To achieve this you need to aim for unit isolation and you may have to accept that some thin elements need to be integration tested, rather than unit tested in order to achieve this isolation. So if you were dealing with events triggered from outside your app you may end up with something like this:

public interface IInternalEventProcessor {
    void SOData(int status);
    void SODirectIO(int eventNumber, int pData, string pString);
};

public class ExternalEventProcessor {
    IInternalEventProcessor _internalProcessor;

    public ExternalEventProcessor(IInternalEventProcessor internalProcessor, /*Ideally pass in interface to external system to allow unit testing*/) {
        _internalProcessor = internalProcessor;
        // Register event subscriptions with external system
    }

    public void SOData(int status) {
        _internalProcessor.SOData(status);
    }

    void SODirectIO(int eventNumber, ref int pData, ref string pString) {
        _internalProcessor.SODirectIO(eventNumber, pData, pString);
    }
}

The purpose of the ExternalEventProcessor is to decouple the dependency on the external system so that unit testing of the event handling in the InternalEventProcessor is easier. Ideally, you would still be able to unit test the ExternalEventProcessor registers for events correctly and passes through by supplying mocks of the external system and the internal implementation, however if you can’t then because the class is cut down the bare minimum having integration testing only for this class might be a realistic option.

于 2015-04-13T08:38:44.817 回答