簡體   English   中英

IIS托管WCF服務:集成測試和代碼覆蓋

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

對於一個項目,我編寫了一個wcf服務庫。 它可以在IIS和自托管服務中托管。

對於所有連接的外部系統,我提供了Mock實現,它們提供了一些通用數據,因此服務(庫)保持運行和工作。 它是一款經典的自動機/有限狀態機。

引導時,所有數據源都連接在一起。 在測試模式下,模擬實現是連接的。 因此,當我運行測試時,服務庫從自托管服務“啟動”,而不是IIS,狀態機繼續運行和處理數據包。

有沒有辦法從這樣的運行中獲得某種“測試覆蓋率”。

如果我可以告訴我從模擬對象提供的示例數據中遇到了哪些代碼路徑,我將非常感激。 然后提供更多的testdata以獲得更高的覆蓋率。

如果我能做到這一點而不必提供“額外的”測試代碼,那就太棒了。 我認為很多案例已經從模擬對象提供的數據中得到了解決。 但是現在我沒有起點。

以下是一些代碼示例,可以更清楚地了解其含義。 當然,代碼被大大簡化了。

在一個非常簡單的控制台應用程序中啟動服務(自托管版本)

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,如上所示創建主機,但無法正確創建主機,因此測試無法真正啟動。 我收到“主機處於故障狀態”錯誤消息。 我按照這個迷你教程 盡管如此,我得到的覆蓋率報告的計算覆蓋率約為20%。 但是服務剛剛起步,到目前為止還沒有做任何工作。

  1. Visual Studio性能工具

這些步驟基本上在本文中進行了描述。 我得到一個myproject.coverage文件,但我無法查看它,因為我只有一個VS專業版,覆蓋范圍似乎僅用於Test Premium或Ultimate版本。

除了嘗試這兩個,我會接受任何答案,顯示如何使用任何這些(openCover首選)。

將接受一個答案,該答案顯示如何測試此設置並獲得代碼覆蓋率,同時利用工具生成大部分代碼(如pex所示,但經過試用后,我發現它不能生成非常好的代碼)。

這將有助於查看服務的運作。

我從未嘗試在覆蓋工具下運行這樣的“控制台類”應用程序。

我建議用NUnit(或任何其他單元測試框架來編寫測試;顯然,它不是單元測試,但技術非常適合)。

在測試中,您打開服務主機,創建服務的客戶端,讓客戶端對您的服務執行某些操作,然后關閉服務主機。

在覆蓋工具下運行此測試,您應該完成。

大約7年前,我使用NUnit和NCover,使用他們當前的版本(NCover是免費軟件,如果我沒記錯的話)。

看起來像OpenCover你實際上正在獲得覆蓋,但服務正在進入Faulted狀態,所以你需要從你的ServiceHost中捕獲故障並加以解決。

基本上你需要某種錯誤日志,我要嘗試的第一件事就是查看系統事件日志(Win + R,eventvwr.msc,Enter)。

您還可以嘗試收聽ServiceHost上的Faulted事件:

host.Faulted += new EventHandler(host_faulted);

以下是解決此問題的另一個SO答案的鏈接: 如何找出ServiceHost Faulted事件的原因

我建議測試你的業務邏輯而不是bootstrap代碼。 我的意思是測試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

    }
}

為了讓你的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";
    }
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM