对不起这个糟糕的头衔,我尽我所能。也许有更多想象力的人可以帮助我和这个头衔。
我是单元测试的新手,我有点迷茫。无论如何,我正在阅读“单位测量的艺术”。
我正在使用Entity Framework 6作为Web Api应用程序。在这个应用程序中我使用这种模式:
Api调用调用Repository的Service,它将数据返回到将数据返回给API的服务。
所以,我有很多这样的方法:
API:
public async Task<IHttpActionResult> GetUserById(Guid id)
{
try
{
UserService userService = new UserService();
return Ok(await userService.GetById(id));
}
catch(Exception ex)
{
return InternalServerError(ex);
}
}
服务:
public class UserService
{
private IUserRepository userRepo;
public IUserRepository userRepo { get => userRepo; set => userRepo = value }
public async Task<AspNetUser> GetById(Guid id)
{
if(id == Guid.Empty() || id == null)
return null;
return await Task.Run(() => userRepo.GetById(id));
}
}
回购:
public interface IUserRepository
{
Task<AspNetUser> GetById(Guid id);
}
public class UserRepository: IUserRepository
{
public async Task<AspNetUser> GetById(Guid id)
{
return DbSet.Where(i => i.Id == id).FirstOrDefault();
}
}
这可能有什么问题?什么可能出错?我应该测试什么?
单元测试的目标是单独测试代码的每个模块(通常等同于方法)。这只能通过松耦合实现。在现有代码中,您不使用DI(依赖注入)并在方法中实例化服务/存储库类。这使得单独测试此代码很困难,如果不是几乎不可能的话。编写单元测试通常会发现像这样的设计/代码缺陷。重构您的代码如下:
FirstOrDefaultAsync
(或可能SingleOrDefaultAsync
);Async
命名您的异步方法,这被认为是正确的约定。UserController.cs
private readonly IUserService userService;
public UserController(IUserService userService){
this.userService = userService;
}
public async Task<IHttpActionResult> GetUserById(Guid id)
{
try
{
return Ok(await userService.GetByIdAsync(id));
}
catch(Exception ex)
{
return InternalServerError(ex);
}
}
UserService.cs
public interface IUserService{
Task<AspNetUser> GetByIdAsync(Guid id);
}
public class UserService : IUserService
{
// you can leave MyDbContext unsealed or you can use an interface on this as well depending on your needs
private readonly MyDbContext dbContext;
public UserService(MyDbContext dbContext) {
this.dbContext = dbContext;
}
public Task<AspNetUser> GetByIdAsync(Guid id)
{
if(id == Guid.Empty())
return Task.FromResult(null as AspNetUser);
return dbContext.AspNetUsers.SingleOrDefaultAsync(user => user.Id == id);
}
}
完成此操作后,您可以对代码进行单元测试。您可以使用流行的假/模拟/替代框架并执行以下操作:
GetUserById
上创建具有自定义行为的模拟IUserService
实例,为您的api的GetByIdAsync
创建测试UserService::GetByIdAsync
的模拟为MyDbContext
方法创建测试,您可以提供AspNetUser
实例的集合并测试在调用方法时返回哪一个,或者甚至测试调用DbSet的表达式。您应该测试的内容取决于可能的输入和相应的预期结果。例如:将空GUID传入GetByIdAsync时会发生什么?在Task中返回null结果时,API会执行什么操作?等等
坦率地说,这可能不是这里的“最佳”答案,我正在投票给其他人。
因为这对于评论来说有点“长”所以我发布的答案是“什么”比伊戈尔所做的“如何”更好 - 那里有好东西!
在这方面,这将是一些通用的。
测试我所谓的“快乐路径”,即有效的GUID(在您的实例中)获得有效的东西。
也许是从字符串中有效的东西:
Guid g = new Guid("11223344-5566-7788-99AA-BBCCDDEEFF00");
测试无效的东西(例如不存在)
Guid g = new Guid(someinvalidthingthatparses);
新:
Guid g = Guid.NewGuid();
全0,即Guid.Empty
测试未启动的值
在一个完美的世界中,其中一些不应该采用被测试的方法,但如果他们这样做呢?它是如何反应的?
为了进一步推断,我离题但是如果你正在测试一些东西,例如传递整数为Int32
,可能只接受正值测试边界,即0,1,-1,Int32.MaxValue
,Int32.MinValue
Int32.MaxValue - 1
Int32.MinValue + 1
,开始思考(当你出现时)如何打破它。如果您稍后遇到故障情况,请为此添加单元测试。如果你传递的东西将在某个地方的回购中,也要考虑其限制 - 例如,日期最小值/最大值在sql与C#和日期与日期时间等方面有所不同.BeginDate / EndDate - 是包含还是不包含等等。
现在每个“FAILURE”点应该做什么,这是如何由调用者/ API代码处理的?如果抛出错误,它如何响应?