In [1]:
from IPython.core.magic import register_line_magic
In [2]:
@register_line_magic
def runtests(line):
    import collections
    import time
    
    ip = get_ipython()
    
    tests = {}
    
    # search will only find things with __call__ methods (e.g. functions)
    for k, v in ip.user_ns.iteritems():
        if k.startswith('test') and isinstance(v, collections.Callable):
            tests[k] = v
    print 'Collected {} tests.\n'.format(len(tests))
    
    ok = 0
    fail = {}
    error = {}
    
    t1 = time.time()
    
    for name, func in tests.iteritems():
        print '{} ... '.format(name),
        
        try:
            func()
        except AssertionError as e:
            print 'fail'
            fail[name] = e
        except Exception as e:
            print 'error'
            error[name] = e
        else:
            print 'ok'
            ok += 1
    
    t2 = time.time()
    
    if fail:
        print ''
        print 'Failures'
        print '========'
        for name, e in fail.iteritems():
            print '{}: {}'.format(name, repr(e))
    
    if error:
        print ''
        print 'Errors'
        print '======'
        for name, e in error.iteritems():
            print '{}: {}'.format(name, repr(e))
    
    print ''
    print 'Ran {} tests in {:.3g} seconds.'.format(len(tests), t2 - t1)
    print 'ok = {}, fail = {}, error = {}'.format(ok, len(fail), len(error))
In [3]:
def test_something():
    assert True

def test_fail():
    assert 4 == 2, '4 != 2'

def test_error():
    raise ValueError('Sorry!')
In [4]:
%runtests
Collected 3 tests.

test_fail ...  fail
test_something ...  ok
test_error ...  error

Failures
========
test_fail: AssertionError('4 != 2',)

Errors
======
test_error: ValueError('Sorry!',)

Ran 3 tests in 0.00016 seconds.
ok = 1, fail = 1, error = 1
In [4]: