單元測試-重構既有的LineNotifyService

單元測試

最近在思考怎麼把HttpClinet隔離出來,以下為例,這個程式碼是有簡化過。

這是已經完成的Production Code,原本的產品是有定IMessageService介面,這邊就省略了。

以下是程式範例,從既有程式來重構與測試吧...

筆者的產品迭代方式 ,從不可測試的爛code→可測試的爛code→可測試的好code

大部分都是遇到爛Code頂多是進化到寫可測試的濫Code不要壞掉,然後再進一步優化改成好的測試Code跟情境有關測試。

    class LineNotifyService
    {
       public bool Send(string msg)
        {
            try
            {
                //TODO:待改善
                using (var client = new HttpClient())
                {
                    client.BaseAddress = new Uri(CommEnvironment.LineNotifyUrl);
                    client.DefaultRequestHeaders.Add("Authorization",
                        "Bearer " + CommEnvironment.LineToken);

                    var form = new FormUrlEncodedContent(new[]
                    {
                        new KeyValuePair<string, string>("message", msg)
                    });

                    var result = client.PostAsync("", form).Result;
                    if (result.IsSuccessStatusCode)
                    {
                        return true;
                    }

                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            return false;
        }
    }

    class CommEnvironment
    {
        public static string LineNotifyUrl = "https://notify-api.line.me/api/notify";
        public static string LineToken = "";

    }

首先先開始做注入的動作先優化

接者來包裝HttpClinet前先定義interface

    public interface IHttpClientProvider
    {
        HttpResponseMessage Post(string requestUri, FormUrlEncodedContent content);
    }
    public class HttpClientProvider : IHttpClientProvider
    {
        private readonly HttpClient _httpClinet;

        public HttpClientProvider(HttpClient httpClinet)
        {
            _httpClinet = httpClinet;
            _httpClinet.DefaultRequestHeaders.Add("Authorization",
            "Bearer " + CommEnvironment.LineToken);
        }

        public HttpResponseMessage Post(string request, FormUrlEncodedContent content)
        {
            return _httpClinet.PostAsync(request, content).Result;
        }
    }

最後加入做注入的部分 

    public class LineNotifyService
    {
        private readonly IHttpClientProvider _httpClientProvider;

        public LineNotifyService(IHttpClientProvider httpClientProvider)
        {
            _httpClientProvider = httpClientProvider;
        }

        public bool Send(string msg)
        {
            try
            {
              
                var form = new FormUrlEncodedContent(new[]
                {
                        new KeyValuePair<string, string>("message", msg)
                });

                var result = _httpClientProvider.Post(CommEnvironment.LineNotifyUrl, form);
                if (result.IsSuccessStatusCode)
                {
                    return true;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            return false;
        }
    }

最後針對HttpClinet隔離測試,測試Service層是否回傳True,依據需求情境去測試成功和失敗。

    public class LineNotifyServiceTest
    {
        private IHttpClientProvider _httpClientProvider;
        [SetUp]
        public void Setup()
        {
            _httpClientProvider = Substitute.For<IHttpClientProvider>();
        }

        [Test]
        public void LineNotifySend_Ok_ReturnTrue()
        {
            // Arrange
            string message = "";
            var expectedResponse = new HttpResponseMessage(HttpStatusCode.OK);
            httpClientProvider.Post(Arg.Any<string>(),Arg.Any<FormUrlEncodedContent>()).Returns(expectedResponse);
            // act
            LineNotifyService lineNotifyService = new LineNotifyService(_httpClientProvider);
            // Assert
            Assert.IsTrue(lineNotifyService.Send(message));
        }
        [Test]
        public void LineNotifySend_BadRequest_ReturnFalse()
        {
            // Arrange
            string message = "";
            var expectedResponse = new HttpResponseMessage(HttpStatusCode.BadRequest);
            _httpClientProvider.Post(Arg.Any<string>(), Arg.Any<FormUrlEncodedContent>()).Returns(expectedResponse);
            // act
            LineNotifyService lineNotifyService = new LineNotifyService(_httpClientProvider);
            // Assert
            Assert.IsFalse(lineNotifyService.Send(message));
        }
    }




這個方式是筆者常用的測試技巧,不過筆者覺得還可以優化,在這邊就先列出這樣寫法。

 

元哥的筆記