Features
nose-of-yeti has a number of features:
describe
syntax for creating classesit
andignore
syntax for creating functionsbefore_each
andafter_each
syntax for creatingsetUp
andtearDown
functions.A 1:1 mapping between lines in the original and converted code
Syntax for shared tests
Making classes
To group tests together you can use the describe keyword:
describe "Some Test":
it "does things":
assert 1 == 1
This will be converted into classes where each test under the group becomes a
method prefixed with test_
:
class Test_SomeTest:
def test_does_things(self):
assert 1 == 1
The class that is inherited can be changed by putting the name of the super class between the describe keyword and the name of the group:
describe NiceTestCase, "name":
pass
becomes:
class TestName(NiceTestCase):
pass
Describe blocks can also be nested. The way this works is that each nested level
will inherit from the class of the previous level. Then, to ensure that tests
from inherited super classes aren’t run multiple times, a special is_noy_spec
attribute is set on each class and the plugins for different test frameworks
will ensure only methods defined on the class itself will be run:
describe "NestedOne":
it "has test":
pass
describe "NestedTwo":
it "also has test":
pass
describe "You get the point":
it "ladelalalal":
pass
becomes:
class Test_NestedOne:
def test_has_test(self):
pass
class Test_NestedOne_NestedTwo(NestedOne):
def test_also_has_test(self):
pass
class Test_NestedOne_NestedTwo_You_get_the_point(Test_NestedOne_NestedTwo):
def test_ladelalalal(self):
pass
Test_NestedOne.is_noy_spec = True
Test_NestedOne_NestedTwo.is_noy_spec = True
Test_NestedOne_NestedTwo_You_get_the_point.is_noy_spec = True
It will prevent nested classes from having the same name as non-nested classes by prefixing the name of the class with the name of the class it inherits from.
Creating test functions
The tests themselves can be specified with it
or ignore
in a similar
fashion to describe
:
it "is a test without a describe":
# Note that it doesn't have a self paramater
pass
# This function has no colon, it will raise a Syntax Error.
# You must specify a colon after blocks.
it "is a method without a colon"
describe "AGroup":
it "is a test with a describe":
# Note that it does have a self parameter
pass
ignore "ignored method":
# This method is named ignore__%s
assert 1 == 3
becomes:
def test_is_a_test_without_a_describe":
# Note that it doesn't have a self parameter
pass
# This function has no colon, it will raise a Syntax Error.
# You must specify a colon after blocks.
def test_is_a_method_without_a_colon()
class Test_AGroup:
def test_is_a_test_with_a_describe(self):
# Note that it does have a self parameter
pass
def ignore__ignored_method(self):
# This method is named ignore__%s
assert 1 == 3
Test_AGroup.is_noy_spec = True
As shown in the example:
it "name"
converts todef test_name
ignore "name""
converts todef ignore__name
If it is part of a describe block, it is given a
self
parameterIf it has no colon, it will cause a SyntaxError
nose-of-yeti can also cope with non-alphanumeric characters in the name of a
test, by removing them from the function name, and then setting __testname__
on the function/method later on:
it "won't don't $houldn't":
pass
describe "Blah":
it "copes with 1!2@3#":
pass
becomes:
def test_wont_dont_houldnt():
pass
class Test_Blah:
def test_copes_with_123(self):
pass
test_wont_dont_houldnt.__testname__ = "won't don't $houldn't"
Test_Blah.test_copes_with_123.__testname__ = "copes with 1!2@3#"
The __testname__
attribute can then be used to print out the names of tests
when it runs them.
Note
you may prefix it
and ignore
with async
to make the
function async if the test framework you are using has the ability to run
async tests.
For example if you use asynctest
with nosetests or with pytest when you
use alt-pytest-asyncio
or pytest-asyncio
plugins.
Extra parameters
nose-of-yeti is also able to cope with making tests accept other parameters.
This is especially useful when using fixtures in pytest:
import pytest
@pytest.fixture()
def magic_number():
return 20
it "takes in the magic number", magic_number:
assert magic_number == 20
describe "Blah":
it "handles default arguments", thing=3, other=4:
assert other - thing == 1
becomes:
def test_takes_in_the_magic_number(magic_number):
assert magic_number == 20
class Test_Blah:
def test_handles_default_arguments(self, thing=3, other=4):
assert other - thing == 1
Note that it will also cope with multiline lists as default parameters:
it "has a contrived default argument", thing = [
1
, 2
, 3
]:
pass
becomes:
def test_has_a_contrived_default_argument(thing=[
1
, 2
, 3
]):
pass
setUp and tearDown
nose-of-yeti will turn before_each
and after_each
into setUp
and
tearDown
respectively.
It will also make sure the setUp
/tearDown
method of the parent class
get called as the first thing in a before_each
/after_each
:
describe "sync example":
before_each:
doSomeSetup()
after_each:
doSomeTearDown()
describe "async example":
async before_each:
doSomeSetup()
async after_each:
doSomeTearDown()
becomes:
class Test_SyncExample:
def setUp(self):
__import__("noseOfYeti").TestSetup(super()).sync_before_each(); doSomeSetup()
def tearDown(self):
__import__("noseOfYeti").TestSetup(super()).sync_after_each(); doSomeTearDown()
class Test_AsyncExample:
async def setUp(self):
await __import__("noseOfYeti").TestSetup(super()).async_before_each(); doSomeSetup()
async def tearDown(self):
await __import__("noseOfYeti").TestSetup(super()).async_after_each(); doSomeTearDown()
To ensure that line numbers between the original file and translated output are
the same, the first line of a setUp
/tearDown
will be placed on the same
line as the inserted super call. This means if you don’t want python to complain
about multiple statements on the same line or you want to define a function
inside setUp
/tearDown
, then just don’t do it on the first line of the
block:
describe "Thing":
before_each:
# Comments are put on the same line, but no semicolon is inserted
after_each:
# Blank line after the after_each
self.thing = 4
becomes:
class Test_Thing(unittest.TestCase):
def setUp(self):
__import__("noseOfYeti").TestSetup(super()).sync_before_each() # Comments are put on the same line, but no semicolon is inserted
def tearDown(self):
__import__("noseOfYeti").TestSetup(super()).sync_after_each()
# Blank line after the after_each
self.thing = 4
Anything on the same line as a before_each
/after_each
will remain on
that line:
describe "Thing":
before_each: # pylint: disable-msg: C0103
becomes:
class Test_Thing(unittest.TestCase):
def setUp(self): # pylint: disable-msg: C0103
__import__("noseOfYeti").TestSetup(super()).sync_before_each()
Line numbers
nose-of-yeti will ensure that the line numbers line up between spec files and translated output. It does this by doing the following:
As mentioned above, lines after a
before_each
orafter_each
will be placed on the same line as the inserted super call.Setting
is_noy_spec
on classes and__testname__
on tests happen at the end of the file after all the other code.