c#TDD第一次在ServiceBase中

时间:2013-02-18 10:08:06

标签: tdd nunit ninject moq ninject-2

我正在尝试首次实现测试驱动开发。 我的项目是dotnet 3.5中的c#。 我已经阅读了c#中的专业测试驱动开发书,现在我想测试包含windows服务的项目。我已经读过最佳实践是所有代码都必须在测试中。以下是我的Windows服务实现方法onStart和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();
        }

      }
    }
   }

你能告诉我怎样才能在这里实施tdd? 谢谢你的回复。

2 个答案:

答案 0 :(得分:6)

您没有TDD服务,而是您的服务将用于完成其工作的对象。

这有几个优点

  • 如果它们将被服务,GUI或诸如此类的东西使用,那么您的对象来自启动不可知。
  • 在任何单元测试框架中创建,测试和销毁普通对象要比启动,测试和停止服务更容易,更快捷。

底线

  • TDD您自己的代码,并将等同的第三方设置留在等式(TDD'其他人的代码首先是一个矛盾的地方:)) < / LI>
  • 让服务使用您的对象。
  • 自动化您的测试用例。

答案 1 :(得分:4)

由于我只是在工作中重构了4个现有的Windows服务,我无法忍受额外的答案!

我所做的是完全剥离Windows服务类,并为4种不同的实现创建我自己的ServiceBase类及其4个衍生物。最根本的原因是,由于测试周期不方便,测试你的Windows服务真的很痛苦:

应用更改,构建,卸载Windows服务,安装更新的Windows服务,测试,通过调试进行斗争,然后重复...

为我提供TDD服务的主要目的是:

  • 解决所有死锁问题。
  • 验证是否调用了对其他对象的委托调用。
  • 大幅缩短开发测试周期以加快开发速度!

我从您的代码示例中认识到了相同的需求。请允许我展示我自己的简化代码,以便绘制一张图片,说明如何成功TDD您的Windows服务。

我将首先展示测试,因为这是有趣的部分。我将在测试下面添加一些已实现类的片段作为参考。

我的[SetUp]和单元测试

真正的东西开始之前的一些设置......

    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);
    }

测试OnStart行为:

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

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

测试OnPause行为:

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

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

我的服务功能实现

有趣且最有意义的部分片段:

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() {}
    // ..
    // ..
}

如何在真实的WindowsService类中使用您的服务类

(这是视觉基础,但这不会产生太大影响)

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

我在两天内重构了4个Windows服务,其好处是无法测量的! TDD真的帮助我提供了质量。


对您的评论的回复

我的Windows服务类是Service1可视基本类。它创建了一个实例 AdministratorAlertingService

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

AdministratorAlertingService扩展了AdministratorServiceBaseClass,其中包含我的其他Windows服务所具有的共享行为(计时器,开始,暂停,停止)。

如果您只有一个Windows服务,那么您当然不需要基类。

在我的单元测试中,我创建了一个新的SuT(受测试的主题),在这种情况下是一个新的AdministratorAlertingService,我使用AutoResetEvent验证它是否具有正确的开始,暂停,停止行为。 Windows服务完成的“实际工作”在专门针对这些类的单元测试中进行模拟和测试。

这样你就可以(并且应该)TDD你的Windows服务。它将大大减少您的Windows服务的开发测试周期。

您可以选择将集成测试添加到测试套件中以测试完整的功能:您委派的手写开始,暂停,停止行为,您不会模拟执行实际工作的类的功能。我获得了最多TDDing我的管理员服务。


我希望它有所帮助!享受您的TDD冒险。