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? } }
解决方法
我不确定你的测试是否真的是一个单元测试,因为它正在运行多个依赖项.假设您运行此测试并获得抛出该方法的异常.
这个例外是否来自
> RepositoryHelper.GetTake2Repository())
投掷? (Depencency问题)
> ProjectCrewsByProjectSpec构造函数抛出? (依赖性问题)
> rep.GetList(spec)投掷?剑道(它是
剑道,对吗?)(依赖问题)
> ToDataSourceResult()抛出? (行为问题)
单元测试是关于完全隔离它们的依赖项测试的东西,所以目前我说它更像是一个集成测试,你不关心系统如何交互,你只是想确保给定的projectID,seasonId和episodeId你得到了预期的结果 – 在这种情况下,真正测试的是rep.GetList()方法和.ToDataSourceResult扩展.
现在集成测试是非常有用的,并且100%作为测试驱动方法的一部分需要,如果那是你真正想做的事情,那么你就是这样做的.(我把它放进去并期待回来;我做了得到回来?)
但是如果你想单元测试这个代码(具体来说,如果你想对你的类的GetProjectBySpec方法进行单元测试),你将不得不像@jimmy_keen那样提到并重构它,以便你可以测试GetProjectBySpec的行为.
例如这是我刚刚发明的一种特定行为,当然你的行为可能有所不同:
>如果输入错误,则抛出ArgumentException
>创建一个新的ProjectCrewsByProjectSpec对象
>调用rep.GetList并将规范传递给它
>返回非null的DataSourceResult
为了能够测试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.
希望能给你一些帮助……当然你可以重构你的测试类,以避免大量的模拟设置,并将它们放在一个地方,以保持代码行最小化.