Pregunta

I'm trying to implement Test-driven development for the first time. My project is a c# in dotnet 3.5. I'm have read the book Professional Test Driven Development in c# and now i want to test my project that contains a windows service.I've read that a best practise is that all code must be under test.The following is my windows service that implement method onStart, and onStop

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using log4net;

namespace MyUcmaService
{
 public partial class MyUcmaService : ServiceBase
 {
    private Worker _workerObject;
    private static MyUcmaService aMyUcmaService;
    private Thread _workerThread;
    private static ILog _log = LogManager.GetLogger(typeof(MyUcmaService));

    public MyUcmaService()
    {
        InitializeComponent();
        aMyUcmaService = this;
    }

    protected override void OnStart(string[] args)
    {
        // TODO: inserire qui il codice necessario per avviare il servizio.
        //Debugger.Launch();
        AppDomain.CurrentDomain.UnhandledException += AppDomainUnhandledException;
        try
        {
            _workerObject = new Worker();
            _workerThread = new Thread(_workerObject.DoWork);

            // Start the worker thread.
            _workerThread.Start();

        }
        catch (Exception ex)
        {
            HandleException(ex);
        }
    }

    protected override void OnStop()
    {
        // TODO: inserire qui il codice delle procedure di chiusura necessarie per arrestare il servizio.
        try
        {
            _workerObject.RequestStop();
            _workerThread.Join();
        }
        catch (Exception ex)
        {
            HandleException(ex);
        }
    }


    private static void AppDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        HandleException(e.ExceptionObject as Exception);
    }

    private static void HandleException(Exception ex)
    {
        if (ex == null)
            return;
        _log.Error(ex);

        if (aMyUcmaService != null)
        {
            aMyUcmaService.OnStop();
        }

      }
    }
   }

Can you tell me how can i implement a tdd here? Thanks for response.

¿Fue útil?

Solución 2

Since I just refactored 4 existing windows services at work I can't resist to throw in an additional answer!

What I did is strip the windows service class completely and make my own ServiceBase class with its 4 derivatives for the 4 different implementations. The most fundamental reason is that it's a real pain to test your windows service due to its inconvenient test cycle:

Apply change, Build, Deinstall windows service, Install updated windows service, Test, Struggle with debugging, and Repeat...

The main purpose of TDD-ing the windows service for me were:

  • Tackle all deadlock issues.
  • Verify that the delegated calls to other objects are called.
  • Heavily reduce the develop-test cycle to speed up development!

I recognize the same needs from your code example. Allow me to show my own simplified code to draw a picture of what you could do to successfully TDD your windows service(s).

I'll show the tests first since that's the interesting part. I'll add some snippets of the implemented classes as reference below the tests.

My [SetUp] and unit tests

Some setup stuff before the real stuff begins...

    private MockRepository _mocks;
    private IAdminLayer _adminLayer;
    private IAlertSchedule _alertingServices;
    private IAlertManager _alertingManager;
    private AutoResetEvent _isExecutedSuccesful;
    private AdministratorAlertingService _alertingService;

    [SetUp]
    public void Setup()
    {
        _isExecutedSuccesful = new AutoResetEvent(false);

        _mocks = new MockRepository();
        _adminLayer = _mocks.DynamicMock<IAdminLayer>();
        _alertingServices = _mocks.DynamicMock<IAlertSchedule>();
        _alertingManager = _mocks.DynamicMock<IAlertManager>();
        var settings = _mocks.DynamicMock<ISettingsService>();

        using (_mocks.Record())
        {
            Expect.Call(_adminLayer.LogSource).Return("myLogSource").Repeat.Any();
            Expect.Call(_adminLayer.Settings).Return(settings);
            Expect.Call(settings.IsInitialised()).Return(true);
            Expect.Call(settings.GetAlertSchedule()).Return(_alertingServices);
        }
        _alertingService = new AdministratorAlertingService(_adminLayer, null);
    }

