【问题标题】:IIS hosted WCF service: Integration tests and code coverageIIS 托管的 WCF 服务:集成测试和代码覆盖率
【发布时间】:2015-03-31 01:20:42
【问题描述】:

对于一个项目,我编写了一个 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

使用特殊的测试 dll 可以创建如上所示的主机,但无法正确创建主机,因此测试并没有真正开始。我收到“主机处于故障状态”错误消息。我关注了this mini-tutorial。尽管如此,我还是得到了一份覆盖率报告,计算出的覆盖率约为 20%。但是该服务才刚刚开始,到目前为止还没有做任何工作。

  1. Visual Studio 性能工具

基本上描述了这些步骤in this article。我得到了一个 myproject.coverage 文件,但我无法查看它,因为我只有一个 VS Professional,覆盖范围似乎只在 Test Premium 或 Ultimate 版中使用。

除了尝试过这两个之外,我将接受任何显示如何启动并运行其中任何一个的答案(首选 openCover)。

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

【问题讨论】:

  • @RobertHarvey 谢谢你的链接。我对这个问题做了一些澄清。
  • 请不要在您的问题中添加“编辑”;这不是一个论坛。 Stack Overflow 上的每个帖子都已经有一个detailed edit history,任何人都可以查看。
  • 嗯,你的例子呢?
  • 您的案例似乎不是唯一的。代码就是代码;覆盖就是覆盖。让我们更具体的唯一方法是查看您想要测试覆盖率的一些代码。

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


【解决方案1】:

我建议测试您的业务逻辑,而不是引导代码。我的意思是测试 DataManager 类而不是托管和初始化代码。您可以使用其中一种单元测试框架(例如 NUnit)编写单元测试。然后,您可以在 Visual Studio 中使用 Resharper Ultimate 或在使用代码覆盖率工具(如 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

    }
}

【讨论】:

    【解决方案2】:

    为了让您的单元测试框架确定覆盖范围,您必须在框架的“运行器”(也就是执行测试的进程)内托管服务。 覆盖范围是由“跑步者”计算的,这意味着如果服务托管在其他任何地方,您将无法获得覆盖范围。 下面我将添加一个示例如何执行此操作。

    问候 胡伊·朱卡

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

    【讨论】:

      【解决方案3】:

      看起来使用 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

      【讨论】:

        【解决方案4】:

        这将有助于查看服务的操作。

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

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

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

        在覆盖工具下运行这个测试,你应该完成了。

        大约 7 年前,我使用 NUnit 和 NCover 完成了这项工作,当时使用的是它们的当前版本(如果我没记错的话,NCover 是免费软件)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-01-11
          • 2018-10-18
          • 1970-01-01
          • 2014-09-26
          • 1970-01-01
          相关资源
          最近更新 更多