【问题标题】:c# TDD first time in ServiceBasec# TDD 第一次在ServiceBase中
【发布时间】:2013-02-18 10:08:05
【问题描述】:

我第一次尝试实现测试驱动的开发。 我的项目是 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 吗? 感谢您的回复。

【问题讨论】:

  • 如果您已经编写了实现......您将无法完成测试驱动开发。
  • 要补充@J.Steen 所说的话,您需要明确区分集成测试和单元测试。例如,测试记录器是否已将某些内容写入文件,这意味着您正在进行集成测试。通常在单元测试中,您会尝试使用虚假的外部依赖项删除所有这些外部依赖项并测试您的核心“业务”逻辑。我希望我说得通
  • @J.Seen 那是我的目标,我想从测试开始重写我的旧项目..但可能是 Dimitar 所说的集成测试......
  • @AndreaG 对。细节,细节,细节。您需要在问题中包含这些信息花絮,以减少误解的机会。 =)
  • 这类似于 TDD-ing 控制台应用程序或 GUI,您编写一个 Launcher 程序并立即委托给一个类。例如Main() 函数调用 BootStrapper.Run() - 现在您可以 TDD bootstrapper.Run 并继续向内。我会通过手动运行手动测试 Main Calls Bootstrapper 是否。它破坏的可能性较小并且没有人发现+如果它曾经做过快速修复=>也许不值得自动化。

标签: tdd nunit ninject moq ninject-2


【解决方案1】:

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

这有几个优点

  • 您的对象从一开始就与服务、GUI 或其他对象使用无关。
  • 在任何单元测试框架中创建、测试和销毁普通对象都比启动、测试和停止服务要容易和快速得多。

底线

  • TDD 你自己的代码,尽量避免第三方设置(TDD'ing 其他人的代码首先是矛盾的:))
  • 让服务使用您的对象。
  • 自动化您的测试用例。

【讨论】:

  • “TDD your own code”,简单而伟大的答案,我自己再好不过了!
【解决方案2】:

由于我刚刚在工作中重构了 4 个现有的 Windows 服务,所以我忍不住要提供一个额外的答案!

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

应用更改、构建、卸载 windows 服务、安装更新的 windows 服务、测试、调试和重复...

对我来说,对 windows 服务进行 TDD 的主要目的是:

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

我从您的代码示例中认识到同样的需求。请允许我展示我自己的简化代码来描绘您可以做些什么来成功 TDD 您的 Windows 服务。

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

我的 [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();
    }

我对服务功能的实现

有趣和最有意义的部分的sn-p:

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 Visual Basic 类。它创建一个实例 AdministratorAlertingService.

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

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

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

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

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

您可以选择将集成测试添加到您的测试套件中以测试完整的功能:您委托的手写启动、暂停、停止行为,您不会模拟执行实际工作的类的功能。我在我的 AdministratorServices 中获得了最多的 TDD。


希望对你有帮助!享受您的 TDD 冒险。

【讨论】:

  • 对不起,我不明白 AdministratorAlertingService 和 AdministratorServiceBase 之间的关系
  • 嘿@AndreaG,我在帖子底部更新了我的答案,并提供了更多详细信息。希望能澄清一下。如果没有,请告诉我
  • 好的,另一个问题,在我的 serviceBase 的 onstart() 方法中,我创建了新线程并在启动后启动。 _workerObject = new Worker(); _workerThread = 新线程(_workerObject.DoWork); // 启动工作线程。 _workerThread.Start();在您的实现中,在 alertingService 的 onstart() 方法中创建了一个新线程是否正确?
  • 我没有使用Thread,但我使用的是TimerTimer 在构造函数中创建,并在OnStart 方法中启动。在我的单元测试中,我验证 TimerElapsed 在我期望它被调用时被调用。在您的情况下,您可能希望检查工作方法 _workerObject.DoWork() 是否在您期望的时候被调用。很相似。我现在必须喂我们的宝宝,所以看起来会离线一个小时左右:)。对于它的价值:我认为你在正确的轨道上。一会儿会回来。
猜你喜欢
  • 2012-08-19
  • 1970-01-01
  • 1970-01-01
  • 2021-09-30
  • 1970-01-01
  • 2010-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多