我可以在这些类型的方法上测试什么?

问题描述 投票:-1回答:2

对不起这个糟糕的头衔,我尽我所能。也许有更多想象力的人可以帮助我和这个头衔。

我是单元测试的新手,我有点迷茫。无论如何,我正在阅读“单位测量的艺术”。

我正在使用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();
    }
}

这可能有什么问题?什么可能出错?我应该测试什么?

c# visual-studio entity-framework unit-testing entity-framework-6
2个回答
2
投票

单元测试的目标是单独测试代码的每个模块(通常等同于方法)。这只能通过松耦合实现。在现有代码中,您不使用DI(依赖注入)并在方法中实例化服务/存储库类。这使得单独测试此代码很困难,如果不是几乎不可能的话。编写单元测试通常会发现像这样的设计/代码缺陷。重构您的代码如下:


  1. 使用接口,您是在存储库中完成的,而不是在您的服务上,但实际上这是使用接口的最佳位置。
  2. 使用依赖注入。这将使测试更容易。
  3. 为什么在EF周围使用存储库模式? EF的DbSet是存储库模式的通用实现,DbContext是工作单元的实现。我强烈建议你不要试图在你自己的半烘焙抽象中重新包装这些,你最终会让自己更难以使用你自己的结构。
  4. 这不是使用async / await的正确方法。调用异步方法,如果不存在,则不要创建异步方法(这不适用于您要使用TPL的情况)。在你的情况下,你应该打电话给FirstOrDefaultAsync(或可能SingleOrDefaultAsync);
  5. 使用后缀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);
    }
}

完成此操作后,您可以对代码进行单元测试。您可以使用流行的假/模拟/替代框架并执行以下操作:

  1. 通过在GetUserById上创建具有自定义行为的模拟IUserService实例,为您的api的GetByIdAsync创建测试
  2. 通过创建UserService::GetByIdAsync的模拟为MyDbContext方法创建测试,您可以提供AspNetUser实例的集合并测试在调用方法时返回哪一个,或者甚至测试调用DbSet的表达式。

您应该测试的内容取决于可能的输入和相应的预期结果。例如:将空GUID传入GetByIdAsync时会发生什么?在Task中返回null结果时,API会执行什么操作?等等


1
投票

坦率地说,这可能不是这里的“最佳”答案,我正在投票给其他人。

因为这对于评论来说有点“长”所以我发布的答案是“什么”比伊戈尔所做的“如何”更好 - 那里有好东西!

在这方面,这将是一些通用的。

测试我所谓的“快乐路径”,即有效的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.MaxValueInt32.MinValue Int32.MaxValue - 1 Int32.MinValue + 1,开始思考(当你出现时)如何打破它。如果您稍后遇到故障情况,请为此添加单元测试。如果你传递的东西将在某个地方的回购中,也要考虑其限制 - 例如,日期最小值/最大值在sql与C#和日期与日期时间等方面有所不同.BeginDate / EndDate - 是包含还是不包含等等。

现在每个“FAILURE”点应该做什么,这是如何由调用者/ API代码处理的?如果抛出错误,它如何响应?

© www.soinside.com 2019 - 2024. All rights reserved.