IIS托管WCF服务:集成测试和代码覆盖

时间:2015-01-31 18:23:32

标签: c# wcf unit-testing integration-testing code-coverage

对于一个项目,我编写了一个wcf服务库。它可以在IIS和自托管服务中托管。

对于所有连接的外部系统,我提供了Mock实现,它们提供了一些通用数据,因此服务(库)保持运行并正常工作。它是一款经典的自动机/有限状态机。

引导时,所有数据源都已连接。在测试模式下,模拟实现是连接的。因此,当我运行测试时,服务库已经启动"来自自托管服务,而不是IIS,状态机一直在运行和处理数据包。

有没有办法获得某种"测试覆盖率"从这样的运行。

如果能告诉我从模拟对象提供的示例数据中遇到了哪些代码路径,我真的很感激。然后提供更多的测试数据以获得更高的覆盖率。

如果我能做到这一点而不必提供额外的"测试代码,会很棒。我认为很多案例已经从模拟对象提供的数据中得到了解决。但是现在我没有起点。

以下是一些代码示例,可以更清楚地了解其含义。当然,代码被大大简化了。

在一个非常简单的控制台应用程序中启动服务(自托管版本)

static void Main(string[] args)
{
    using (var host = new ServiceHost(typeof(MyServiceLib.Service.MyServiceLib)))
    {
        host.Open();
        Console.ReadLine();
        host.Close();
    }
}

在服务库中,从该代码中调用构造函数

public MyServiceLib()
{
    Task.Factory.StartNew(this.Scaffold);
}

除了启动状态机

之外什么也没做
private void Scaffold()
{
    // lots of code deleted for simplicity reasons
    var dataSource = new MockDataSource();

    // inject the mocked datasource
    this.dataManager = new DataManager(dataSource);

    // this runs in its own thread. There are parts that are started on a timer event.
    this.dataManager.Start();
}

public class DataManager : IDataManager
{
     public void Start()
     {
         while (this.IsRunning)
         {
             var data = this.dataSource.getNext();

             if (data != null)
             {
                 // do some work with the data retrieved
                 // lots of code paths will be hit from that
                 this.Process(data);
             }
             else
             {
                 Thread.Sleep(1000);
             }
         }
     }

     public void Process(IData data)
     {
        switch (data.PackageType)
        {
            case EnumPackageType.Single:
            {
                ProcessSingle(data);
                break;
            }
            case EnumPackageType.Multiple:
            {
                ProcessMultiple(data);
                break;
            }
            // here are lots of cases
            default:
            {
                Logger.Error("unknown package type");
                break;
            }
        }
     }
}

到目前为止我尝试过:

  1. OpenCover
  2. 使用特殊的测试dll,如上所示创建主机,但无法正确创建主机,因此测试无法真正启动。我得到一个"主机处于故障状态"错误信息。我跟着this mini-tutorial。尽管如此,我得到的覆盖率报告的计算覆盖率约为20%。但是服务刚刚起步,到目前为止还没有做任何工作。

    1. Visual Studio性能工具
    2. 这些步骤基本上是in this article所描述的。我得到了一个myproject.coverage文件,但是我无法查看它,因为我只有VS专业版,覆盖范围似乎仅用于Test Premium或Ultimate版本。

      除了尝试过这两个之外,我会接受任何答案,展示如何使用任何一个(openCover首选)来启动和运行。

      将接受一个答案,该答案显示如何测试此设置并获得代码覆盖率,同时利用工具生成大部分代码(如pex那样,但经过试用后我发现它不能生成非常好的代码)。

4 个答案:

答案 0 :(得分:1)

有助于查看服务的运营情况。

我从没尝试在覆盖工具下运行这样的“控制台类”应用程序。

我建议编写一个测试,让我们说NUnit(或任何其他单元测试框架;显然,它不是单元测试,但技术非常适合)。

在测试中,您打开服务主机,创建服务的客户端,让客户端对您的服务执行某些操作,然后关闭服务主机。

在覆盖工具下运行此测试,您应该完成。

我在大约7年前用NUnit和NCover做了那个,当时使用他们当前的版本(NCover是免费软件,如果我没记错的话)。

答案 1 :(得分:1)

使用OpenCover看起来实际上是覆盖范围,但服务正在进入Faulted状态,因此您需要从ServiceHost中捕获故障并对其进行处理。

基本上你需要某种错误日志,我要尝试的第一件事就是查看系统事件日志(Win + R,eventvwr.msc,Enter)。

