c# – 如何为返回列表的方法创建正确的单元测试?
我有这个方法:
public DataSourceResult GetProjectBySpec(int projectId,int seasonId,int episodeId) { using (var rep = RepositoryHelper.GetTake2Repository<ITake2RepositoryBase>()) { var spec = new ProjectCrewsByProjectSpec(projectId,seasonId,episodeId); var personList = rep.GetList<ProjectDGACrew>(spec).Select(p => new { //big query... .ToDataSourceResult(); return personList; } } 我需要为此创建一个单元测试. 我的第一个问题是: >我在测试什么?我是否仅测试该方法是否返回列表? 这是我到目前为止: [TestClass] public class CrewControllerTest { [TestMethod] public void GetProjectCrewsBySpecTest() { // arrange int projectId = 1; int seasonId = 2; int episodeId = 3; // act var crewController = new CrewController(); DataSourceResult dsr = crewController.GetProjectCrewsBySpec(1,2,3); // assert // what or how do I assert here? Am I just checking whether "dsr" is a list? How do I do that? } } 解决方法
我也不是专家,而且我只是做了一段时间的TDD,所以拿着我在这个漫无边际的答案中用一勺盐写的东西:)我相信其他人可以指出我是否真的做了什么糟糕的错误或指向错误的方向……
我不确定你的测试是否真的是一个单元测试,因为它正在运行多个依赖项.假设您运行此测试并获得抛出该方法的异常. > RepositoryHelper.GetTake2Repository()) 单元测试是关于完全隔离它们的依赖项测试的东西,所以目前我说它更像是一个集成测试,你不关心系统如何交互,你只是想确保给定的projectID,seasonId和episodeId你得到了预期的结果 – 在这种情况下,真正测试的是rep.GetList()方法和.ToDataSourceResult扩展. 现在集成测试是非常有用的,并且100%作为测试驱动方法的一部分需要,如果那是你真正想做的事情,那么你就是这样做的.(我把它放进去并期待回来;我做了得到回来?) 但是如果你想单元测试这个代码(具体来说,如果你想对你的类的GetProjectBySpec方法进行单元测试),你将不得不像@jimmy_keen那样提到并重构它,以便你可以测试GetProjectBySpec的行为. >如果输入错误,则抛出ArgumentException 为了能够测试GetProjectBySpec执行上面列表中的所有操作,您需要做的第一件事就是重构它以便它不会创建自己的依赖项 – 相反,您需要为它提供所需的依赖项,通过Dependency Injection. 当您通过Interface注入时,DI确实最有效,因此在任何类提供此方法时,该类的构造函数应该采用例如IRepositoryHelper的实例并将其存储在私有只读成员中.它还应该采用IProjectCrewsByProjectSpecFactory的实例,您可以使用它来创建规范.既然你想测试GetProjectBySpec实际上对这些依赖关系做了什么,那么你将使用一个模拟框架,如Moq,除了下面的例子,我不会在这里讨论. 如果这些类当前都没有实现这样的接口,那么只需使用Visual Studio根据类定义为您提取接口定义.如果他们是你无法控制的第三方课程,这可能会很棘手. 但是让我们假设您可以定义接口:(与我一起使用通用<>位,我从不100%开启,我相信比我聪明的人可以告诉你所有的“T”应该去……)下面的代码没有经过测试也没有检查错别字! public interface IRepositoryHelper<ProjectDGACrew> { IList<ProjectDGACrew> GetList(IProjectCrewsByProjectSpecFactory spec); } public interface IProjectCrewsByProjectSpecFactory { ProjectDGACrew Create(int projectId,int episodeId); } 您的代码最终会看起来像这样: //somewhere in your class definition private readonly IRepositoryHelper<T> repo; private readonly IProjectCrewsByProjectSpecFactory pfactory; //constructor public MyClass(IRepositoryHelper<ProjectDGACrew> repo,IProjectCrewsByProjectSpecFactory pfactory) { this.repo = repo; this.pfactory=pfactory; } //method to be tested public DataSourceResult GetProjectBySpec(int projectId,int episodeId) { var spec = pfactory.Create(projectId,episodeId); var personList = repo.GetList(spec).Select(p => new {//big query...}).ToDataSourceResult(); return personList; } 现在你有4种测试方法可供编写: [TestMethod] [ExepctedException(typeof(ArgumentException)] public void SUT_WhenInputIsBad_ThrowsArgumentException() { var sut = new MyClass(null,null); //don't care about our dependencies for this check sut.GetProjectBySpec(0,0); //or whatever is invalid input for you. //don't care about the return,only that the method throws. } [TestMethod] public void SUT_WhenInputIsGood_CreatesProjectCrewsByProjectSpec() { //create dependencies using Moq framework. var pf= new Mock<IProjectCrewsByProjectSpecFactory>(); var repo = new Mock<IRepository<ProjectDgaCrew>>(); //setup such that a call to pfactory.Create in the tested method will return nothing //because we actually don't care about the result - only that the Create method is called. pf.Setup(p=>p.Create(1,3)).Returns<ProjectDgaCrew>(new ProjectDgaCrew()); //setup the repo such that any call to GetList with any ProjectDgaCrew object returns an empty list //again we do not care about the result. //This mock dependency is only being used here //to stop an exception being thrown from the test method //you might want to refactor your behaviours //to specify an early exit from the function when the factory returns a null object for example. repo.Setup(r=>r.GetList(It.IsAny<ProjectDgaCrew>()).Returns<IList<ProjectDGACrew>>(new List<ProjectDgaCrew>()); //create our System under test,inject our mock objects: var sut = new MyClass(repo,pf.Object); //call the method: sut.GetProjectBySpec(1,3); //and verify that it did indeed call the factory.Create method. pf.Verify(p=>p.Create(1,3),"pf.Create was not called with 1,3"); } public void SUT_WhenInputIsGood_CallsRepoGetList(){} //you get the idea public void SUT_WhenInputIsGood_ReturnsNonNullDataSourceResult(){}//and so on. 希望能给你一些帮助……当然你可以重构你的测试类,以避免大量的模拟设置,并将它们放在一个地方,以保持代码行最小化. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |