如何用数据库查询unit testing一个对象

我听说unit testing“非常棒”,“非常酷”和“各种各样的好东西”,但是我的文件中有70%以上涉及到数据库访问(一些读取和一些写入),我不知道如何为这些文件写一个unit testing。

我使用PHP和Python,但我认为这是一个适用于大多数/所有使用数据库访问的语言的问题。

我会build议嘲笑你的数据库调用。 Mock基本上是对象,看起来像你正在试图调用一个方法的对象,因为他们有相同的属性,方法等可用于调用者。 但是,当调用某个特定方法时,不是执行它们编程时要执行的任何操作,而是完全跳过该操作,只返回结果。 这个结果通常是由你提前定义的。

为了设置你的对象模拟,你可能需要使用某种控制/dependency injection模式的反转,如下面的伪代码:

class Bar { private FooDataProvider _dataProvider; public instantiate(FooDataProvider dataProvider) { _dataProvider = dataProvider; } public getAllFoos() { // instead of calling Foo.GetAll() here, we are introducing an extra layer of abstraction return _dataProvider.GetAllFoos(); } } class FooDataProvider { public Foo[] GetAllFoos() { return Foo.GetAll(); } } 

现在在你的unit testing中,你创build了一个FooDataProvider的模拟器,它允许你调用GetAllFoos方法,而不必实际地击中数据库。

 class BarTests { public TestGetAllFoos() { // here we set up our mock FooDataProvider mockRepository = MockingFramework.new() mockFooDataProvider = mockRepository.CreateMockOfType(FooDataProvider); // create a new array of Foo objects testFooArray = new Foo[] {Foo.new(), Foo.new(), Foo.new()} // the next statement will cause testFooArray to be returned every time we call FooDAtaProvider.GetAllFoos, // instead of calling to the database and returning whatever is in there // ExpectCallTo and Returns are methods provided by our imaginary mocking framework ExpectCallTo(mockFooDataProvider.GetAllFoos).Returns(testFooArray) // now begins our actual unit test testBar = new Bar(mockFooDataProvider) baz = testBar.GetAllFoos() // baz should now equal the testFooArray object we created earlier Assert.AreEqual(3, baz.length) } } 

简而言之,这是一个常见的嘲笑场景。 当然你也可能想unit testing你的实际的数据库调用,为此你需要点击数据库。

理想情况下,你的对象应该是持续的无知。 例如,你应该有一个“数据访问层”,你会请求,将返回对象。 这样,您可以将这部分从unit testing中分离出来,或者单独进行testing。

如果您的对象与您的数据层紧密耦合,则很难进行适当的unit testing。 unit testing的第一部分是“单元”。 所有单位应该能够被孤立地进行testing。

在我的c#项目中,我使用了一个完全独立的数据层的NHibernate。 我的对象位于核心域模型中,可从我的应用程序层访问。 应用程序层同时与数据层和域模型层进行通信。

应用程序层有时也被称为“业务层”。

如果您正在使用PHP,请创build一组特定的用于数据访问的类。 确保你的对象不知道它们是如何被持久化的,并把你的应用程序类中的两个连接起来。

另一种select是使用模拟/存根。

使用数据库访问unit testing对象的最简单方法是使用事务处理作用域。

例如:

