什么是按以下方式可能出现的问题与单元测试ASP.NET MVC code? [英] What are the possible problems with unit testing ASP.NET MVC code in the following way?

查看:90
本文介绍了什么是按以下方式可能出现的问题与单元测试ASP.NET MVC code?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在寻找的路上单元测试中的的NuGetGallery 。我观察到,当控制器进行测试,服务类嘲笑。这对我来说很有意义,因为在测试控制器逻辑,我不想担心下面的架构层。使用这种方法一段时间后,我发现我是如何经常​​地跑来跑去修我嘲笑都在我的控制测试时,我的服务类改变。为了解决这个问题,在没有咨询的人是比我聪明,我开始写这样的测试(别担心,我还没有得到那么远):

I've been looking at the way unit testing is done in the NuGetGallery. I observed that when controllers are tested, service classes are mocked. This makes sense to me because while testing the controller logic, I didn't want to be worried about the architectural layers below. After using this approach for a while, I noticed how often I was running around fixing my mocks all over my controller tests when my service classes changed. To solve this problem, without consulting people that are smarter than me, I started writing tests like this (don't worry, I haven't gotten that far):

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");
    }
}

我想这将是集成测试,因为我用的是真实的数据库(我preFER的inmemory之一)。我通过把数据在我的数据库开始我的测试(每个测试在一个事务中运行,并在测试完成时被回滚)。然后,我打电话给我的控制器和我真的不在乎它是如何从数据库中检索数据(通过存储库或服务类)只是模型被发送到视图必须有我放进数据库又名我的说法纪录。这个方法的很酷的事情是,很多时候,我可以继续添加复杂的多个图层,而无需改变我的控制器测试:

I guess this would be integration testing because I am using a real database (I'd prefer an inmemory one). I begin my test by putting data in my database (each test runs in a transaction and is rolled back when the test completes). Then I call my controller and I really don't care how it retrieves the data from the database (via a repository or service class) just that the Model to be sent to the view must have the record I put into the database aka my assertion. The cool thing about this approach is that a lot of times I can continue to add more layers of complexity without having to change my controller tests:

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创建一个接口,并将其传递到我的控制器的构造函数。原因是1)我不打算模仿我PersonService和2)我不觉得我需要注入,因为我PersonController我的依赖,现在只需要依赖于一个类型PersonService的。

Now I realize I didn't create an interface for my PersonService and pass it into the constructor of my controller. The reason is 1) I don't plan to mock my PersonService and 2) I didn't feel I needed to inject my dependency since my PersonController for now only needs to depend on one type of PersonService.

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

I'm new at unit testing and I'm always happy to be shown that I'm wrong. Please point out why the way I'm testng my controllers could be a really bad idea (besides the obvious increase in the time my tests will take to run).

推荐答案

嗯。这里的几件事情交配。

Hmm. a few things here mate.

首先,它看起来像你想测试一个控制器方法。大:)

First, it looks like you're trying to test the a controller method. Great :)

因此​​,这意味着,任何控制器的需求,应该被嘲笑。这是因为

So this means, that anything the controller needs, should be mocked. This is because


  1. 您不想担心依赖内部发生了什么。

  2. 您可以验证依赖被称为/执行。

好了,让我们看看你做了什么,我会看看我是否能重构它,使其更多的测试。

Ok, so lets look at what you did and I'll see if i can refactor it to make it a bit more testable.

-REMEMBER-我测试的的控制器方法的,而不是东西控制器方法调用/取决于。

-REMEMBER- i'm testing the CONTROLLER METHOD, not the stuff the controller method calls/depends upon.

因此​​,这意味着我不关心服务实例或存储库实例(您决定要遵循的建筑曾经方式)。

So this means I don't care about the service instance or the repository instance (which ever architectural way you decide to follow).

请注意:我已经把事情变得简单,所以我剥夺很多废话了,等

NOTE: I've kept things simple, so i've stripped lots of crap out, etc.

首先,我们需要存储库的接口。这可以实现为内存回购,实体框架回购等。你会,看到为什么很快。

First, we need an interface for the repository. This can be implemented as a in-memory repo, an entity framework repo, etc.. You'll see why, soon.

public interface ILESRepository
{
    IQueryable<Person> GetAll();
}

控制器

下面,我们使用的接口。这意味着它真​​的很容易,真棒使用模拟 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);
    }
}

单元测试

确定 - 这里是神奇的钱拍的东西。
首先,我们创建了一些虚伪的人。和我一起在这里工作......我会告诉你,我们用这个在转瞬间。这只是一个无聊的,简单的列表你的 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 我的测试框架和起订量我的嘲笑。任何框架是好的,在这里。

Now we have the test itself. I'm using xUnit for my testing framework and moq for my mocking. Any framework is fine, here.

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());
    }
}

好吧,让我们来看看我做了什么。

Ok, lets look at what I did.

首先,我安排我的废话。我首先创建的 ILESRepository 的模拟。
后来我说:如果有谁调用了 GETALL&LT;&人GT;()方法,以及..不-really-打一个数据库或文件或任何..只是返回的人的名单,这在创建 FakePeople.GetSomeFakePeople()

First, I arrange my crap. I first create a mock of the ILESRepository. Then i say: If anyone ever calls the GetAll<Person>() method, well .. don't -really- hit a database or a file or whatever .. just return a list of people, which created in FakePeople.GetSomeFakePeople().

因此​​,这是在控制器中会发生什么......

So this is what would happen in the controller ...

var model = _repository.GetAll<Person>()
                       .FirstOrDefault(x => x.Id == id);

首先,我们要求我们的模拟击中 GETALL&LT;&人GT;()方法。我只是'设置它'返回的人的名单..所以后来我们有3个对象的列表。接下来,我们再3 对象这个名单上称之为 FirstOrDefault(...) ..它返回单个对象或为空,这取决于 ID 的值。

First, we ask our mock to hit the GetAll<Person>() method. I just 'set it up' to return a list of people .. so then we have a list of 3 Person objects. Next, we then call a FirstOrDefault(...) on this list of 3 Person objects .. which returns the single object or null, depending on what the value of id is.

田田!这就是钱拍:)

Tada! That's the money shot :)

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

Now back to the rest of the unit test.

我们然后我们断言。没什么难有。
对于加分,我验证,我们实际上已经叫 GETALL&LT;人&GT;()方法,在模拟..控制器的内指数方法。这是一个安全的通话,以确保我们的控制器逻辑(我们测试了)做的权利。

We Act and then we Assert. Nothing hard there. For bonus points, I verify that we've actually called the GetAll<Person>() method, on the mock .. inside the Controller's Index method. This is a safety call to make sure our controller logic (we're testing for) was done right.

有时候,你可能要检查坏情况下的,像坏数据传递的人。这意味着你可能永远不会得到模拟的方法(这是正确的),所以你验证,他们从不叫。

Sometimes, you might want to check for bad scenario's, like a person passed in bad data. This means you might never ever get to the mock methods (which is correct) so you verify that they were never called.

确定 - ?的问题,类

Ok - questions, class?

这篇关于什么是按以下方式可能出现的问题与单元测试ASP.NET MVC code?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