Created
March 11, 2013 14:54
-
-
Save imankulov/5134786 to your computer and use it in GitHub Desktop.
py.test fixture creation problem.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
# This is how I'd implement this using only bare pytest functionality | |
import pytest | |
from user import add_transaction, delete_transaction, get_user_balance | |
# Attempt 1. | |
# According to the documentation at http://pytest.org/latest/fixture.html | |
# try to start with a simple fixture | |
@pytest.fixture | |
def transaction(request): | |
transaction_id = add_transaction(user_id=1, amount=100) | |
request.addfinalizer(lambda: delete_transaction(transaction_id)) | |
return transaction_id | |
# But how to pass extra parameters to the fixture? | |
# It's a stalemate | |
# Just keep this function here for the record | |
# Attempt 2. | |
# Make it smarter and create a gen function taking care of everything | |
@pytest.fixture | |
def transaction_gen(request): | |
created_ids = [] | |
def gen(user_id=1, amount=100): | |
created_ids.append(add_transaction(amount=amount, user_id=user_id)) | |
def destroy(): | |
for id in created_ids: | |
delete_transaction(id) | |
request.addfinalizer(destroy) | |
return gen | |
def test_negative_transactions_are_taken_into_account__with_gen(transaction_gen): | |
transaction_gen(amount=100) | |
transaction_gen(amount=-100) | |
assert get_user_balance(1) == 0 | |
def test_others_transactions_arent_taken_into_account__with_gen(transaction_gen): | |
transaction_gen(amount=100, user_id=1) | |
transaction_gen(amount=100, user_id=2) | |
assert get_user_balance(1) == 100 | |
# It works, but it looks to me more like a hack than a ready-to-use solution :-( | |
# Attempt 3. | |
# I've been forced to return a function from a function and also use another | |
# function to clean up everything. To make it more explicit I could probably | |
# extract these two functions to a separate manager class | |
class TransactionMgr(object): | |
def __init__(self): | |
self.created_ids = [] | |
def gen(self, user_id=1, amount=100): | |
self.created_ids.append(add_transaction(amount=amount, user_id=user_id)) | |
def destroy(self): | |
for id in self.created_ids: | |
delete_transaction(id) | |
@pytest.fixture | |
def transaction_mgr(request): | |
mgr = TransactionMgr() | |
request.addfinalizer(mgr.destroy) | |
return mgr | |
def test_negative_transactions_are_taken_into_account__with_mgr(transaction_mgr): | |
transaction_mgr.gen(amount=100) | |
transaction_mgr.gen(amount=-100) | |
assert get_user_balance(1) == 0 | |
def test_others_transactions_arent_taken_into_account__with_mgr(transaction_mgr): | |
transaction_mgr.gen(amount=100, user_id=1) | |
transaction_mgr.gen(amount=100, user_id=2) | |
assert get_user_balance(1) == 100 | |
# Looks better, but anyway, I was forced to create the whole class managing my | |
# fixtures. | |
# It turned out that as long as I want to use only one instance of the | |
# fixture, I'm good with simple @pytest.fixture, but as soon as I need to manage | |
# more than one fixture instance in a test, I'm forced to create a boilerplate | |
# class, and this approach is neither obvious nor documented. | |
# Attempt 4. | |
# The problem could be solved easier ... Maybe I missed something obvious from | |
# the pytest documentation |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
# | |
# This is how it can be implemented with resources library | |
# | |
from user import add_transaction, delete_transaction, get_user_balance | |
from resources import resources | |
# Usually this code should live in a separate "resources_smth.py" file | |
@resources.register_func | |
def transaction(user_id=1, amount=100): | |
transaction_id = add_transaction(user_id, amount) | |
try: | |
yield transaction_id | |
finally: | |
delete_transaction(transaction_id) | |
# this is the test itself | |
resources.register_mod(__name__) | |
def test_negative_transactions_are_taken_into_account(): | |
with resources.transaction_ctx(amount=100), \ | |
resources.transaction_ctx(amount=-100): | |
assert get_user_balance(1) == 0 | |
def test_others_transactions_arent_taken_into_account(): | |
with resources.transaction_ctx(user_id=1), \ | |
resources.transaction_ctx(user_id=2): | |
assert get_user_balance(1) == 100 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
# Let's pretend it's a database containing user payment transactions | |
# | |
# We can add a new transaction, remove transaction by id, and get the | |
# user balance | |
# | |
# We want to make sure that several transactions add up correctly (including | |
# transactions with negative amount), and that our codebase counts user balance | |
# correctly for each user, even in presence of transactions for other users. | |
db = [] | |
def add_transaction(user_id, amount): | |
global db | |
if db: | |
id = max(t['id'] for t in db) + 1 | |
else: | |
id = 1 | |
db.append({'id': id, 'user_id': user_id, 'amount': amount}) | |
return id | |
def delete_transaction(id): | |
global db | |
db = [t for t in db if t['id'] != id] | |
def get_user_balance(user_id): | |
global db | |
return sum(t['amount'] for t in db if t['user_id'] == user_id) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment