gpt4 book ai didi

asp.net-mvc-3 - 通过以下方式对 ASP.NET MVC 代码进行单元测试可能会出现哪些问题?

转载 作者:行者123 更新时间:2023-12-04 23:06:47 25 4
gpt4 key购买 nike

我一直在查看 the NuGetGallery 中的单元测试方式。 .我观察到当 Controller 被测试时,服务类被模拟了。这对我来说很有意义,因为在测试 Controller 逻辑时,我不想担心下面的架构层。在使用这种方法一段时间后,我注意到当我的服务类发生变化时,我经常在我的 Controller 测试中修复我的模拟。为了解决这个问题,在没有咨询比我聪明的人的情况下,我开始编写这样的测试(别担心,我还没有做到这一点):

public class PersonController : Controller
{
private readonly LESRepository _repository;

public PersonController(LESRepository repository)
{
_repository = repository;
}

public ActionResult Index(int id)
{
var model = _repository.GetAll<Person>()
.FirstOrDefault(x => x.Id == id);

var viewModel = new VMPerson(model);
return View(viewModel);
}
}

public class PersonControllerTests
{
public void can_get_person()
{
var person = _helper.CreatePerson(username: "John");
var controller = new PersonController(_repository);
controller.FakeOutContext();

var result = (ViewResult)controller.Index(person.Id);
var model = (VMPerson)result.Model;
Assert.IsTrue(model.Person.Username == "John");
}
}

我想这将是集成测试,因为我使用的是真实数据库(我更喜欢内存数据库)。我通过将数据放入我的数据库来开始我的测试(每个测试都在一个事务中运行,并在测试完成时回滚)。然后我调用我的 Controller ,我真的不在乎它如何从数据库中检索数据(通过存储库或服务类),只是要发送到 View 的模型必须具有我放入数据库的记录,也就是我的断言.这种方法很酷的一点是,很多时候我可以继续添加更多的复杂层,而无需更改我的 Controller 测试:
public class PersonController : Controller
{
private readonly LESRepository _repository;
private readonly PersonService _personService;

public PersonController(LESRepository repository)
{
_repository = repository;
_personService = new PersonService(_repository);
}

public ActionResult Index(int id)
{
var model = _personService.GetActivePerson(id);
if(model == null)
return PersonNotFoundResult();

var viewModel = new VMPerson(model);
return View(viewModel);
}
}

现在我意识到我没有为我的 PersonService 创建一个接口(interface)并将它传递给我的 Controller 的构造函数。原因是 1)我不打算模拟我的 PersonService 和 2)我觉得我不需要注入(inject)我的依赖项,因为我的 PersonController 现在只需要依赖一种类型的 PersonService。

我是单元测试的新手,我总是很高兴被证明我错了。请指出为什么我测试 Controller 的方式可能是一个非常糟糕的主意(除了我的测试运行时间明显增加)。

最佳答案

唔。伙计,这里有几件事。

首先,看起来您正在尝试测试 Controller 方法。伟大的 :)

所以这意味着, Controller 需要的任何东西都应该被模拟。这是因为

  • 您不想担心在该依赖项中会发生什么。
  • 您可以验证依赖项是否被调用/执行。

  • 好的,让我们看看你做了什么,我会看看我是否可以重构它以使其更具可测试性。

    -记住-我正在测试 Controller 方法,而不是 Controller 方法调用/依赖的东西。

    所以这意味着我不关心服务实例或存储库实例(无论您决定遵循哪种架构方式)。

    注意:我把事情简单化了,所以我去掉了很多废话等等。

    界面

    首先,我们需要一个存储库的接口(interface)。这可以实现为内存中的存储库、 Entity Framework 存储库等。您很快就会明白为什么。
    public interface ILESRepository
    {
    IQueryable<Person> GetAll();
    }

    Controller

    在这里,我们使用接口(interface)。这意味着使用模拟 IRepository 真的很简单而且很棒。或者一个真实的例子。
    public class PersonController : Controller
    {
    private readonly ILESRepository _repository;

    public PersonController(ILESRepository repository)
    {
    if (repository == null)
    {
    throw new ArgumentNullException("repository");
    }
    _repository = repository;
    }

    public ActionResult Index(int id)
    {
    var model = _repository.GetAll<Person>()
    .FirstOrDefault(x => x.Id == id);

    var viewModel = new VMPerson(model);
    return View(viewModel);
    }
    }

    单元测试

    好的 - 这是神奇的金钱射击的东西。
    首先,我们创建了一些 Fake People。在这里和我一起工作......我会告诉你我们在哪里使用它。这只是您的 POCO 的一个无聊、简单的列表。的。
    public static class FakePeople()
    {
    public static IList<Person> GetSomeFakePeople()
    {
    return new List<Person>
    {
    new Person { Id = 1, Name = "John" },
    new Person { Id = 2, Name = "Fred" },
    new Person { Id = 3, Name = "Sally" },
    }
    }
    }

    现在我们有了测试本身。我正在使用 xUnit对于我的测试框架和 moq为了我的 mock 。任何框架都可以,在这里。
    public class PersonControllerTests
    {
    [Fact]
    public void GivenAListOfPeople_Index_Returns1Person()
    {
    // Arrange.
    var mockRepository = new Mock<ILESRepository>();
    mockRepository.Setup(x => x.GetAll<Person>())
    .Returns(
    FakePeople.GetSomeFakePeople()
    .AsQueryable);
    var controller = new PersonController(mockRepository);
    controller.FakeOutContext();

    // Act.
    var result = controller.Index(person.Id) as ViewResult;

    // Assert.
    Assert.NotNull(result);
    var model = result.Model as VMPerson;
    Assert.NotNull(model);
    Assert.Equal(1, model.Person.Id);
    Assert.Equal("John", model.Person.Username);

    // Make sure we actually called the GetAll<Person>() method on our mock.
    mockRepository.Verify(x => x.GetAll<Person>(), Times.Once());
    }
    }

    好的,让我们看看我做了什么。

    首先,我整理我的废话。我首先创建了 ILESRepository 的模拟。 .
    然后我说:如果有人调用 GetAll<Person>()方法,好吧 .. 不要 - 真的 - 访问数据库或文件或其他任何东西 .. 只需返回在 FakePeople.GetSomeFakePeople() 中创建的人员列表.

    所以这就是 Controller 中会发生的事情......
    var model = _repository.GetAll<Person>()
    .FirstOrDefault(x => x.Id == id);

    首先,我们要求我们的模拟点击 GetAll<Person>()方法。我只是“设置”以返回人员列表......所以我们有一个列表 3 Person对象。接下来,我们再调用 FirstOrDefault(...)在此列表中 3 Person objects .. 返回单个对象或 null,具体取决于 id 的值。是。

    多田!这就是金钱射击:)

    现在回到单元测试的其余部分。

    我们 Act然后我们 Assert .那里没什么难的。
    对于奖励积分,我 verify我们实际上调用了 GetAll<Person>()方法,在 Controller 的 Index 内的模拟 ..方法。这是一个安全调用,以确保我们的 Controller 逻辑(我们正在测试)正确完成。

    有时,您可能想要检查不良情况,例如有人传入了不良数据。这意味着您可能永远无法使用模拟方法(这是正确的),所以您 verify他们从未被召唤。

    好的 - 问题,上课?

    关于asp.net-mvc-3 - 通过以下方式对 ASP.NET MVC 代码进行单元测试可能会出现哪些问题?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10359080/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com