2012-02-01 50 views
19

Tôi đang viết một số mã gọi một dịch vụ web, đọc lại phản hồi và thực hiện điều gì đó với nó. Mã của tôi trông danh nghĩa như thế này:Kiểm tra đơn vị các yêu cầu HTTP trong C#

string body = CreateHttpBody(regularExpression, strategy); 

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_url); 
request.Method = "POST"; 
request.ContentType = "text/plain; charset=utf-8"; 

using (Stream requestStream = request.GetRequestStream()) 
{ 
    requestStream.Write(Encoding.UTF8.GetBytes(body), 0, body.Length); 
    requestStream.Flush(); 
} 

using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) 
{ 
    byte[] data = new byte[response.ContentLength]; 

    using (Stream stream = response.GetResponseStream()) 
    { 
     int bytesRead = 0; 

     while (bytesRead < data.Length) 
     { 
      bytesRead += stream.Read(data, bytesRead, data.Length - bytesRead); 
     } 
    } 

    return ExtractResponse(Encoding.UTF8.GetString(data)); 
} 

Các bộ phận duy nhất mà tôi đang thực sự làm bất kỳ thao tác tùy chỉnh là trong ExtractResponseCreateHttpBody phương pháp. Tuy nhiên nó cảm thấy sai để chỉ đơn vị kiểm tra những phương pháp đó, và hy vọng rằng phần còn lại của mã đến với nhau một cách chính xác. Có cách nào tôi có thể đánh chặn yêu cầu HTTP và thay vào đó cung cấp dữ liệu giả lập không?

EDIT Thông tin này hiện đã lỗi thời. Việc xây dựng loại mã này dễ dàng hơn bằng cách sử dụng các thư viện System.Net.Http.HttpClient.

+0

Bạn có thể thiết lập một _ (misbehaving, hoặc bất cứ điều gì bạn muốn kiểm tra) _ webserver và sau đó sửa đổi các tập tin '\ Windows \ System32 \ drivers \ etc \ hosts' để" chuyển hướng "yêu cầu đến máy chủ đó. – ordag

+0

Liên quan: https://stackoverflow.com/q/9823039/12484 –

Trả lời

14

Trong mã của bạn, bạn không thể chặn các cuộc gọi đến HttpWebRequest vì bạn tạo đối tượng theo cùng một phương pháp. Nếu bạn để một đối tượng khác tạo ra HttpWebRequest, bạn có thể truyền vào đối tượng giả và sử dụng đối tượng đó để kiểm tra.

Vì vậy, thay vì điều này:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_url); 

Sử dụng này:

IHttpWebRequest request = this.WebRequestFactory.Create(_url); 

Trong thử nghiệm đơn vị của bạn, bạn có thể vượt qua trong một WebRequestFactory mà tạo ra một đối tượng giả.

Hơn nữa, bạn có thể chia nhỏ mã đọc luồng của mình trong một chức năng riêng biệt:

using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) 
{ 
    byte[] data = ReadStream(response.GetResponseStream()); 
    return ExtractResponse(Encoding.UTF8.GetString(data)); 
} 

Điều này làm cho nó có thể để kiểm tra ReadStream() riêng.

Để thực hiện thêm bài kiểm tra tích hợp, bạn có thể thiết lập máy chủ HTTP của riêng bạn để trả về dữ liệu kiểm tra và chuyển URL của máy chủ đó đến phương thức của bạn.

+1

Sau khi thử nghiệm với Moles và Moq trong một nỗ lực để giả mạo các đối tượng HttpWebRequest và HttpWebResponse, tôi phải kết luận rằng nó không thể được thực hiện, làm cho điều này nhiều nhất tiếp cận hiệu quả. – Ceilingfish

6

Tôi có thể bắt đầu bằng cách tái cấu trúc mã để làm cho nó kết hợp yếu hơn với yêu cầu HTTP thực tế. Ngay bây giờ mã này dường như làm khá nhiều thứ.

Điều này có thể được thực hiện bằng cách đưa ra một sự trừu tượng:

public interface IDataRetriever 
{ 
    public byte[] RetrieveData(byte[] request); 
} 

Bây giờ lớp mà bạn đang cố gắng kiểm tra đơn vị có thể được tách riêng từ yêu cầu HTTP thực tế bằng cách sử dụng Inversion of mẫu thiết kế điều khiển:

public class ClassToTest 
{ 
    private readonly IDataRetriever _dataRetriever; 
    public Foo(IDataRetriever dataRetriever) 
    { 
     _dataRetriever = dataRetriever; 
    } 

    public string MethodToTest(string regularExpression, string strategy) 
    { 
     string body = CreateHttpBody(regularExpression, strategy); 
     byte[] result = _dataRetriever.RetrieveData(Encoding.UTF8.GetBytes(body)); 
     return ExtractResponse(Encoding.UTF8.GetString(result)); 
    } 
} 

