简体   繁体   English

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

[英]IIS hosted WCF service: Integration tests and code coverage

For a project I have programmed a wcf service library. 对于一个项目,我编写了一个wcf服务库。 It can be hosted in IIS and in a self-hosted service. 它可以在IIS和自托管服务中托管。

For all external systems that are connected, I have provided Mock implementations which give some generic data, so such the service (library) keeps running and doing work. 对于所有连接的外部系统,我提供了Mock实现,它们提供了一些通用数据,因此服务(库)保持运行和工作。 It is a classic automaton / finite-state machine. 它是一款经典的自动机/有限状态机。

While bootstrapping, all data sources are connected. 引导时,所有数据源都连接在一起。 In testing mode, the mock implementations are connected. 在测试模式下,模拟实现是连接的。 So when I run tests, the service library is "started" from a self-hosted service, not IIS and the the state machine keeps running and processing data packages. 因此,当我运行测试时,服务库从自托管服务“启动”,而不是IIS,状态机继续运行和处理数据包。

Is there any way to get some kind of "test coverage" from such a run. 有没有办法从这样的运行中获得某种“测试覆盖率”。

I would really appreciate if I could tell which code paths are hit by the example data I provide from the mock objects. 如果我可以告诉我从模拟对象提供的示例数据中遇到了哪些代码路径,我将非常感激。 And then provide more testdata to get a higher coverage. 然后提供更多的testdata以获得更高的覆盖率。

If I could do this without having to provide "lots of extra" testing code, it would be great. 如果我能做到这一点而不必提供“额外的”测试代码,那就太棒了。 I think a lot of cases are already covered from the data provided from the mock objects. 我认为很多案例已经从模拟对象提供的数据中得到了解决。 But right now I have no starting point for that. 但是现在我没有起点。

Here are some code examples to give a more clear picture of what is meant. 以下是一些代码示例,可以更清楚地了解其含义。 Code is strongly simplified of course. 当然,代码被大大简化了。

In a very simple console application to start the service (self hosted version) 在一个非常简单的控制台应用程序中启动服务(自托管版本)

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

In the service library, a constructor is called from that code 在服务库中,从该代码调用构造函数

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

Which does nothing more than starting the state machine 这只不过是启动状态机

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

What I have tried so far: 到目前为止我尝试了什么:

  1. OpenCover OpenCover

with a special test dll that would create the Host as shown above, but the host cannot be created properly, so the testing does not start really. 使用特殊的测试dll,如上所示创建主机,但无法正确创建主机,因此测试无法真正启动。 I get a "Host is in fault state" error message. 我收到“主机处于故障状态”错误消息。 I followed this mini-tutorial . 我按照这个迷你教程 Despite that I get a coverage report with a calculated coverage of about 20%. 尽管如此,我得到的覆盖率报告的计算覆盖率约为20%。 But the service is just starting, it is not doing any work so far. 但是服务刚刚起步,到目前为止还没有做任何工作。

  1. Visual Studio Performance Tools Visual Studio性能工具

The steps are essentially described in this article . 这些步骤基本上在本文中进行了描述。 I get a myproject.coverage file, but I cannot view it, because I only have a VS Professional, the coverage seems to be only of use in Test Premium or Ultimate editions. 我得到一个myproject.coverage文件,但我无法查看它,因为我只有一个VS专业版,覆盖范围似乎仅用于Test Premium或Ultimate版本。

Besides having tried those two, I will accept any answer showing how to get it up and running with any of those (openCover preferred). 除了尝试这两个,我会接受任何答案,显示如何使用任何这些(openCover首选)。

Will accept an answer that shows how to test this setup and get a code coverage while leveraging tools to generate most of the code (as pex would, but after trial I see it does not generate very good code). 将接受一个答案,该答案显示如何测试此设置并获得代码覆盖率,同时利用工具生成大部分代码(如pex所示,但经过试用后,我发现它不能生成非常好的代码)。

It would help to see the operations of the service. 这将有助于查看服务的运作。

I never tried running such "console kind" application under a coverage tool. 我从未尝试在覆盖工具下运行这样的“控制台类”应用程序。

I would suggest writing a test with let's say NUnit (or any other unit testing framework; it's not a unit test, obviously, but the technique fits quite well). 我建议用NUnit(或任何其他单元测试框架来编写测试;显然,它不是单元测试,但技术非常适合)。

In the test, you open the service host, create a client of the service, let the client execute some operations on your service, and close the service host. 在测试中,您打开服务主机,创建服务的客户端,让客户端对您的服务执行某些操作,然后关闭服务主机。

Run this test under a coverage tool, and you should be done. 在覆盖工具下运行此测试,您应该完成。

I've done that with NUnit and NCover about 7 years ago, using their current versions at that time (NCover was free software, if I remember it right). 大约7年前,我使用NUnit和NCover,使用他们当前的版本(NCover是免费软件,如果我没记错的话)。

Looks like with OpenCover you are actually getting the coverage, but the service is entering Faulted state, so to you need to catch the faults from your ServiceHost and adress that. 看起来像OpenCover你实际上正在获得覆盖,但服务正在进入Faulted状态,所以你需要从你的ServiceHost中捕获故障并加以解决。

Basically you need some kind of error log, and the first thing i would try is looking in the system event logs (Win+R, eventvwr.msc, Enter). 基本上你需要某种错误日志,我要尝试的第一件事就是查看系统事件日志(Win + R,eventvwr.msc,Enter)。

You can also try to listen to the Faulted events on your ServiceHost: 您还可以尝试收听ServiceHost上的Faulted事件:

host.Faulted += new EventHandler(host_faulted);

Here is the link to another SO answer addressing this issue: How to find out the reason of ServiceHost Faulted event 以下是解决此问题的另一个SO答案的链接: 如何找出ServiceHost Faulted事件的原因

I would suggest testing your business logic and not the bootstrap code. 我建议测试你的业务逻辑而不是bootstrap代码。 I mean testing DataManager class and not the hosting and the initializing code. 我的意思是测试DataManager类而不是托管和初始化代码。 You can write a unit test, using one of the unit testing frameworks, for example NUnit. 您可以使用其中一个单元测试框架编写单元测试,例如NUnit。 Then you can run your tests either in Visual Studio with Resharper Ultimate or in your Continuous Integration with Code Coverage tool, like OpenCover or dotCover to get your code coverage. 然后,您可以在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

    }
}

in order to allow your Unit-Test-Framework to determin the coverage you have to host the service within the "runner" of the framework (aka. the process that is executing the tests). 为了让你的Unit-Test-Framework能够确定你必须在框架的“runner”(也就是执行测试的过程)中托管服务的覆盖范围。 The coverage is calculated by and withing the "runner" what means that you can not get coverage if the service is hosted anywhere else. 覆盖范围由“跑步者”计算并且与“跑步者”相关,这意味着如果服务在其他任何地方托管,则无法获得保险。 Below I'll add an example how to do this. 下面我将添加一个如何执行此操作的示例。

Greetings Juy Juka 问候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";
    }
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM