我正在为Zend Framework 3应用程序编写集成/数据库测试
3.1.0
,6.2.2
和3.0.0
我的测试因失败而失败
Connect Error: SQLSTATE[HY000] [1040] Too many connections
我设置了一些断点并查看了数据库:
SHOW STATUS WHERE `variable_name` = 'Threads_connected';
我已经看到了100
打开的连接。
我已经通过在以下方面断开连接来减少它们tearDown()
:
protected function tearDown()
{
parent::tearDown();
if ($this->dbAdapter && $this->dbAdapter instanceof Adapter) {
$this->dbAdapter->getDriver()->getConnection()->disconnect();
}
}
但我仍然80
打开了连接。
如何将测试中的数据库连接数量减少到可能的最小值?
发布于 2018-03-21 07:58:19
如果“你的代码是以难以测试的方式编写的,这似乎是一个问题”。数据库连接应由DIC处理或(在某些连接池的情况下)处理一些专用类。基本上,包含的类retrieveActualData()
应该将Sql
实例作为构造函数中的依赖项传递。
相反,它看起来像你的Sql
类是一个有害的PDO
包装器,当你创建一个实例时(很可能)建立了一个数据库连接。相反,你应该在多个类中共享相同的PDO实例。这样你就可以控制已建立连接的数量,并有办法测试你在(某些)隔离中的代码。
所以,主要的解决方案是 - 你的代码不好,但你可以清理它。
不要将代码new
片段散布在执行树的深处,而是将连接作为依赖项传递并共享。
这样你的测试可以转向使用各种模拟和存根,这有助于你隔离测试结构。
但也有一个更实际的方面,你应该考虑。在集成测试中使用SQLite代替真实数据库。PDO支持该选项(您只需为测试代码提供不同的DSN)。
如果你切换到使用SQLite作为“测试数据库”,你将能够有一个定义良好的数据库状态(多个),你可以测试你的代码。
你有类似文件integration-002.db
,其中包含准备好的数据库状态。在集成测试的引导程序中,你只需将准备好的sqlite数据库文件复制integration-0902.db
到live-002.db
并运行所有测试即可。
use PHPUnit\Framework\TestCase;
final class CombinedTest extends TestCase
{
public static function setUpBeforeClass()
{
copy(FIXTURE_PATH . '/integration-02.db', FIXTURE_PATH . '/live-02.db');
}
// your test go here
}
这样你就可以更好地控制持久状态,并且你的测试运行速度会快很多,因为不涉及网络堆栈。
当发现新的错误时,你还可以准备任意数量的测试数据库并添加新的测试数据库。这种方法可以让你在数据库中重新创建更复杂的场景,甚至模拟数据损坏。
发布于 2018-03-21 09:29:16
你可能已将PHP / DB配置为使用持久连接。只有在测试结束执行后,这些连接才会保留在那里。它没有那么坏。
从手册:永久连接是脚本执行结束时不会关闭的链接。当请求一个持久连接时,PHP会检查是否已经有一个相同的持久连接(它从前面保持打开状态) - 如果存在,它会使用它。
一旦你username@host:port
建立了连接,完成了你的工作并断开连接(scipt结束执行),然后再次连接username@host:port
,无论使用哪个表,你都将通过相同的连接套接字进行连接。
你的问题的四个可能的原因
最有可能的是第4层,因为它试图创建一个frabric函数来创建数据库句柄,每次你需要数据库时,就是创建新的连接:
function getConnection() {
// This is an example to test, that it do leave behind a non closed connection.
// Skip "p:", to reduce connections left unless you are configured
// globally for persistency, eg. by mysqlnd.
// p: forced persistency
$link = mysqli_connect("p:127.0.0.1", "my_user", "my_password", "my_db");
if (!$link) return false;
return $link;
}
情况是,对于同一个线程中每个实例方法的调用,都会打开一个全新的连接,因为你真的在问这个问题。只有在不再使用持久套接字时,持久套接字才会被重用(创建者脚本先前已结束其执行)。(至少这是几年前我学会使用它们的方式)
为避免创建太多连接,重建连接工厂以存储所有不同的连接,并按需提供所需的链接,而无需一次又一次地调用连接构建器。这种方式对于一个特定的用户来说是一个终极服务器,例如你最终会运行一次。mysqli_connect
从服务器检索持久连接,并在脚本执行结束时保持全部重用。
class db
{
static $storage = array();
public static function getConnection($username = 'username') {
if (!array_key_exists($username, self::$storage) {
$link = mysqli_connect("p:127.0.0.1", $username, "my_password", "my_db");
if (!$link) return false;
self::$storage[$username] = $link;
}
return self::$storage[$username];
}
}
// ---
$a = db::getConnection();
$b = db::getConnection();
// both $a and $b are the same connection, using the same socket on your server
var_dump($a, $b);
回到你提供的例子,这可能是因为一行:
$sql = new Sql($this->dbAdapter);
在测试中一遍又一遍地执行反复执行,或者由于驱动程序本身在经常重复使用时做了非凡的事情。我的问题是,如果驱动程序在每次getConnection()
运行时都没有创建新连接,或者Sql()
每次调用时都没有创建新连接的构造函数new Sql
。
https://stackoverflow.com/questions/-100007696
复制相似问题