Không còn trách nhiệm của ClassToTest đối phó với yêu cầu HTTP thực tế nữa. Nó bây giờ được tách ra. Kiểm tra các MethodToTest trở thành một nhiệm vụ tầm thường.

Và phần cuối cùng rõ ràng là phải có một thực hiện các khái niệm trừu tượng mà chúng tôi đã giới thiệu:

public class MyDataRetriever : IDataRetriever 
{ 
    private readonly string _url; 
    public MyDataRetriever(string url) 
    { 
     _url = url; 
    } 

    public byte[] RetrieveData(byte[] request) 
    { 
     using (var client = new WebClient()) 
     { 
      client.Headers[HttpRequestHeader.ContentType] = "text/plain; charset=utf-8"; 
      return client.UploadData(_url, request); 
     } 
    } 
} 

Sau đó, bạn có thể cấu hình khuôn khổ DI yêu thích của bạn để tiêm một trường hợp MyDataRetriever vào constructor ClassToTest lớp trong thực tế của bạn ứng dụng.

+1

Tôi có thể thấy điểm của bạn, tuy nhiên lớp mà mã này cư trú được thiết kế để trở thành cơ chế truy xuất dữ liệu HTTP. Tôi muốn kiểm tra xem mã mà tôi đang sử dụng để xử lý yêu cầu có được định dạng tốt hay không, vì vậy nếu tôi trừu tượng hóa mã HTTP, thì tôi sẽ trừu tượng hóa chính xác phần mà tôi muốn thử nghiệm. – Ceilingfish

+2

@Ceilingfish, không, bạn chắc chắn không muốn thử nghiệm các lớp HttpWebRequest và HttpWebResponse. Chúng đã được Microsoft thử nghiệm rộng rãi. Những gì bạn muốn thử nghiệm là phương pháp của bạn cung cấp đầu vào chính xác cho một số hộp đen (được cung cấp cho bạn và được dự kiến ​​sẽ hoạt động) và nó chuyển đổi đầu ra của nó sang một số định dạng mong muốn. Đó là phương pháp cơ bản của bạn. Dù sao, không có mức trừu tượng này, bạn không thể giả lập các lớp đó và bạn không còn thực hiện các bài kiểm tra đơn vị nhưng các bài kiểm tra tích hợp nữa. –

+3

Đúng, tôi không muốn kiểm tra các lớp đó, tôi muốn kiểm tra _usage_ của các lớp đó. Mã để gửi và nhận yêu cầu HTTP không được ẩn hoàn toàn, do đó, nó dựa vào mã của tôi là chính xác để đọc thành công câu trả lời từ yêu cầu. – Ceilingfish

4

Nếu giả mạo HttpWebRequest và HttpWebResponse trở nên quá cồng kềnh, hoặc nếu bạn cần kiểm tra mã trong thử nghiệm chấp nhận, nơi bạn đang gọi mã từ "bên ngoài", thì việc tạo dịch vụ giả có lẽ là cách tốt nhất đi.

Tôi thực sự đã viết một thư viện mã nguồn mở có tên là MockHttpServer để hỗ trợ việc này, làm cho nó trở nên cực kỳ đơn giản để loại bỏ bất kỳ dịch vụ bên ngoài nào giao tiếp qua HTTP.

Đây là ví dụ về việc sử dụng nó với RestSharp để gọi điểm cuối API với nó, nhưng HttpWebRequest cũng sẽ hoạt động.

using (new MockServer(3333, "/api/customer", (req, rsp, prm) => "Result Body")) 
{ 
    var client = new RestClient("http://localhost:3333/"); 
    var result = client.Execute(new RestRequest("/api/customer", Method.GET)); 
} 

Có một readme khá chi tiết trên trang GitHub mà đi qua tất cả các tùy chọn có sẵn để sử dụng nó, và thư viện riêng của mình là có sẵn thông qua NuGet.

+0

Tôi đã tìm thấy https://github.com/unosquare/embedio thực sự hữu ích khi làm điều này – Ceilingfish

+0

@Ceilingfish rằng dự án trông thật tuyệt, nhưng có vẻ như quá mức cần thiết chỉ đơn giản là chế nhạo một vài câu trả lời. Đó là một thư viện máy chủ web hoàn chỉnh, điều này bổ sung thêm một chút phức tạp. –

2

Nếu bạn vui lòng chuyển đến HttpClient (thư viện chính thức, di động, http máy khách), sau đó tôi đã viết thư viện một thời gian ngắn có thể được gọi là MockHttp. Nó cung cấp một API thông thạo cho phép bạn cung cấp câu trả lời cho các yêu cầu được so khớp bằng một loạt các thuộc tính.

Các vấn đề liên quan