如何编写数据库调用的unit testing
我接近一个新项目的开始,并且第一次尝试将unit testing包括在我的一个项目中(gasp!)。
我在devise一些unit testing时遇到了麻烦。 我有几个方法已经很容易testing(传入两个值,并检查预期的输出)。 我有其他部分的代码正在做更复杂的事情,如针对数据库运行查询,我不知道如何testing它们。
public DataTable ExecuteQuery(SqlConnection ActiveConnection, string Query, SqlParameterCollection Parameters) { DataTable resultSet = new DataTable(); SqlCommand queryCommand = new SqlCommand(); try { queryCommand.Connection = ActiveConnection; queryCommand.CommandText = Query; if (Parameters != null) { foreach (SqlParameter param in Parameters) { queryCommand.Parameters.Add(param); } } SqlDataAdapter queryDA = new SqlDataAdapter(queryCommand); queryDA.Fill(resultSet); } catch (Exception ex) { //TODO: Improve error handling Console.WriteLine(ex.Message); } return resultSet; }
这个方法基本上需要从数据库中提取一些数据,并将数据返回到DataTable对象中。
第一个问题可能是最复杂的:我应该在这样的情况下testing什么?
一旦解决了这个问题,是否需要模拟数据库组件或尝试对实际的数据库进行testing。
你在testing什么?
有三种可能性,我的头顶:
答:您正在testingDAO(数据访问对象)类,确保它正确地编组传递到数据库的值/参数,并正确封送/转换/打包从数据库获得的结果。
在这种情况下,根本不需要连接到数据库; 你只需要一个unit testing,用模拟replace数据库(或中间层,例如JDBC,(N)Hibernate,iBatis)。
B.您正在testing(生成的)SQL的语法正确性。
在这种情况下,由于SQL方言有所不同,因此您需要针对RDBMS的正确版本运行(可能生成的)SQL,而不是尝试模拟RDBMS的所有怪癖(以及任何更改function的RDBMS升级都会被你的testing)。
C.您正在testingSQL的语义正确性,即对于给定的基准数据集,您的操作(访问/select和突变/插入和更新)会生成预期的新数据集。
为此,您需要使用dbunit(它允许您设置基线并将结果集与预期的结果集进行比较),或者使用我在此概述的技术完全在数据库中进行testing: 最佳方式testingSQL查询 。
这就是为什么(恕我直言)unit testing有时会造成开发人员的虚假安全感。 根据我使用与数据库交互的应用程序的经验,错误通常是数据处于意外状态(exception或缺失值等)的结果。 如果您经常在unit testing中模拟数据访问,那么当您的代码实际上仍然容易受到这种错误的影响时,您会认为自己的代码运行良好。
我认为你最好的办法是有一个方便的testing数据库,充满了糟糕的数据,并运行你的数据库组件testing。 一直以来,记住你的用户将比你在搞数据上好得多。
unit testing的重点是单独testing一个单元 (duh)。 数据库调用的要点是与另一个单元(数据库) 集成 。 Ergo:unit testing数据库调用没有意义。
但是,应该集成testing数据库调用(如果需要,可以使用与unit testing相同的工具)。
为了爱上帝,不要试试一个已经存在的人口数据库。 但你知道这一点。
一般来说,您已经知道每个查询将要检索的数据types,无论您是对用户进行身份validation,查找电话簿/组织结构图条目或其他任何内容。 你知道你感兴趣的领域,你知道他们有什么约束(例如, UNIQUE
, NOT NULL
,等等)。 你是unit testing你的代码与数据库交互,而不是数据库本身,所以思考如何testing这些function。 如果字段可能为NULL
,则应该进行testing,确保代码正确处理NULL
值。 如果其中一个字段是string( CHAR
, VARCHAR
, TEXT
,&c),请确保正确处理转义字符。
假设用户将尝试将任何*放入数据库,并相应地生成testing用例。 你会想用这个模拟对象。
*包括不良的,恶意的或无效的input。
你可以unit testing一切,除了:queryDA.Fill(resultSet);
只要你执行queryDA.Fill(resultSet),你必须模拟/伪造数据库,或者你正在进行集成testing。
我认为集成testing不是坏事,只是它会遇到不同types的错误,有不同的假阴性和假阳性的可能性,不可能经常这样做,因为它太慢了。
如果我是unit testing这个代码,我会validation参数是否正确构build,命令生成器是否创build了正确数量的参数? 他们都有价值吗? 空值,空string和DbNull得到正确处理?
实际上,填充数据集是testing你的数据库,这是你的DAL范围之外的片状组件。
严格来说,从数据库或文件系统写入/读取的testing不是unit testing。 (虽然它可能是一个集成testing,它可能使用NUnit或JUnit编写)。 unit testing应该testing一个类的操作,隔离它的依赖关系。 所以,当你为接口和业务逻辑层编写unit testing时,根本不需要数据库。
好的,但是如何对数据库访问层进行unit testing呢? 我喜欢本书的build议: xUnit Test Patterns (链接指向本书的“Testing w / DB”一章),关键是:
- 使用往返testing
- 不要在数据访问testing夹具中编写太多的testing,因为它们的运行速度要远远低于“真正的”unit testing
- 如果您可以避免使用真正的数据库进行testing,请在没有数据库的情况
对于unit testing,我通常会嘲笑或伪造数据库。 然后通过dependency injection来使用你的模拟或假实现来testing你的方法。 你也可能有一些集成testing,将testing数据库中的约束,外键关系等。
至于你要testing什么,你要确保该方法使用来自参数的连接,查询string被分配给该命令,并且返回的结果集与通过期望提供的结果集相同在填充方法。 注意 – testing一个返回值的Get方法比修改参数的Fill方法更容易。
为了做到这一点,虽然你应该使用一些dependency injection(DI),而对于.NET有几个。 我目前正在使用Unity框架,但还有其他的更容易。
这里是一个从这个网站上的这个主题的链接,但也有其他的: .NET中的dependency injection与例子?
这将使你更容易地模拟出你的应用程序的其他部分,只需要一个模拟类实现接口,所以你可以控制它将如何响应。 但是,这也意味着devise到一个接口。
既然你问到最好的做法,这将是一个国际海事组织。
然后,除非你需要,否则不要去分贝,因为build议是另一个。
如果你需要testing某些行为,比如与级联删除的外键关系,那么你可能希望为此编写数据库testing,但是通常不会去真正的数据库是最好的,尤其是因为不止一个人可能会运行一个unit testing一次,如果他们要去相同的数据库testing可能会失败,因为预期的数据可能会改变。
编辑:通过数据库unit testing我的意思是,因为它只是使用t-sql来做一些设置,testing和拆卸。 http://msdn.microsoft.com/en-us/library/aa833233%28VS.80%29.aspx
在基于JDBC的项目上,可以模拟JDBC连接,以便在没有实时RDBMS的情况下执行testing,每个testing用例都是独立的(没有数据冲突)。
它允许validation,持久性代码传递适当的查询/参数(例如https://github.com/playframework/playframework/blob/master/framework/src/anorm/src/test/scala/anorm/ParameterSpec.scala )并处理如预期的那样,JDBC结果(parsing/映射)(“需要从数据库中提取一些数据,并返回DataTable对象中的数据”)。
像jOOQ或Acolyte这样的框架可以用于: https : //github.com/cchantep/acolyte 。