On 02/28/2016 08:51 PM, Nando Florestan wrote:
Hello,

Because even in-memory SQLite is too slow for unit tests (I have seen a
couple large web applications with 7-minute+ test suites), I have
started experimenting with new FakeSession and FakeQuery classes that
store model instances in Python memory (they never talk to any
database).  So we would remain in the object-oriented world and never go
into the relational world in unit tests.  I know object-oriented models
are only a subset of what SQLAlchemy does, but it is the most important
part and if I could unit-test code like that more quickly, I would be
much happier.

The hard part is making queries work -- I would be satisfied even if
only a reasonable subset of queries worked.  I would like to get your
help figuring this out.  Take a look at FakeSession and FakeQuery:

https://github.com/nandoflorestan/bag/blob/master/bag/sqlalchemy/testing.py#L68

Now there is one thing I've realized that SQLAlchemy could do to help me
achieve this, but it doesn't do.  Suppose you have:

some_query = session.query(User).filter(User.name == "Albert Roussel")

When unit tests are running, a FakeSession instance is passed to the
application code, which then could be read as:

fake_query = my_fake_session.query(User).filter(User.name == "Albert
Roussel")

The argument to .filter() is one clause with left and right parts and an
operator between them.  Well when you inspect the resulting object
created by SQLAlchemy, it contains the operator and the right side
intact, but the left side -- User.name -- has already had some
information lost:  the resulting object doesn't know anymore that the
query referred to the class User, having translated it to table "user"
with a "name" column that might or might not have the same name as the
User class' instance variable "name".

not actually true:

from sqlalchemy import Column, Integer
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class A(Base):
    __tablename__ = 'a'
    id = Column(Integer, primary_key=True)

crit = A.id == 5

assert crit.left is not A.__table__.c.id
assert hash(crit.left) == hash(A.__table__.c.id)
assert crit.left._annotations['parententity'].class_ is A
assert A.__mapper__.class_manager[A.__mapper__._columntoproperty[crit.left].key] is A.id





This makes the job much harder.  It is understandable that SQLAlchemy
"forgets" the class and mapper because all it wants to do is generate
SQL, but if I could configure the session or the query to remember the
class and attribute name, that would be so much better for the code I am
writing...

Some people will say it's not worth it to pursue this project.  These
know-it-alls can remain silent, please.  I would like to hear from
people who see that creating a fake environment to unit test application
code very quickly is an important thing.  So please help the project as
defined, don't try to prove that my project is worthless.  Because it
isn't.  Save your time and my own.

Mocking a Session and Query for tests without hitting a database is achievable using a full front-to-back MockSession, which I have illustrated here: https://bitbucket.org/zzzeek/pycon2014_atmcraft/src/f50cbe745a197ea7db83569283b703c418481222/atmcraft/tests/_mock_session.py?at=master&fileviewer=file-view-default

an example of its use is here:

https://bitbucket.org/zzzeek/pycon2014_atmcraft/src/f50cbe745a197ea7db83569283b703c418481222/atmcraft/tests/test_views_mockdb.py?at=master&fileviewer=file-view-default

in this test, web "requests" are made which call into MockSession for "database" access. As the MockSession has been pre-loaded with exactly the sequence of calls expected, we assert not only that the database-calling method works but also that it makes exactly the database calls we expect.

Essentially, the steps are:

1. make a MockSession

2. call exactly the desired queries as they are to be called by the application, and at the end of each query chain set up the "return_value" and "side_effect" for calls like all(), one(), first(), and others. This sets up the MockSession with exactly the input values expected, and it makes no difference at all that something like "User.name == 'foo'" converts to a different object; MockSession ensures that incoming ClauseElements are hashed on their ultimate string representation in SQL.

3. inject the MockSession into the code to be tested. When the code calls upon MockSession with the expected methods and parameters, the desired result is returned.

Hope this helps!






Thanks.

Nando Florestan

--
You received this message because you are subscribed to the Google
Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send
an email to [email protected]
<mailto:[email protected]>.
To post to this group, send email to [email protected]
<mailto:[email protected]>.
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to