9

我有这个方法:

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

我需要为此创建一个单元测试。

我的第一个问题是:

  1. 我在测试什么?我是否只测试该方法是否返回列表?

  2. 如果是这样,我将如何进行测试?

这是我到目前为止所拥有的:

    [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?
        }
    }
4

4 回答 4

4

我也不是专家,而且我只做了一小段时间 TDD,所以用一勺盐来拿我在这个漫无边际的答案中写的东西:) 我相信其他人可以指出我是否真的做过严重的错误或将您指向错误的方向...

我不确定您的测试是否真的是单元测试,因为它正在执行多个依赖项。假设您运行此测试并从方法中抛出异常。这个异常是否来自

  • RepositoryHelper.GetTake2Repository()) 抛出?(依赖问题)
  • ProjectCrewsByProjectSpec 构造函数抛出?(依赖问题)
  • rep.GetList(spec) 抛出?剑道(它是剑道,对吧?)(依赖问题)
  • ToDataSourceResult() 抛出?(行为问题)

单元测试就是完全独立于它们的依赖项来测试事物,所以目前我想说它更像是一个集成测试,你并不真正关心系统如何交互,你只想确保对于给定的projectID、seasonId 和 episodeId 你会得到预期的结果——在这种情况下,真正测试的是rep.GetList()方法与 . ToDataSourceResult扩展。

现在集成测试非常有用,并且 100% 需要作为测试驱动方法的一部分,如果那是你真正想做的,那么你做对了。(我把它放进并期待回来;我拿回来?)

但是如果你想对这段代码进行单元测试(特别是,如果你想对你的类的GetProjectBySpec方法进行单元测试),你必须按照@jimmy_keen 提到的那样做并重构它,以便你可以测试 GetProjectBySpec 的行为。例如,这是我刚刚发明的一个特定行为,当然你的可能会有所不同:

  • 如果输入错误,则抛出 ArgumentException
  • 创建一个新的 ProjectCrewsByProjectSpec 对象
  • 调用 rep.GetList 并将 spec 传递给它
  • 返回一个非空的 DataSourceResult

为了能够测试 GetProjectBySpec 是否执行上述列表中的所有操作,您需要做的第一件事是重构它,以便它不会创建自己的依赖项 - 相反,您给它提供它需要的依赖项,通过依赖注入

当您通过接口注入时,DI 确实效果最好,因此在任何提供此方法的类中,您的该类的构造函数都应该采用例如IRepositoryHelper的实例并将其存储在私有只读成员中。它还应该采用IProjectCrewsByProjectSpecFactory的实例,您将使用它来创建规范。现在,既然您想测试 GetProjectBySpec 对这些依赖项的实际作用,那么您将使用Moq之类的模拟框架,除了下面的示例外,我不会在这里介绍它。

如果这些类目前都没有实现这样的接口,那么只需使用 Visual Studio 根据类定义为您提取接口定义。如果它们是您无法控制的第 3 方类,这可能会很棘手。

但是让我们假设您可以这样定义接口:(请容忍我从来没有 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.

希望能给你一些帮助……当然,你可以重构你的测试类来避免大量的模拟设置,并将它们都放在一个地方,以将代码行保持在最低限度。

于 2013-07-11T00:00:10.227 回答
3

单元测试通常(应该)测试类客户端所理解的合同。当您的代码的客户调用.GetProjectBySpec(1, 2, 3)时,他期望发生什么?单元测试应该回答这个问题:

当存储库中有 5 个项目(ABCDE)并且我GetProjectBySpec使用参数123调用时,我应该得到项目BC

在您的情况下,它可能部分取决于正在做什么//big query...。如果它是从存储库返回的结果的过滤/转换,这就是你应该测试的。

请注意,您可能需要更改一些内容才能使此测试隔离(与存储库/数据库):

  • RepositoryHelper.GetTake2Repository应该包装在接口中,作为依赖注入并稍后在单元测试中模拟
  • 如果new ProjectCrewsByProjectSpec创建复杂对象,您可能希望使用工厂来代替

当您模拟存储库时,您只需指示您的模拟在使用匹配spec参数调用它时返回一些预先已知的项目列表。然后你的单元测试可以验证返回的数据是否GetProjectBySpec符合你的期望。

于 2013-07-10T21:32:14.760 回答
1

我这样写测试:

  1. 通过代码为每条路径编写一个测试。
  2. 编写边界条件测试。例如:您的列表中的零个、一个或两个项目。参数错误等
  3. 写负面测试。这些是最难的,因为您可以编写无限数量的无用测试。一个很好的例子是检查不应该改变的东西没有改变。

祝你好运

于 2013-07-11T00:59:44.603 回答
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);          
        }
}
于 2013-08-14T18:45:26.137 回答