您还可以尝试收听ServiceHost上的Faulted事件:

host.Faulted += new EventHandler(host_faulted);

以下是解决此问题的另一个SO答案的链接: How to find out the reason of ServiceHost Faulted event

答案 2 :(得分:1)

我建议测试你的业务逻辑而不是bootstrap代码。我的意思是测试DataManager类而不是托管和初始化代码。您可以使用其中一个单元测试框架编写单元测试,例如NUnit。然后,您可以在带有Resharper Ultimate的Visual Studio中或在使用代码覆盖率的持续集成工具(如OpenCover或dotCover)中运行测试,以获得代码覆盖率。

[TestFixture]
public class DataManagerTests
{

    [Test]
    public void Process_Single_Processed()
    {
        // Arrange
        IData data = new SingleData();

        DataManager dataManager = new DataManager();

        // Act
        dataManager.Process(data);

        // Assert
        // check data processed correctly

    }
}

答案 3 :(得分:0)

为了让你的Unit-Test-Framework能够确定你必须在框架的“runner”(也就是执行测试的过程)中托管服务的覆盖范围。 覆盖范围由“跑步者”计算并且与“跑步者”相关,这意味着如果服务在其他任何地方托管,则无法获得保险。 下面我将添加一个如何执行此操作的示例。

问候 Juy Juka

namespace ConsoleApplication4
{
  using System.ServiceModel; // Don't forgett to add System.ServiceModel as Reference to the Project.

  public class Program
  {
    static void Main(string[] args)
    {
      string arg = ((args != null && args.Length > decimal.Zero ? args[(int)decimal.Zero] : null) ?? string.Empty).ToLower(); // This is only reading the input for the example application, see also end of Main method.
      string randomUrl = "net.tcp://localhost:60" + new System.Random().Next(1, 100) + "/rnd" + new System.Random().Next(); // random URL to allow multiple instances parallel (for example in Unit-Tests). // Better way?
      if (arg.StartsWith("t"))
      {
        // this part could be written as a UnitTest and should be 
        string result = null;
        using (ServiceHost host = new ServiceHost(typeof(MyService)))
        {
          host.AddServiceEndpoint(typeof(IMyService), new NetTcpBinding(), randomUrl);
          host.Open();
          IMyService instance = ChannelFactory<IMyService>.CreateChannel(new NetTcpBinding(), new EndpointAddress(randomUrl), null);
          result = instance.GetIdentity();
          host.Close();
        }
        // Assert.Equals(result,"Juy Juka");
      }
      else if (arg.StartsWith("s"))
      {
        // This part runs the service and provides it to the outside. Just to show that it is a real and working host. (and not only working in a Unit-Test)
        using (ServiceHost host = new ServiceHost(typeof(MyService)))
        {
          host.AddServiceEndpoint(typeof(IMyService), new NetTcpBinding(), randomUrl);
          host.Open();
          System.Console.Out.WriteLine("Service hosted under following URL. Terminate with ENTER.");
          System.Console.Out.WriteLine(randomUrl);
          System.Console.In.ReadLine();
          host.Close();
        }
      }
      else if (arg.StartsWith("c"))
      {
        // This part consumes a service that is run/hosted outoside of the application. Just to show that it is a real and working host. (and not only working in a Unit-Test)
        System.Console.Out.WriteLine("Please enter URL of the Service. Execute GetIdentity with ENTER. Terminate with ENTER.");
        IMyService instance = ChannelFactory<IMyService>.CreateChannel(new NetTcpBinding(), new EndpointAddress(System.Console.In.ReadLine()), null);
        System.Console.Out.WriteLine(instance.GetIdentity());
        System.Console.In.ReadLine();
      }
      else
      {
        // This is only to explain the example application here.
        System.Console.Out.WriteLine("I don't understand? Please use one of the following (Terminate this instance with ENTER):");
        System.Console.Out.WriteLine("t: To host and call the service at once, like in a UnitTest.");
        System.Console.Out.WriteLine("s: To host the servic, waiting for clients.");
        System.Console.Out.WriteLine("c: To contact a hosted service and display it's GetIdenttity result.");
        System.Console.In.ReadLine();
      }
    }
  }

  // Declaration and Implementation of the Service

  [ServiceContract]
  public interface IMyService
  {
    [OperationContract]
    string GetIdentity();
  }

  public class MyService : IMyService
  {
    public string GetIdentity()
    {
      return "Juy Juka";
    }
  }
}