I have this idea for a Flask-inspired unit testing API, is it worth
pursuing?
math = Tests() # similar to a TestCase or flask.Module
@math.context
def context():
setup()
yield # could yield values to test args, or use context-locals
teardown()
@math.test
def arithmetics():
assert_equal(1 + 1, 2)
suite = Suite() # similar to TestSuite or flask.Flask
suite.register(math)
if __name__ == '__main__':
suite.run()
I want to treat testing as just another type of package. We'd usually
consider it bad to, say, autoregister view modules in a web framework,
or rely on naming for classifying objects.
I also have this idea for dict-configured multi-assertions, something
like:
assert_all_attributes(response, {
'status_code': 200,
'content_type': 'text/html',
'data:in': '<h1>Hello World!</h1>'
})
What do you think?
I just released version 0.1 of this, considered alpha status. Please test it out if you're interested, and report any bugs, questions or ideas you have. PyPI: http://pypi.python.org/pypi/Attest/ Docs: http://packages.python.org/Attest/ GitHub: http://github.com/dag/attest/ Please keep in mind that it's a first release and in alpha-status - I'm very open to suggestions for improvements. Dag
Looks very interesting - I'm not a great fan of unittest but have used it for pragmatic reasons - this looks like it might become a good alternative. This also looks like it might work very nicely with Flask. You could create a @context decorator function to return a Flask request context, for example. The existing Flask-Testing unittest methods (assert_404 etc) could be ported over quite nicely. It wasn't quite clear from the docs, but how do you manage a "teardown" ? For example, if I need to create/destroy a test database ? On 25 November 2010 21:54, Dag Odenhall <dag.odenhall@gmail.com> wrote: > I just released version 0.1 of this, considered alpha status. Please test it > out if you're interested, and report any bugs, questions or ideas you have. > > PyPI: http://pypi.python.org/pypi/Attest/ > Docs: http://packages.python.org/Attest/ > GitHub: http://github.com/dag/attest/ > > Please keep in mind that it's a first release and in alpha-status - I'm very > open to suggestions for improvements. > > Dag
On Thu, 2010-11-25 at 22:31 +0000, danjac354@gmail.com wrote: > This also looks like it might work very nicely with Flask. You could > create a @context decorator function to return a Flask request > context, for example. I want to make it easier to "inherit" a context. Currently you have to do this whole dance, @my.context def context(): with imported_context(): yield Which seems a little overkill to me, would be nice to easily subclass Tests and add a default context, i.e. a FlaskTests that runs in a test_request_context. > The existing Flask-Testing unittest methods > (assert_404 etc) could be ported over quite nicely. Continuing on the previous point, I'd like a Werkzeug test client that returns the result wrapped in Assert. Such a client could be yielded from the default context. This makes it easy to assert things like the status code. api = FlaskTests() @api.test def returns_json(client): response = client.get('/api/') assert response.status_code == 200 assert response.mimetype == 'application/json' In this example, 'response' is a response object wrapped in attest.Assert. Note that the 'assert' keywords here are optional, I just add them for clarity/readability and to avoid some potential silently passing tests. The actual assertions are done in Assert.__eq__. > It wasn't quite clear from the docs, but how do you manage a > "teardown" ? For example, if I need to create/destroy a test database? They're just context managers, so just do stuff after the yield. Might need to wrap in try-finally though *shouldn't* be needed as Attest will catch all exceptions anyway. You can also yield from a context manager such as contextlib.closing() or something similar built into your DB toolkit. @my.context def context(): with db.open() as conn: yield conn A similar example exists in the docs: http://packages.python.org/Attest/api.html#attest.Tests.context
Flask-Testing currently does some syntactic sugar (assert_404 etc)
which can be ported over as simple functions (or just do assert
response.status_code == 404 etc, if you prefer).
It also provides a few utilities e.g. to help with JSON testing. This
requires e.g. being able to set the Response class.
It should be quite doable as you describe, something along these lines:
from flaskext.testing import Assert, assert_200
from myproject import app # instance or factory function
api = Assert(app)
@api.context
def test_something(client):
response = client.get("/")
assert_200(response)
There would need to be an easy way to hook into the context manager.
For example, in addition to setting up the test_request_context, I
might need to do some DB setup. In SQLAlchemy for example I need to
create/drop the db, remove the current session, etc.
So we might need something like this:
@api.before
def before():
db.create_db()
@api.after
def after()
db.session.remove()
db.drop_db()
These would need to be run inside of the test_request_context call.
On 25 November 2010 22:56, Dag Odenhall <dag.odenhall@gmail.com> wrote:
> On Thu, 2010-11-25 at 22:31 +0000, danjac354@gmail.com wrote:
>> This also looks like it might work very nicely with Flask. You could
>> create a @context decorator function to return a Flask request
>> context, for example.
>
> I want to make it easier to "inherit" a context. Currently you have to
> do this whole dance,
>
> @my.context
> def context():
> with imported_context():
> yield
>
> Which seems a little overkill to me, would be nice to easily subclass
> Tests and add a default context, i.e. a FlaskTests that runs in a
> test_request_context.
>
>> The existing Flask-Testing unittest methods
>> (assert_404 etc) could be ported over quite nicely.
>
> Continuing on the previous point, I'd like a Werkzeug test client that
> returns the result wrapped in Assert. Such a client could be yielded
> from the default context. This makes it easy to assert things like the
> status code.
>
> api = FlaskTests()
>
> @api.test
> def returns_json(client):
> response = client.get('/api/')
> assert response.status_code == 200
> assert response.mimetype == 'application/json'
>
> In this example, 'response' is a response object wrapped in
> attest.Assert. Note that the 'assert' keywords here are optional, I just
> add them for clarity/readability and to avoid some potential silently
> passing tests. The actual assertions are done in Assert.__eq__.
>
>> It wasn't quite clear from the docs, but how do you manage a
>> "teardown" ? For example, if I need to create/destroy a test database?
>
> They're just context managers, so just do stuff after the yield. Might
> need to wrap in try-finally though *shouldn't* be needed as Attest will
> catch all exceptions anyway. You can also yield from a context manager
> such as contextlib.closing() or something similar built into your DB
> toolkit.
>
> @my.context
> def context():
> with db.open() as conn:
> yield conn
>
> A similar example exists in the docs:
> http://packages.python.org/Attest/api.html#attest.Tests.context
>
>
On Thu, 2010-11-25 at 23:18 +0000, danjac354@gmail.com wrote: > There would need to be an easy way to hook into the context manager. > For example, in addition to setting up the test_request_context, I > might need to do some DB setup. In SQLAlchemy for example I need to > create/drop the db, remove the current session, etc. @api.context def context(): with app.test_request_context(): db.create_db() yield db.session.remove() db.drop_db() If 'test_request_context' was made a default context for a FlaskTests class or similar, you could also drop that with statement. I just had an idea how to do that, the Tests class could inject all contexts in a contexts attribute and then you could subclass like so: class FlaskTests(Tests): contexts = (app.test_request_context,) And to generalize for any app, class FlaskTests(Tests): def __init__(self, app): Tests.__init__(self) self.contexts = (app.test_request_context,) Not sure yet if this is the best idea, but it's one idea. A contrived example obviously, the real thing would handle app factories and probably a nice way to subclass FlaskTests too for setting your application so you don't have to do it for each FlaskTests instance.
On Fri, 2010-11-26 at 00:35 +0100, Dag Odenhall wrote:
> Not sure yet if this is the best idea, but it's one idea.
Pushed a different solution: you can have multiple @tests.context. This
allows the use of factory functions:
def flask_tests(app):
tests = Tests()
@tests.context
def request_context():
with app.test_request_context():
yield Assert(app.test_client())
return tests
api = flask_tests(app)
@api.context
def not_overiding(): pass
@api.test
def json(client):
response = client.get('/api/')
response.mimetype == 'application/json'
response.status_code == 200
You could also have helper functions for adding contexts with less
boilerplate:
def with_db(tests):
@tests.context
def connect():
with db.open() as conn:
yield conn
api = flask_tests(app)
with_db(api)
@api.test
def json(client, conn): pass
Is this a good solution? It feels right to me, and in relation to Flask
and the tradition of create_app() and init_app() etc.
One quick issue I thought of: I definitely like the concept with operator overloading of the Assert object, but the name is a bit off I think - it is a verb, and at the same time it tries to be something different from a normal assert, but sticks with the same name. How about Asserter, or maybe something even more different? Thanks, Zahari On Thu, Nov 25, 2010 at 11:54 PM, Dag Odenhall <dag.odenhall@gmail.com> wrote: > I just released version 0.1 of this, considered alpha status. Please test it > out if you're interested, and report any bugs, questions or ideas you have. > > PyPI: http://pypi.python.org/pypi/Attest/ > Docs: http://packages.python.org/Attest/ > GitHub: http://github.com/dag/attest/ > > Please keep in mind that it's a first release and in alpha-status - I'm very > open to suggestions for improvements. > > Dag
On Fri, 2010-11-26 at 01:09 +0200, Zahari Petkov wrote: > One quick issue I thought of: I definitely like the concept with > operator overloading of the Assert object, but the name is a bit off I > think - it is a verb, and at the same time it tries to be something > different from a normal assert, but sticks with the same name. How > about Asserter, or maybe something even more different? It's verb-like when used like, Assert(result) == expected For situations where you wrap 'result' before asserting, 'Assertive' would be a better name. You can always 'import as'…
BTW, I particularly liked this use case:
hello = Assert('hello')
hello == 'hello'
hello.upper() == 'HELLO'
hello.capitalize() == 'Hello'
Very impressive.
On Fri, Nov 26, 2010 at 1:09 AM, Zahari Petkov <zarchaoz@gmail.com> wrote:
> One quick issue I thought of: I definitely like the concept with
> operator overloading of the Assert object, but the name is a bit off I
> think - it is a verb, and at the same time it tries to be something
> different from a normal assert, but sticks with the same name. How
> about Asserter, or maybe something even more different?
>
> Thanks,
> Zahari
>
> On Thu, Nov 25, 2010 at 11:54 PM, Dag Odenhall <dag.odenhall@gmail.com> wrote:
>> I just released version 0.1 of this, considered alpha status. Please test it
>> out if you're interested, and report any bugs, questions or ideas you have.
>>
>> PyPI: http://pypi.python.org/pypi/Attest/
>> Docs: http://packages.python.org/Attest/
>> GitHub: http://github.com/dag/attest/
>>
>> Please keep in mind that it's a first release and in alpha-status - I'm very
>> open to suggestions for improvements.
>>
>> Dag
>
I started on something like this in a branch of Flask-Testing - there might be something of use to you: http://bitbucket.org/danjac/flask-testing/src/37863a0d955a/flaskext/testing.py On 10 November 2010 20:48, Dag Odenhall <dag.odenhall@gmail.com> wrote: > I have this idea for a Flask-inspired unit testing API, is it worth > pursuing? > > > math = Tests() # similar to a TestCase or flask.Module > > @math.context > def context(): > setup() > yield # could yield values to test args, or use context-locals > teardown() > > @math.test > def arithmetics(): > assert_equal(1 + 1, 2) > > suite = Suite() # similar to TestSuite or flask.Flask > suite.register(math) > > if __name__ == '__main__': > suite.run() > > > I want to treat testing as just another type of package. We'd usually > consider it bad to, say, autoregister view modules in a web framework, or > rely on naming for classifying objects. > > I also have this idea for dict-configured multi-assertions, something like: > > > assert_all_attributes(response, { > 'status_code': 200, > 'content_type': 'text/html', > 'data:in': '<h1>Hello World!</h1>' > }) > > > What do you think?