單元測試
最近在思考怎麼把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));
}
}
這個方式是筆者常用的測試技巧,不過筆者覺得還可以優化,在這邊就先列出這樣寫法。
元哥的筆記