C#单元测试中向模拟数据库添加数据的方法

C#单元测试中向模拟数据库添加数据的方法,c#,unit-testing,mocking,tdd,C#,Unit Testing,Mocking,Tdd,这篇文章更像是讨论的开始,因为我对单元测试和TDD有些陌生 我目前正在为一个与多个数据库交互的.NET进程编写一些单元测试,并正在使用模拟数据库上下文,试图在测试中覆盖不同的边缘情况,验证程序本身的异常处理,等等。也就是说,我的一些单元测试使用有效数据,而其他的则不使用 我正在寻找在向模拟数据库上下文中添加有效/虚假数据时建议的最佳实践方面的反馈。我见过很多人这样做(例如,实现存储库模式、将模拟数据添加到.csv文件并使其成为项目的一部分,等等) 我目前正在考虑使用存储库模式将Survey对象添

这篇文章更像是讨论的开始,因为我对单元测试和TDD有些陌生

我目前正在为一个与多个数据库交互的.NET进程编写一些单元测试,并正在使用模拟数据库上下文,试图在测试中覆盖不同的边缘情况,验证程序本身的异常处理,等等。也就是说,我的一些单元测试使用有效数据,而其他的则不使用

我正在寻找在向模拟数据库上下文中添加有效/虚假数据时建议的最佳实践方面的反馈。我见过很多人这样做(例如,实现存储库模式、将模拟数据添加到.csv文件并使其成为项目的一部分,等等)

我目前正在考虑使用存储库模式将
Survey
对象添加到目标数据库中的
Surveys
表中

首先,我有一个界面:

公共接口是surveyrepository
{
IQueryable SurveySeries{get;}
}
这是为单元测试所需的模拟假/有效数据存储库实现的

class FakeSurveyRepository:ISurveyRepository
{
私有静态IQueryable fakeSurveySeries=新列表{
新调查{id=1,SurveyName=“NotValid1”,SurveyData=“fake”},
新调查{id=2,SurveyName=“NotValid2”,SurveyData=“super fake”},
.........,
新调查{id=10,SurveyName=“NotValid10”,SurveyData=“the fakest”}
}.AsQueryable();
公共可查询调查
{ 
获取{return fakeSurveySeries;}
}
}
//RealSurveyRepository:ISurveyRepository与此类似,但数据“良好”
然后,通过向构造函数中的序列传递引用,我有一个类来为假/有效数据使用这些数据:

public class SurveySeriesProcessor
{
   private ISurveyRepository surveyRepository;

   public SurveySeriesProcessor( ISurveyRepository surveyRepository )
   {
       this.surveyRepository = surveyRepository;
   }

   public IQueryable<Survey> GetSurveys()
   {
      return surveyRepository.SurveySeries
   }
} 
public class SurveySeriesProcessor
{
私人调查报告;
公共调查报告处理人(ISURVEY报告调查报告)
{
this.surveyRepository=surveyRepository;
}
公共IQueryable GetSurveys()
{
返回调查repository.SurveySeries
}
} 
然后可以在我的测试中使用这些对象,例如:

[TestClass]
public class SurveyTests
{
    [TestMethod]
    WhenInvalidSurveysFound_SurveyCopierThrowsInvalidSurveyDataErrorForEach()
    {
       // create mocking DB context and add fake data
       var contextFactory = new ContextFactory( ContextType.Mocking );
       var surveySeriesProcessor = new SurveySeriesProcessor( new FakeSurveyRepository() );

       foreach(Survey surveyRecord in surveySeriesProcessor.GetSurveys() )
       {
          contextFactory.TargetDBContext.Surveys.AddObject( surveyRecord );
       }
       // instantiate object being tested and run it against fake test data
       var testSurveyCopier = new SurveyCopier( contextFactory );
       testSurveyCopier.Start();
       // test behavior
       List<ErrorMessage> errors = testSurveyCopier.ErrorMessages;
       errors.Count.ShouldEqual( surveySeriesProcessor.GetSurveys().Count );
       foreach(ErrorMessage errMsg in errors)
       {
          errMsg.ErrorCode.ShouldEqual(ErrorMessage.ErrorMessageCode.InvalidSurveyData);
       }
    }
}
[TestClass]
公共类调查
{
[测试方法]
当发现无效调查时\u SurveyCopierThrowsInvalidSurveyDataErrorForEach()
{
//创建模拟数据库上下文并添加假数据
var contextFactory=新的contextFactory(ContextType.Mocking);
var surveySeriesProcessor=新的surveySeriesProcessor(新的FakeSurveyRepository());
foreach(surveySeriesProcessor.GetSurveys()中的SurveySeriesRecord)
{
contextFactory.TargetDBContext.Surveys.AddObject(surveyRecord);
}
//实例化正在测试的对象,并针对假测试数据运行它
var testSurveyCopier=新的SurveyCopier(contextFactory);
testSurveyCopier.Start();
//测试行为
列出错误=testSurveyCopier.ErrorMessages;
errors.Count.ShouldEqual(surveySeriesProcessor.GetSurveys().Count);
foreach(错误消息errMsg in errors)
{
errMsg.ErrorCode.ShouldEqual(ErrorMessage.ErrorMessageCode.InvalidSurveyData);
}
}
}
注意:我意识到,在提供的示例代码中,我不一定需要让实现
ISurveyRepository
的类将序列作为
IQueryable
返回(它们很可能是
List
)。但是,我将在将来扩展接口和这些类的功能,以根据添加到LINQ查询中的某些条件过滤出假/有效序列,这就是为什么我让存储库实现
IQueryable
。这是模拟代码,旨在传达我所想的基本原则

