In this post:
- I attempt to wire up the stuff SQLAlchemy needs in a testing context.
Okay, here's the plan for the code I wrote last week. I'm going to write tests against it at that layer, and then try to have the GUI code just be thin wrappers around that functionality.
First, I want to write those tests, so I can get my coverage back up.
Okay, let's see... I'm slightly adding functionality at random, which is bad. The next thing to do must be figuring out how to spin these things up in the first place. So, first up, let's see how to use SQLAlchemy with pytest. I found a blogpost, Testing with py.test and Sqlalchemy, that looks like it provides a decent starting point, but it does appear to be more pyramid-specific than I think it lets on to start with. I have no good idea where some of these modules are coming from, and I think I'll need to do some things differently, on account of I legitimately am planning to use SQLite to power the Conworld Codex, since it's a GUI app and all.
Like, let's see, it looks like I don't care in the same way about sessions...
Okay, this is going to be yet another post where I didn't give myself enough lead time. My goal now is to lay out the logic I need to create test fixtures that will suffice to handle this code. I don't want to deal with the multiple session setup right now, because that can't possibly gain me anything with my plans. I want to have a distinct in-memory SQLite engine for each test. This should just be a matter of deleting the right code.
The SQLAlchemy documentation, on "Joining a Session into an External Transaction (such as for test suites)" was also very helpful, and I ended up leaning more on that.
I moved things around some more, and I've got code that looks plausible; I'll have to try it out later. Something I'm going to have to accept is that I won't be able to move as fast in these posts when I'm learning so much. Here's the current state of things:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
"""Test fixtures for conworld_codex tests.""" from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker import pytest @pytest.fixture(scope='session') def models(): """Import the models module.""" import conworld_codex.models return conworld_codex.models @pytest.fixture(scope='session') def _session(): """Create a global session.""" return sessionmaker() @pytest.fixture(scope='session') def engine(): """Create a global engine. Honestly unsure whether this should be session scoped, or function scoped. """ return create_engine('sqlite://') @pytest.fixture def connection(request, models, engine): """Create a connection and set up the database, for the duration of a test. I'm not sure if it actually makes sense to do this work every time. """ models.Base.metadata.create_all(engine) connection = engine.connect() models.Base.metadata.bind = engine try: yield connection finally: models.Base.metadata.drop_all() connection.close() @pytest.fixture def db_session(request, _session, connection, models): """Create a transaction against a session, and roll it back after the test. To use subtransactions, call begin_nested. I think. """ trans = connection.begin() session = _session(bind=connection) try: yield session finally: session.close() trans.rollback()
I'll be very interested to see which parts of this make sense, which cause minor ergonomics issues, and which are just plain wrong.
Next week, I attempt to put this code into practice.