Cockroachdb/Copist:在围棋测试中模拟SQL数据库从未如此简单

2020-07-12 23:09:51

在围棋测试中模拟您的SQL数据库从未如此简单。复制器库自动记录测试期间进行的低级SQL调用。然后,它生成无需连接到Real SQL数据库即可回放这些调用的测试代码。再做一次测试。这一次,它们将运行得更快,因为现在它们不需要数据库连接。

最棒的是,您的测试将在每个测试用例之间运行,就好像您的测试数据库被重置为干净的、众所周知的状态一样。令人沮丧的问题已经不复存在,即测试单独运行时运行良好,但与修改数据库的其他测试协同运行时会失败。

只要代码直接或间接使用GO的SQL包(例如,GO ORM和广泛使用的sqlxpackage),Copist就不会对生产代码施加任何开销,而且几乎不需要对您的应用程序或测试代码进行任何更改。这是因为Copist在Go的sqlpackage的驱动程序级别运行。

假设您有一些应用程序代码,它打开到Postgresdatabase的连接并查询一些客户数据:

func QueryName(db*sql.DB)string{row,_:=db.Query(";select name from Customers where id=$1";,100)推迟rows.Close()for rows.Next(){var name string rows.Scan(&;name)return name}return";";}。

测试此代码的通常方法是创建一个测试数据库,并使用测试客户数据填充该数据库。但是,如果应用程序代码修改了数据库中的行,比如删除客户怎么办?如果上面的代码在修改后的数据库上运行,它可能不会返回预期的客户。因此,重要的是要在测试用例之间重置数据库的状态,以便测试的行为是可预测的。但是连接到数据库的速度很慢。运行查询的速度很慢。而且在每次测试之间重置整个数据库的状态非常慢。

各种模仿库是使用测试数据库的另一种选择,这些库在应用程序或数据访问堆栈的某一层拦截调用,并在不需要接触数据库的情况下返回录制的响应。其中许多库的问题在于,它们要求开发人员手动构建录制的响应,这在应用程序发生更改时既耗时又脆弱。

Copyist包含一个Go SQL包驱动程序,用于记录应用程序和测试代码进行的低级SQL调用。当使用COPYSTER调用GO测试时,如果使用";-RECORD&34;命令行标志,则COPYSTER驱动程序将记录所有SQL调用。测试完成后,Copist将在随附的测试文件中生成回放GO代码。然后,可以在没有";-record";标志的情况下再次运行GO测试。这一次,复印机驱动程序将回放录制的通话,而不需要访问数据库。GO测试并没有变得更明智,它的运行方式就像它正在使用数据库一样。

下面是使用Copist的推荐测试模式。该示例显示了如何对上面所示的QueryName函数进行单元测试。

func TestMain(m*testing.M){flag.parse()copy ist.Register(";postgres";,setDB)os.Exit(m.Run())}func TestQueryName(t*testing.T){defer copy ist.Open().Close()db,_:=sql.Open(";Copist_Postgres";,";postgreSQL://root。{t.Error(";测试失败";)}}。

在TestMain函数(或在任何测试之前调用的任何其他位置)中,调用copy ist.Register函数。此函数用于将新驱动程序注册到名为Copist_<;driverName>;的GO';的SQL包中。在您要录制的任何测试中,添加一个延迟复制器。Open().Close()语句。此语句开始一个新的录制会话,然后在测试结束时调用Close时生成回放代码。

Copist确实需要知道是在录制模式下运行还是在回放模式下运行。要使Copist在录制模式下运行,请使用记录标志调用测试:

这将在与TestQueryNamefile相同的目录中生成一个新的测试文件。例如,如果该文件名为app_test.go,则Copyist将生成一个包含TestQueryName测试记录的app_copy ist_test.go文件。现在再尝试运行测试几次(第一次需要重新编译测试,因此需要更长的时间):

上面的部分忽略了一个重要的细节。注册驱动程序以与Copist一起使用时,Register的第二个参数是回调函数:

如果不为空,则只要Copyist在录制模式下运行,每次调用Copist.Open(通常在每次测试开始时)时,Copist都会调用此函数。此重置函数可以做它喜欢做的任何事情,但通常它会对数据库运行SQL脚本,以便通过删除/创建表、删除表中的数据和/或将数据插入到表中来使测试更方便,从而将其重置为干净状态。

这只是意味着您需要使用";-record&34;命令行标志重新运行测试,以便生成新的录制。最有可能的是,您更改了应用程序或测试代码,以便它们使用不同的调用顺序或内容以不同的方式调用数据库。

但是,在更罕见的情况下,您已经重新生成了录制,没有进行任何测试或应用程序更改,但在以不同的顺序运行测试时仍然看到此错误。这是由你的应用程序或你正在使用的ORM中的非决定论造成的。

作为不确定性的一个例子,一些ORM在第一个连接打开时向数据库发送设置查询,以确定数据库版本。因此,无论哪个测试碰巧先运行,都会记录一个额外的查询调用。如果您首先运行不同的测试,您将看到意外调用错误,因为其他测试并不期待额外的调用。

解决这些问题的办法是消除非决定论。例如,在ORM发送设置查询的情况下,从TestMainmethod初始化它:

测试文件的大小与您的测试对数据库的访问次数以及它们请求的数据量直接相关。虽然复制者费力地生成高效的代码,以尽可能多地消除冗余,但它所能做的也就这么多了。尝试编写对较小数量的有趣数据进行操作的测试。对于需要大量数据库调用或大量数据的测试,请使用不同形式的验证。关于模仿者的一个好处是,你可以选择哪些测试将使用它。为正确的工作选择正确的工具,诸如此类。

由于Copist的工作方式,它只能用于非并行、单线程测试和应用程序代码。这是因为驱动程序代码无法知道哪个测试和/或哪个线程使用了哪些连接、语句和事务。

Copist目前仅支持Postgres PQ驱动程序。如果您想要扩展Copist以支持其他驱动程序,如MySQL或SQLite,请您提交拉取请求。

Copist不会实现每个SQL包驱动程序接口和方法。这可能意味着复制者可能不能完全使用一些具有更高级功能的驱动程序。欢迎在这一领域的贡献。