Conworld Codex 2018-05-24

By Max Woerner Chase

In this post:


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.