我有这个方法:
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?
}
}
答案 0 :(得分:3)
单元测试通常(应该)测试合同客户所理解的合同。当您的代码客户端调用.GetProjectBySpec(1, 2, 3)
时,他期望发生什么?单元测试应该回答这个问题:
当存储库中有5个项目时( A , B , C , D , E) )我用参数 1 , 2 , 3 调用
GetProjectBySpec
,我应该得到项目 < em> B 和 C
在你的情况下,它可能取决于//big query...
部分的内容。如果它是从存储库返回的结果的过滤/转换,那么您应该测试它。
请注意,为了使此测试与存储库/数据库隔离,您可能需要更改一些内容:
RepositoryHelper.GetTake2Repository
应该包含在接口中,injected作为依赖项,mocked稍后再进行单元测试new ProjectCrewsByProjectSpec
创建了复杂对象,您可能希望使用factory而不是如果您要对存储库进行模拟,则只需指示模拟器在使用匹配的spec
参数调用时返回一些预先知道的项目列表。然后,您的单元测试可以验证从GetProjectBySpec
返回的数据是否符合您的期望。
答案 1 :(得分:3)
我也不是专家,而且我只是做了一小段时间的TDD,所以在这个漫无边际的答案中用一勺盐写下来:)我相信其他人可以指出我是否已经犯了任何非常糟糕的错误,或指向错误的方向......
我不确定你的测试是否真的是一个单元测试,因为它正在运行多个依赖项。假设您运行此测试并获得抛出该方法的异常。 这个例外是否来自
单元测试完全是为了测试与依赖项完全隔离的东西,所以目前我说它更像是一个集成测试,你根本不关心 系统如何交互,你只想确保对于给定的projectID,seasonId和episodeId你得到了预期的结果 - 在这种情况下,真正的测试是 rep.GetList()方法与。 ToDataSourceResult 扩展名一起使用。
现在,集成测试非常有用,并且作为测试驱动方法的一部分需要100%,如果这是你真正想做的事情,那么你就是在做正确的事。(我把这个 in并期待 回来;我那回来了吗?)
但是如果你想单元测试这个代码(具体来说,如果你想对你的类的 GetProjectBySpec 方法进行单元测试),你将不得不像@jimmy_keen那样提到并重构它,以便您可以测试GetProjectBySpec的行为。 例如这是我刚刚发明的一种特定行为,当然你的可能会有所不同:
为了能够测试GetProjectBySpec执行上面列表中的所有操作,您需要做的第一件事就是重构它以便它不会创建自己的依赖项 - 相反,您可以为它提供依赖项它需要,通过Dependency Injection。
当您通过Interface注入时,DI确实最有效,因此在任何类提供此方法时,该类的构造函数应该采用例如 IRepositoryHelper 的实例并将其存储在私有只读成员中。它还应该使用 IProjectCrewsByProjectSpecFactory 的实例来创建您的规范。既然你想用这些依赖关系来测试GetProjectBySpec实际做什么那么你将使用像Moq这样的模拟框架,除了下面的例子,我不会在这里讨论。如果这些类当前都没有实现这样的接口,那么只需使用Visual Studio根据类定义为您提取接口定义。如果他们是你无法控制的第三方课程,这可能会很棘手。
但是让我们假设您可以定义接口:(跟我一起使用通用的&lt;&gt;位,我从不100%打开,我相信比我聪明的人可以告诉你哪里所有的“T”应该去......)下面的代码没有经过测试也没有检查拼写错误!
public interface IRepositoryHelper<ProjectDGACrew>
{
IList<ProjectDGACrew> GetList(IProjectCrewsByProjectSpecFactory spec);
}
public interface IProjectCrewsByProjectSpecFactory
{
ProjectDGACrew Create(int projectId, int seasonId, 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 seasonId, int episodeId)
{
var spec = pfactory.Create(projectId, seasonId, 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,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,2,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,2,3);
//and verify that it did indeed call the factory.Create method.
pf.Verify(p=>p.Create(1,2,3),"pf.Create was not called with 1,2,3");
}
public void SUT_WhenInputIsGood_CallsRepoGetList(){} //you get the idea
public void SUT_WhenInputIsGood_ReturnsNonNullDataSourceResult(){}//and so on.
希望能给你一些帮助......当然你可以重构你的测试类,以避免大量的模拟设置,并将它们全部放在一个地方,以保持代码行最小化。
答案 2 :(得分:0)
我用这种方式写测试:
答案 3 :(得分:0)
这是一种更简单的方法,您可以在其中测试数据。修改你离开的地方。
[TestClass]
public class CrewControllerTest
{
[TestMethod]
public void GetProjectCrewsBySpecTest()
{
// arrange
const String ExpectedOutput = "";
int projectId = 1;
int seasonId = 2;
int episodeId = 3;
// act
var crewController = new CrewController();
var resultList= crewController.GetProjectCrewsBySpec(1, 2,3) as DataSourceResult;
var someInsideData = resultlist.FirstOrDefault().GetType().GetProperty("PropertyName").GetValue(resultList.FirstOrDefault(),null);
// assert
Assert.AreEqual(someInsideData , ExpectedOutput);
}
}