繁体   English   中英

如何为返回列表的方法创建适当的单元测试?

[英]How do you create a proper unit test for a method that returns a list?

我有这个方法:

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

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

当有在库5个项目(A,B,C,d,e)和我请GetProjectBySpec与参数1,2,3,我应该得到项目BC

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

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

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

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

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

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

  • RepositoryHelper.GetTake2Repository())抛出? (Depencency问题)
  • ProjectCrewsByProjectSpec构造函数抛出? (依赖性问题)
  • rep.GetList(spec)投掷? 剑道(剑道,对吗?)(依赖问题)
  • ToDataSourceResult()抛出? (行为问题)

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

如今的集成测试是非常非常有用的,需要100%的测试驱动方法的一部分,如果这就是你真正想做的事情,那么你正在做有关的权利(我把这个预期回;没有我回来?)

但是如果你想单元测试这个代码(具体来说,如果你想对你的类的GetProjectBySpec方法进行单元测试),你将不得不像@jimmy_keen那样提到并重构它,以便你可以测试GetProjectBySpec的行为 例如,这是我刚刚发明的特定行为,当然你的行为可能有所不同:

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

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

当您通过Interface注入时,DI确实最有效,因此在任何类提供此方法时,该类的构造函数应该采用例如IRepositoryHelper的实例并将其存储在私有只读成员中。 它还应该采用IProjectCrewsByProjectSpecFactory的实例,您可以使用它来创建规范。 既然你想测试GetProjectBySpec实际上这些依赖关系了什么,那么你将使用一个模拟框架,比如Moq ,除了下面的例子,我不会在这里讨论。

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

但是让我们假设你可以定义一下接口:(跟我一起使用通用的<>位,我永远不会百分之百,我相信比我聪明的人可以告诉你所有“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.

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

我这样写测试:

  1. 通过代码为每条路径编写一个测试。
  2. 写一个边界条件的测试。 例如:列表中的零个,一个或两个项目。 参数不好等
  3. 写负面测试。 这些是最困难的,因为你可以编写无数次无用的测试。 一个很好的例子是检查不应更改的内容是否未更改。

祝好运

这是一种更简单的方法,您可以在其中测试数据。 修改你离开的地方。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM