• WebApi的一种集成测试写法(in-memory)


    WebApi的一种集成测试写法(in-memory)

     

    大家是如何对webApi写测试的呢?

    1.利用Fiddler直接做请求,观察response的内容。

    2.利用Httpclient做请求,断言response的内容。

    3.直接调用webApi的action,这种方式的测试跟真实的调用还是有一定差距,不够完美。

    接下来我介绍一种webApi的in-memory调用方法,也能够达到对webApi的测试,并且由于是in-memory调用,效率也比较高,非常适写单元测试。本文参考了In memory client, host and integration testing of your Web API service

    一、首先写一个OrderController用来做测试用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class OrderController : ApiController
        {
            // GET api/order
            public Order Get()
            {
                return new Order(){Id = 1,Descriptions = "descriptions",Name = "name"};
            }
     
            // GET api/order/5
            public string Get(int id)
            {
                return "value";
            }
     
            // POST api/order
            public Order Post(Order order)
            {
                return order;
            }
     
            // DELETE api/order/5
            public void Delete(int id)
            {
            }
        }

    二、WebApi的请求过程

    webApi的核心是对消息的管道处理,整个核心是有一系列消息处理器(HttpMessageHandler)首尾连接的双向管道,管道头为HttpServer,管道尾为HttpControllerDispatcher,HttpControllerDispatcher负责对controller的激活和action的执行,然后相应的消息逆向流出管道。

    所以我们可以利用HttpMessageInvoker将一个请求消息HttpRequestMessage发送到管道中,最后收到的消息HttpResponseMessage就代表一个真实的请求响应。

    三、Get请求的测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    [Test]
    public void GetTest()
    {
        string baseAddress = "http://localhost:33203/";
     
        HttpConfiguration config = new HttpConfiguration();
        WebApiConfig.Register(config);
        config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
        HttpServer server = new HttpServer(config);
        HttpMessageInvoker messageInvoker = new HttpMessageInvoker(server);
        CancellationTokenSource cts = new CancellationTokenSource();
        HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, baseAddress + "api/order");
     
        using (HttpResponseMessage response = messageInvoker.SendAsync(request, cts.Token).Result)
        {
            var content = response.Content.ReadAsStringAsync().Result;
            var result = JsonConvert.DeserializeObject<Order>(content);
     
            result.Name.Should().Be("name");
        }
    }

    四、Post请求的测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    [Test]
    public void PostTest()
    {
        string baseAddress = "http://localhost:33203/";
     
        HttpConfiguration config = new HttpConfiguration();
        WebApiConfig.Register(config);
        config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
        HttpServer server = new HttpServer(config);
        HttpMessageInvoker messageInvoker = new HttpMessageInvoker(server);
        CancellationTokenSource cts = new CancellationTokenSource();
        HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, baseAddress + "api/order");
        var order = new Order() { Id = 1, Name = "orderName", Descriptions = "orderDescriptions" };
        request.Content = new ObjectContent<Order>(order, new JsonMediaTypeFormatter());
        using (HttpResponseMessage response = messageInvoker.SendAsync(request, cts.Token).Result)
        {
            var content = JsonConvert.SerializeObject(order, new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver() });
            response.Content.ReadAsStringAsync().Result.Should().Be(content);
        }
    }

    四、重构

    可以看到这两个测试大部分的代码是相同的,都是用来发送请求。因此我们提取一个webApiTestBase类,该基类可以提供InvokeGetRequest,InvokePostRequest,InvokePutRequest等方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    public abstract class ApiTestBase
        {
            public abstract string GetBaseAddress();
     
            protected TResult InvokeGetRequest<TResult>(string api)
            {
                using (var invoker = CreateMessageInvoker())
                {
                    using (var cts = new CancellationTokenSource())
                    {
                        var request = new HttpRequestMessage(HttpMethod.Get, GetBaseAddress() + api);
                        using (HttpResponseMessage response = invoker.SendAsync(request, cts.Token).Result)
                        {
                            var result = response.Content.ReadAsStringAsync().Result;
                            return JsonConvert.DeserializeObject<TResult>(result);
                        }
                    }
                }
            }
     
            protected TResult InvokePostRequest<TResult, TArguemnt>(string api, TArguemnt arg)
            {
                var invoker = CreateMessageInvoker();
                using (var cts = new CancellationTokenSource())
                {
                    var request = new HttpRequestMessage(HttpMethod.Post, GetBaseAddress() + api);
                    request.Content = new ObjectContent<TArguemnt>(arg, new JsonMediaTypeFormatter());
                    using (HttpResponseMessage response = invoker.SendAsync(request, cts.Token).Result)
                    {
                        var result = response.Content.ReadAsStringAsync().Result;
                        return JsonConvert.DeserializeObject<TResult>(result);
                    }
                }
            }
     
            private HttpMessageInvoker CreateMessageInvoker()
            {
                var config = new HttpConfiguration();
                WebApiConfig.Register(config);
                var server = new HttpServer(config);
                var messageInvoker = new HttpMessageInvoker(server);
                return messageInvoker;
            }
        }

    有了这个基类,我们写测试只需要重写方法GetBaseAddress(),然后直接调用基类方法并进行断言即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    [TestFixture]
        public class OrderApiTests:ApiTestBase
        {
            public override string GetBaseAddress()
            {
                return "http://localhost:33203/";
            }
     
            [Test]
            public void Should_get_order_successfully()
            {
                var result = InvokeGetRequest<Order>("api/order");
     
                result.Name.Should().Be("name");
                result.Descriptions.Should().Be("descriptions");
                result.Id.Should().Be(1);
            }
     
            [Test]
            public void Should_post_order_successfully()
            {
                var newOrder=new Order(){Name = "newOrder",Id = 100,Descriptions = "new-order-description"};
     
                var result = InvokePostRequest<Order,Order>("api/order", newOrder);
     
                result.Name.Should().Be("newOrder");
                result.Id.Should().Be(100);
                result.Descriptions.Should().Be("new-order-description");
            }
        }

    是不是干净多了。

    这种in-memory的测试方案有什么优点和缺点呢?

    优点:

    1.模拟真实调用,需要传入api地址即可得到结果,由于整个调用是in-memory的,所有效率很高,很适合集成测试。

    2.整个测试时可以调试的,可以直接从单元测试调试进去,如果你写一个httpClient的测试,需要把webApi启动起来,然后。。。麻烦

    缺点:我觉得原文作者说的那些缺点都可以忽略不计^_^

  • 相关阅读:
    VuGen错误处理函数
    LR的日志
    创建性能测试脚本时如何选择HTML模式和URL模式
    Java变量
    查找&排序
    selenium执行JS
    Python中 is 和 == 的区别
    Python中 and or的计算规则
    selenium使用location定位元素坐标偏差
    错误:Could not find an available JavaScript runtime
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/4660256.html
Copyright © 2020-2023  润新知