C# 带NUnit、NSubtitute的Web API的TDD
我仍然对一些TDD概念以及如何正确地实现它感到困惑。我正试图使用它实现一个新的项目使用Web API。我已经读了很多关于它的文章,一些文章建议NUnit作为一个测试框架,NSubstitute模拟存储库 我不明白的是,使用NSubstitute我们可以定义我们想要的预期结果,如果我们想验证代码逻辑,这是否有效 假设我有一个这样的控制器,带有C# 带NUnit、NSubtitute的Web API的TDD,c#,asp.net,unit-testing,asp.net-web-api,nsubstitute,C#,Asp.net,Unit Testing,Asp.net Web Api,Nsubstitute,我仍然对一些TDD概念以及如何正确地实现它感到困惑。我正试图使用它实现一个新的项目使用Web API。我已经读了很多关于它的文章,一些文章建议NUnit作为一个测试框架,NSubstitute模拟存储库 我不明白的是,使用NSubstitute我们可以定义我们想要的预期结果,如果我们想验证代码逻辑,这是否有效 假设我有一个这样的控制器,带有Put和Delete方法: [BasicAuthentication] public class ClientsController : BaseContro
Put
和Delete
方法:
[BasicAuthentication]
public class ClientsController : BaseController
{
// Dependency injection inputs new ClientsRepository
public ClientsController(IRepository<ContactIndex> clientRepo) : base(clientRepo) { }
[HttpPut]
public IHttpActionResult PutClient(string accountId, long clientId, [FromBody] ClientContent data, string userId = "", string deviceId = "", string deviceName = "")
{
var result = repository.UpdateItem(new CommonField()
{
AccountId = accountId,
DeviceId = deviceId,
DeviceName = deviceName,
UserId = userId
}, clientId, data);
if (result.Data == null)
{
return NotFound();
}
if (result.Data.Value != clientId)
{
return InternalServerError();
}
IResult<IDatabaseTable> updatedData = repository.GetItem(accountId, clientId);
if (updatedData.Error)
{
return InternalServerError();
}
return Ok(updatedData.Data);
}
[HttpDelete]
public IHttpActionResult DeleteClient(string accountId, long clientId, string userId = "", string deviceId = "")
{
var endResult = repository.DeleteItem(new CommonField()
{
AccountId = accountId,
DeviceId = deviceId,
DeviceName = string.Empty,
UserId = userId
}, clientId);
if (endResult.Error)
{
return InternalServerError();
}
if (endResult.Data <= 0)
{
return NotFound();
}
return Ok();
}
}
[基本认证]
公共类ClientsController:BaseController
{
//依赖项注入输入新客户端repository
公共客户端控制器(IRepository clientRepo):基(clientRepo){}
[HttpPut]
public IHttpActionResult PutClient(字符串accountId,长clientId,[FromBody]ClientContent数据,字符串userId=,字符串deviceId=,字符串deviceName=)
{
var result=repository.UpdateItem(新公共字段()
{
AccountId=AccountId,
DeviceId=DeviceId,
DeviceName=DeviceName,
UserId=UserId
},客户ID,数据);
如果(result.Data==null)
{
返回NotFound();
}
if(result.Data.Value!=clientId)
{
返回InternalServerError();
}
IResult updateData=repository.GetItem(accountId,clientId);
if(updatedata.Error)
{
返回InternalServerError();
}
返回Ok(UpdateData.Data);
}
[HttpDelete]
public IHttpActionResult DeleteClient(字符串accountId,long clientId,字符串userId=“”,字符串deviceId=“”)
{
var endResult=repository.DeleteItem(新公共字段()
{
AccountId=AccountId,
DeviceId=DeviceId,
DeviceName=string.Empty,
UserId=UserId
},clientId);
if(endResult.Error)
{
返回InternalServerError();
}
如果(endResult.Data首先,在TDD中编码时,必须尽可能使用最小的函数。大约三行代码(不包括括号和签名)该函数应该只有一个用途。例如:一个名为GetEncriptedData的函数应该调用另外两个方法GetData和EncryptData,而不是获取数据并对其进行加密。如果tdd做得很好,那么获得该结果应该不是一个问题。当函数太长时,测试毫无意义,因为它们无法真正覆盖所有逻辑。我的测试使用having-when-then逻辑。例如:having-initialisationa\u-when-when-when-b\u-then-should-become是测试的名称。
你会发现测试中有三块代码代表这三个部分。还有更多。在执行tdd时,你应该始终一次执行一个步骤。如果你希望你的函数返回2,那么做一个测试,验证它是否返回2,并让你的函数实际返回2。之后,你可以需要一些条件并在其他条件下测试它们测试用例和所有测试都应该在最后完成。TDD是一种完全不同的编码方式。你做了一个测试,它失败了,你做了必要的代码,所以它通过了,你做了另一个测试,它失败了……这是我的经验,我实现TDD的方式告诉我你错了。但这是我的观点。希望我能帮助你。如果您实际发布的e代码是测试与代码比率的真实反映,因此您似乎没有遵循TDD方法。其中一个核心概念是,您不会编写未经测试的代码。这意味着,作为一项基本规则,您需要对代码中的每个分支至少进行一次测试,否则就没有意义了在上,该分支已被写入
查看您的DeleteClient
方法有三个分支,因此该方法至少应该有三个测试(您只发布了两个)
您可以使用NSubtitute
将代码重定向到这些不同的路径,以便对它们进行测试。因此,要重定向到InternalError分支,您可以设置如下替换:
[TestFixture]
public class ClientsControllerTest
{
private ClientsController _baseController;
private IRepository<ContactIndex> clientsRepository;
private string accountId = "account_id";
private string userId = "user_id";
private long clientId = 123;
private CommonField commonField;
[SetUp]
public void SetUp()
{
clientsRepository = Substitute.For<IRepository<ContactIndex>>();
_baseController = new ClientsController(clientsRepository);
commonField = new CommonField()
{
AccountId = accountId,
DeviceId = string.Empty,
DeviceName = string.Empty,
UserId = userId
};
}
[Test]
public void PostClient_ContactNameNotExists_ReturnBadRequest()
{
// Arrange
var data = new ClientContent
{
shippingName = "TestShippingName 1",
shippingAddress1 = "TestShippingAdress 1"
};
clientsRepository.CreateItem(commonField, data)
.Returns(new Result<long>
{
Message = "Bad Request"
});
// Act
var result = _baseController.PostClient(accountId, data, userId);
// Asserts
Assert.IsInstanceOf<BadRequestErrorMessageResult>(result);
}
[Test]
public void PutClient_ClientNotExists_ReturnNotFound()
{
// Arrange
var data = new ClientContent
{
contactName = "TestContactName 1",
shippingName = "TestShippingName 1",
shippingAddress1 = "TestShippingAdress 1"
};
clientsRepository.UpdateItem(commonField, clientId, data)
.Returns(new Result<long?>
{
Message = "Data Not Found"
});
var result = _baseController.PutClient(accountId, clientId, data, userId);
Assert.IsInstanceOf<NotFoundResult>(result);
}
[Test]
public void PutClient_UpdateSucceed_ReturnOk()
{
// Arrange
var postedData = new ClientContent
{
contactName = "TestContactName 1",
shippingName = "TestShippingName 1",
shippingAddress1 = "TestShippingAdress 1"
};
var expectedResult = new ContactIndex() { id = 123 };
clientsRepository.UpdateItem(commonField, clientId, postedData)
.Returns(new Result<long?> (123)
{
Message = "Data Not Found"
});
clientsRepository.GetItem(accountId, clientId)
.Returns(new Result<ContactIndex>
(
expectedResult
));
// Act
var result = _baseController.PutClient(accountId, clientId, postedData, userId)
.ShouldBeOfType<OkNegotiatedContentResult<ContactIndex>>();
// Assert
result.Content.ShouldBe(expectedResult);
}
[Test]
public void DeleteClient_ClientNotExists_ReturnNotFound()
{
clientsRepository.Delete(accountId, userId, "", "", clientId)
.Returns(new Result<int>()
{
Message = ""
});
var result = _baseController.DeleteClient(accountId, clientId, userId);
Assert.IsInstanceOf<NotFoundResult>(result);
}
[Test]
public void DeleteClient_DeleteSucceed_ReturnOk()
{
clientsRepository.Delete(accountId, userId, "", "", clientId)
.Returns(new Result<int>(123)
{
Message = ""
});
var result = _baseController.DeleteClient(accountId, clientId, userId);
Assert.IsInstanceOf<OkResult>(result);
}
}
clientsRepository.Delete(Args.Any<int>(), Args.Any<int>(),
Args.Any<string>(), Args.Any<string>(),
Args.Any<int>())
.Returns(new Result<int>()
{
Error = SomeError;
});
我使用ReturnsInternalError
测试作为基础,因为在验证调用后,我希望尽快退出被测试的方法,在本例中,这是通过返回一个错误来实现的。所以你是说,如果我的方法中有3个以上的任务,它看起来就像“代码气味”然后呢?不知何故,我必须重构它。超过三行包括所有内容。甚至声明。真正的tdd不应该需要重构,因为一旦通过测试就应该可以了。如果代码是错误的,那么测试本身就是错误的。但正如我所说,tdd是一种极端而精确的编码方式。它不仅仅是拥有100%的cove愤怒。你有任何参考或项目的例子来更好地理解TDD吗?谷歌叔叔鲍勃TDD。你会得到你需要的所有信息;)啊,我明白了,我现在明白了。谢谢你对我的问题的广泛解释。
clientsRepository.Delete(Args.Any<int>(), Args.Any<int>(),
Args.Any<string>(), Args.Any<string>(),
Args.Any<int>())
.Returns(new Result<int>()
{
Error = SomeError;
});
clientsRepository.Received().Delete(accountId, userId, "", "", clientId);