Как подсчитать запросы sqlalchemy в модульных тестах

В Django я часто утверждаю количество запросов, которые должны быть сделаны, чтобы модульные тесты ломали новые проблемы с запросом N + 1

from django import db
from django.conf import settings
settings.DEBUG=True

class SendData(TestCase):
    def test_send(self):
        db.connection.queries = []
        event = Events.objects.all()[1:]
        s = str(event) # QuerySet is lazy, force retrieval
        self.assertEquals(len(db.connection.queries), 2)

В SQLAlchemy трассировка на STDOUT включена, установив флаг echo двигатель

engine.echo=True

Каков наилучший способ написать тесты, которые подсчитывают количество запросов, сделанных SQLAlchemy?

class SendData(TestCase):
    def test_send(self):
        event = session.query(Events).first()
        s = str(event)
        self.assertEquals( ... , 2)

Ответ 1

Использовать SQLAlchemy Основные события для выполнения запросов регистрации/отслеживания (вы можете прикрепить их к своим модуляционным тестам, чтобы они не влияли на вашу производительность на самом деле:

event.listen(engine, "before_cursor_execute", catch_queries)

Теперь вы пишете функцию catch_queries, где путь зависит от того, как вы тестируете. Например, вы можете определить эту функцию в своем тестовом заявлении:

def test_something(self):
    stmts = []
    def catch_queries(conn, cursor, statement, ...):
        stmts.append(statement)
    # Now attach it as a listener and work with the collected events after running your test

Вышеупомянутый метод - просто вдохновение. Для расширенных случаев вы, вероятно, хотели бы иметь глобальный кеш событий, которые вы опорожняете после каждого теста. Причина в том, что до 0,9 (текущий dev) нет API для удаления прослушивателей событий. Таким образом, один глобальный слушатель получает доступ к глобальному списку.

Ответ 2

Я создал для этой цели класс менеджера контекста:

class DBStatementCounter(object):
    """
    Use as a context manager to count the number of execute() performed
    against the given sqlalchemy connection.

    Usage:
        with DBStatementCounter(conn) as ctr:
            conn.execute("SELECT 1")
            conn.execute("SELECT 1")
        assert ctr.get_count() == 2
    """
    def __init__(self, conn):
        self.conn = conn
        self.count = 0
        # Will have to rely on this since sqlalchemy 0.8 does not support
        # removing event listeners
        self.do_count = False
        sqlalchemy.event.listen(conn, 'after_execute', self.callback)

    def __enter__(self):
        self.do_count = True
        return self

    def __exit__(self, *_):
        self.do_count = False

    def get_count(self):
        return self.count

    def callback(self, *_):
        if self.do_count:
            self.count += 1