考虑到所有这些,我想问的是:

  • 你对我在这种情况下可以采取的替代方法有什么建议吗
  • 你过去用过什么方法?你喜欢/不喜欢这些方法的哪些方面?你发现哪一个最容易维护
  • 鉴于我所发布的内容,您是否注意到我的单元测试一般方法中的缺陷?有时,我觉得我编写的单元测试试图覆盖太多的领域,而不是简洁、优雅和中肯

  • 这是一次公开的讨论。请记住,这是我写的第一套单元测试(不过,我已经阅读了大量关于这个主题的文献)。

    我认为你的思路很好

    就个人而言,在同样的情况下,如果我处理的是存储库样式的模式

    public interface IRepository<T>
    {
        IEnumerable<T> GetAll();
    }
    
    
    public class PonyRepository : IRepository<Pony>
    {
        IEnumerable<Pony> GetAll();
    }
    
    公共接口IRepository
    {
    IEnumerable GetAll();
    }
    公共类PonyRepository:IRepository
    {
    IEnumerable GetAll();
    }
    
    为了实际提供所需的数据,我通常创建一个TestObjects或TestFakes类来按需提供所需的数据

    public class FakeStuff
    {
         public static IEnumerable<Pony> JustSomeGenericPonies(int numberOfPonies)
         {
            // return just some basic list
             return new List<Pony>{new Pony{Colour = "Brown", Awesomeness = AwesomenessLevel.Max}};
    
             // or could equally just go bananas in here and do stuff like...
             var lOfP = new List<Pony>();
             for(int i = 0; i < numberOfPonies; i++)
             {
                 var p = new Pony();
                 if(i % 2 == 0) 
                 {
                     p.Colour = "Gray";
                 }
                 else
                 {
                     p.Colour = "Orange"; 
                 }
    
                 lOfP.Add(p);
             }
    
             return lOfP;
         }
    }
    
    public-class-FakeStuff
    {
    公共静态IEnumerable JustSomeGenericPonies(int numberOfPonies)
    {
    //只返回一些基本列表
    返回新列表{new Pony{color=“Brown”,Awesomeness=AwesomenessLevel.Max};
    //或者也可以在这里发疯,做一些像。。。
    var lOfP=新列表();
    for(int i=0;i
    并以此进行测试:

    [Test]
    public void Hello_I_Want_to_test_ponies()
    {
        Mock<IRepository<Pony> _mockPonyRepo = new Mock<IRepository<Pony>>();
        _mockPonyRepo.SetUp(m => m.GetAll()).Returns(FakeStuff.JustSomeGenericPonies(50));
    
        // Do things that test using the repository
    }
    
    [测试]
    公共无效你好我想测试小马()
    {
    Mock m.GetAll()).Returns(FakeStuff.JustSomeGenericPonies(50));
    //使用存储库进行测试
    }
    
    所以,通过保留假数据,这提供了假数据的可重用性
    [Test]
    public void Hello_I_Want_to_test_ponies()
    {
        Mock<IRepository<Pony> _mockPonyRepo = new Mock<IRepository<Pony>>();
        _mockPonyRepo.SetUp(m => m.GetAll()).Returns(FakeStuff.JustSomeGenericPonies(50));
    
        // Do things that test using the repository
    }
    
    public class FakePonyRepositoryThatOnlyReturnsBrownPonies : IRepository<Pony>
    {
        private List<Pony> _verySpecificAndNotReusableListOfOnlyBrownPonies = new List....
    
        public IEnumerable<Pony> GetAll()
        {
            return _verySpecificAndNotReusableListOfOnlyBrownPonies;
        }
    }
    
    public class FakePonyRepositoryThatThrowsExceptionFromGetAll : IRepository<Pony>
    {
        public IEnumerable<Pony> GetAll()
        {
            throw new OmgNoPoniesException();
        }
    }