Asp.net mvc 3 以以下方式对ASP.NET MVC代码进行单元测试可能存在哪些问题?
我一直在研究单元测试在中的实现方式。我观察到,当控制器被测试时,服务类被模拟。这对我来说很有意义,因为在测试控制器逻辑时,我不想担心下面的架构层。在使用这种方法一段时间后,我注意到,当我的服务类发生变化时,我经常在控制器测试中四处修复模拟。为了解决这个问题,在没有咨询比我聪明的人的情况下,我开始编写这样的测试(别担心,我还没走那么远): 现在我意识到我没有为PersonService创建接口并将其传递给控制器的构造函数。原因是1)我不打算嘲笑我的PersonService,2)我觉得我不需要注入依赖项,因为我的PersonController目前只需要依赖一种类型的PersonServiceAsp.net mvc 3 以以下方式对ASP.NET MVC代码进行单元测试可能存在哪些问题?,asp.net-mvc-3,unit-testing,Asp.net Mvc 3,Unit Testing,我一直在研究单元测试在中的实现方式。我观察到,当控制器被测试时,服务类被模拟。这对我来说很有意义,因为在测试控制器逻辑时,我不想担心下面的架构层。在使用这种方法一段时间后,我注意到,当我的服务类发生变化时,我经常在控制器测试中四处修复模拟。为了解决这个问题,在没有咨询比我聪明的人的情况下,我开始编写这样的测试(别担心,我还没走那么远): 现在我意识到我没有为PersonService创建接口并将其传递给控制器的构造函数。原因是1)我不打算嘲笑我的PersonService,2)我觉得我不需要注入
我是单元测试新手,我总是很高兴被证明我错了。请指出为什么我测试控制器的方式可能是一个非常糟糕的主意(除了测试运行时间明显增加之外) 即使您不打算模拟接口,我也强烈建议您不要通过在构造函数中创建对象来隐藏对象的真正依赖关系,您正在违反单一责任原则,并且正在编写不可测试的代码 <> P>编写测试时要考虑的最重要的事情是:“编写测试没有神奇的关键”。有很多工具可以帮助您编写测试,但真正的工作应该放在编写可测试代码上,而不是试图破解我们现有的代码来编写一个通常以集成测试而不是单元测试结束的测试 在构造函数中创建新对象是代码不可测试的第一个重要信号之一 当我开始编写测试时,这些链接帮助了我很多,让我告诉你,在你开始编写测试后,这将成为你日常工作的一部分,你会喜欢编写测试的好处。我无法想象自己在没有测试的情况下编写代码 清洁代码指南(用于谷歌): 要获得更多信息,请阅读以下内容: 观看这段由Misko Hevery播放的视频 编辑: 这篇来自Martin Fowler的文章解释了经典和模拟TDD方法之间的区别 作为总结:
- 经典的TDD方法:这意味着您可以在不创建替代或双重(mock、stub、dummie)的情况下测试所有内容,但web服务或数据库等外部服务除外。经典的测试人员只对外部服务使用双工
- 好处:当您进行测试时,实际上是在测试应用程序的布线逻辑和逻辑本身(不是孤立地测试)
- 缺点:如果发生错误,您可能会看到数百个测试失败,并且很难找到负责的代码
- Mockist TDD方法:采用Mockist方法的人将隔离测试所有代码,因为他们将为每个依赖项创建双倍
- 好处:您正在隔离测试应用程序的每个部分。如果发生错误,您确切地知道它发生在哪里,因为只有几个测试会失败,理想情况下只有一个测试会失败
- 缺点:您必须将所有依赖项加倍,这会使测试更加困难,但您可以使用AutoFixture之类的工具自动为依赖项创建加倍
有一些不利因素 首先,当您有一个依赖于外部组件(如实时数据库)的测试时,该测试不再是真正可预测的。它可能由于多种原因而失败——网络中断、数据库帐户密码更改、缺少一些DLL等。因此,当测试突然失败时,您无法立即确定缺陷所在。这是一个数据库问题吗?你班上有什么棘手的错误 当你知道哪项考试失败就可以立即回答这个问题时,你就拥有了令人羡慕的能力 其次,如果存在数据库问题,那么依赖它的所有测试都将立即失败。这可能没有那么严重,因为你可能知道原因是什么,但我保证这会让你慢下来检查每一个。广泛的失败可以掩盖真正的问题,因为您不想在50个测试中的每一个上都看到异常 我知道你想知道除了执行时间以外的其他因素,但这确实很重要。您希望尽可能频繁地运行测试,而较长的运行时间不鼓励这样做 我有两个项目:一个是在10秒内运行600多个测试,另一个是在50秒内运行40多个测试(这个项目实际上是有意与数据库对话)。我在开发时更频繁地运行更快的测试套件。猜猜我觉得哪一个更容易合作
所有这些都表明,测试外部组件是有价值的。只是当你进行单元测试的时候。集成测试更脆弱、更慢。这使得它们更加昂贵。在单元测试中访问数据库会产生以下后果:
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");
}
}
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);
}
}
public interface ILESRepository
{
IQueryable<Person> GetAll();
}
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);
}
}
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" },
}
}
}
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());
}
}
var model = _repository.GetAll<Person>()
.FirstOrDefault(x => x.Id == id);