Test the OnStart behavior:

    [Test]
    public void AlertingServiceTestOnStart()
    {
        new Thread(ExecuteOnStart).Start();
        Assert.IsTrue(_isExecutedSuccesful.WaitOne());
        Assert.IsTrue(_alertingService.ServiceTimer.Enabled);
    }

    private void ExecuteOnStart()
    {
        _alertingService.OnStart();
        _isExecutedSuccesful.Set();
    }

Test the OnPause behavior:

    [Test]
    public void AlertingServiceTestOnPause()
    {
        new Thread(ExecuteOnPause).Start();
        Assert.IsTrue(_isExecutedSuccesful.WaitOne());
        Assert.IsFalse(_alertingService.ServiceTimer.Enabled);
    }

    private void ExecuteOnPause()
    {
        _alertingService.OnPause();
        _isExecutedSuccesful.Set();
    }

My implementation of the Service functionality

A snippet of the interesting and most meaningful parts:

public abstract class AdministratorServiceBase
{
    protected readonly IAdminLayer AdminLayer;
    protected readonly ServiceBase Service;
    public Timer ServiceTimer = new Timer();      
    protected AutoResetEvent ResetEvent = new AutoResetEvent(true);

    protected AdministratorServiceBase(IAdminLayer adminLayer, ServiceBase service, string name, string logname, string logsource, string version)
    {
        // Removed irrelevant implementation
        ServiceTimer.Elapsed += ServiceTimerElapsed;
    }

    public virtual void OnStart()
    {
        try { // Removed irrelevant implementation }
        catch (Exception ex)
        {
            HandleException(" detected a failure (trying to start).", ex, true, true);
        }            
    }

    // Same story for the other service methods...
    public virtual void OnPause() {}
    public virtual void OnContinue() {}
    // ..
    // ..
}

How you use your service class in the real WindowsService class

(it's visual basic, but that will not make much of a difference)

Public Class Service1

    Private ReadOnly _alertingService As AdministratorAlertingService = New AdministratorAlertingService(AdminLayer.GetSingleInstance(), Me)

    Protected Overrides Sub OnStart(ByVal args() As String)
        _alertingService.OnStart()
    End Sub

    Protected Overrides Sub OnPause()
        _alertingService.OnPause()
    End Sub

    // etc etc 

End Class

I refactored the 4 windows services in two days, and the benefits are unmeasurable! TDD really helped me delivering quality.


A reply to your comment

My Windows Service class is the Service1 visual basic class. It creates an instance of a AdministratorAlertingService.

Private ReadOnly _alertingService As AdministratorAlertingService = 
    New AdministratorAlertingService(/* parameters /*)

The AdministratorAlertingService extends the AdministratorServiceBaseClass which contains the shared behavior that my other Windows Services have as well (a timer, start, pause, stop).

If you only have one Windows Service then you don't need a base class of course.

In my unit tests I create a new SuT (subject under test) which in this case is a new AdministratorAlertingService and I verify that it has the correct start, pause, stop behavior using a AutoResetEvent. The 'actual work' that is done by the Windows Service is mocked and tested in unit tests specifically for these classes.

That way you can (and should) TDD your windows services. It will decrease your development-test cycles of your windows service dramatically.

You can choose to add integration tests to your test suite to test the complete functionality: your delegated hand written start, pause, stop behavior where you do not mock the functionality of the classes that do the actual work. I earned the most TDDing my AdministratorServices.


I hope it helps! Enjoy your TDD adventure.

Otros consejos

You do not TDD the service but the objects that your service is going to use to do its job.

This has several advantages

  • Your objects are from the start up agnostic if they are going to be used by a service, a GUI or whatnot.
  • It is much easier and faster to create, test and destroy plain objects in any unit testing framework than it will be starting, testing and stopping a service.

Bottom line

  • TDD your own code and leave as much third party setup out of the equation (TDD'ing other peoples code is an oxymoron in the first place:))
  • Let the service use your objects.
  • Automate your testcases.
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top