  [Test] [ExpectedException(typeof(NotFoundException))] public void DeleteAttendee() { using(TransactionScope scope = new TransactionScope()) { Attendee anAttendee = Attendee.Get(3); anAttendee.Delete(); anAttendee.Save(); //Try reloading. Instance should have been deleted. Attendee deletedAttendee = Attendee.Get(3); } } 

这将恢复数据库的状态,基本上就像一个事务回滚,所以你可以多次运行testing,没有任何副作用。 我们在大型项目中成功地使用了这种方法。 我们的构build需要一些时间来运行(15分钟),但是进行1800个unit testing并不可怕。 另外,如果构build时间是一个问题,您可以将构build过程更改为具有多个构build,一个用于构buildsrc,另一个之后会处理unit testing,代码分析,打包等。

如果你想unit testing你的类,你应该模拟数据库访问。 毕竟,你不想在unit testing中testing数据库。 那将是一个综合testing。

摘要呼叫,然后插入一个模拟,只是返回预期的数据。 如果你的类没有做更多的执行查询,它可能不值得testing它们,但是…

当我们开始考察包含大量“业务逻辑”sql操作的中间层stream程的unit testing时,我也许可以尝试一下我们的经验。

我们首先创build了一个抽象层,允许我们“插入”任何合理的数据库连接(在我们的例子中,我们只支持一个ODBCtypes的连接)。

一旦到位,我们就可以在代码中做这样的事情(我们用C ++工作,但是我相信你明白了):

GetDatabase().executeSQL(“INSERT INTO foo(blah,blah)”)

在正常运行时,GetDatabase()将返回一个对象,它通过ODBC直接将所有的sql(包括查询)提供给数据库。

然后我们开始研究内存数据库 – 长久以来最好的方式似乎是SQLite。 ( http://www.sqlite.org/index.html )。 设置和使用非常简单,并允许我们创build子类并重写GetDatabase()以将sql转发到为每个执行的testing创build和销毁的内存数据库。

我们还处于早期阶段,但目前看起来还不错,但是我们必须确保我们创build了所有必需的表格,并使用testing数据填充它们 – 但是我们通过创build一套通用的帮助函数,可以为我们做很多这一切。

总的来说,它对我们的TDD过程非常有帮助,因为修复某些bug似乎相当无害的更改会对系统的其他(难以检测)区域产生相当奇怪的影响 – 这是由于sql /数据库的本质所致。

显然,我们的经验围绕着一个C ++开发环境,但是我相信你可能会在PHP / Python下得到类似的工作。

希望这可以帮助。

您拥有的选项:

  • 编写一个脚本,在开始unit testing之前清除数据库,然后使用预定义的数据集填充数据库并运行testing。 你也可以在每次testing之前做到这一点 – 它会很慢,但不容易出错。
  • 注入数据库。 (伪Java中的示例,但适用于所有OO语言)

     class Database {
     公共结果查询(string查询){...真正的数据库在这里...}
     } 

    类MockDatabase扩展数据库{
    公共结果查询(string查询){
    返回“模拟结果”;
    }
    }

    类ObjectThatUsesDB {
    public ObjectThatUsesDB(Database db){
    this.database = db;
    }
    }
    现在在生产中你使用正常的数据库,并为所有的testing,你只是注入模拟数据库,你可以创build特设的。

  • 在大多数代码中不要使用数据库(反正这是一个糟糕的做法)。 创build一个“数据库”对象,而不是返回结果将返回正常的对象(即将返回User而不是一个元组{name: "marcin", password: "blah"} ))写所有的testing与临时构build的真实对象写一个大的testing,依赖于一个数据库,确保这个转换工作正常。

当然,这些方法不是相互排斥的,你可以根据需要混合使用。

本书的“ xUnittesting模式”描述了一些处理命中数据库的unit testing代码的方法。 我同意其他那些说你不想这么做的人,因为这很慢,但是你必须在某个时候做,IMO。 嘲笑数据库连接来testing更高层次的东西是一个好主意,但是看看这本书的build议,你可以做些什么来与真正的数据库进行交互。

我通常试图分解testing对象(和ORM,如果有的话)和testing数据库之间的testing。 我通过嘲笑数据访问调用来testing事物的对象,而我通过testing与db的对象交互来testing事物的数据库方面,在我的经验中,通常相当有限。

我曾经为编写unit testing感到沮丧,直到我开始嘲笑数据访问部分,所以我不需要创buildtesting数据库或生成testing数据。 通过模拟数据,您可以在运行时全部生成数据,并确保您的对象可以与已知input正常工作。

我从来没有在PHP中这样做,我从来没有使用Python,但是你想要做的是模拟调用数据库。 要做到这一点,你可以实现一些IoC是否第三方工具,或者你自己pipe理它,那么你可以实现一些模拟版本的数据库调用者,这是你将控制该假冒的结果。

一个简单的IoCforms可以通过编码到接口来执行。 这需要在你的代码中进行某种面向对象的操作,所以它可能不适用于你正在做的事情(我说,因为我所要做的就是提及PHP和Python)

希望这是有帮助的,如果没有别的,你有一些术语,现在search。

我同意第一篇文章 – 数据库访问应该被剥离到实现一个接口的DAO层。 然后,您可以根据DAO层的存根实现testing您的逻辑。

你可以使用模拟框架来抽象出数据库引擎。 我不知道如果PHP / Python得到了一些,但对于types的语言(C#,Java等)有很多select

这也取决于你如何devise这些数据库访问代码,因为有些devise比之前的文章提到的更容易进行unit testing。

如果你的项目有很高的凝聚力和松散的耦合,unit testing你的数据库访问是很容易的。 这样你就可以只testing每个特定的类所做的事情,而不必一次testing所有的东西。

例如,如果您unit testing您的用户界面类,则您所编写的testing应该只尝试validationUI内的逻辑是否按预期运行,而不是该function后面的业务逻辑或数据库操作。

如果你想unit testing实际的数据库访问,实际上你最终会得到更多的集成testing,因为你将依赖于networking堆栈和你的数据库服务器,但是你可以validation你的SQL代码是否做了你所要求的做。

对于我个人而言,unit testing的隐藏力量在于它迫使我以比没有它们更好的方式来devise我的应用程序。 这是因为这真的帮我摆脱了“这个function应该尽一切”的心态。

对不起,我没有任何PHP / Python的具体代码示例,但如果你想看到一个.NET的例子,我有一个职位 ,描述了一个技术,我曾经做过这个相同的testing。

为unit testing设置testing数据可能是一个挑战。

谈到Java,如果您使用Spring API进行unit testing,则可以在单元级别上控制事务。 换句话说,您可以执行涉及数据库更新/插入/删除和回滚更改的unit testing。 在执行结束时,您将数据库中的所有内容都保存在开始执行之前。 对我来说,这是尽可能好的。