-
Notifications
You must be signed in to change notification settings - Fork 383
unittesting_unit_examples
This article outlines simple examples of various test units along with their unit syntax, as well as advice on when to use which unit.
This simple unit should be the go-to approach for testing functions.
def foo():
return "bar"
@pytest.mark.describe(".foo()")
class TestFoo(object):
@pytest.mark.it("Returns 'bar'")
def test_returns_bar(self):
assert foo() == "bar"
This should only be used if there are sufficiently many differences in behavior of the function given a specific condition. For most situations, a regular function unit is more appropriate - only use this pattern if the differences are sufficiently large that they are, fundamentally, different units.
def foo(mode):
if mode == "mode1":
do_thing1()
do_thing2()
elif mode == "mode2":
do_thing3()
do_thing4()
@pytest.mark.describe(".foo() -- Using mode1")
class TestFooMode1(object):
@pytest.mark.it("Does thing1")
def test_thing_1(self, thing1):
foo("mode1")
assert thing1.done == True
@pytest.mark.it("Does thing2")
def test_thing_2(self, thing2):
foo("mode1")
assert thing2.done == True
@pytest.mark.describe(".foo() -- Using mode2")
class TestFooMode2(object):
@pytest.mark.it("Does thing3")
def test_thing_3(self, thing3):
foo("mode2")
assert thing3.done == True
@pytest.mark.it("Does thing4")
def test_thing_4(self, thing4):
foo("mode2")
assert thing4.done == True
This test unit should be applied in order to test various aspects of an instance of a class.
Beware: This unit should ONLY be applied to items that are semantically tightly coupled to an instance of a class. Methods that "do" something would be part of a separate unit. Generally this would apply only to dunder methods, instance attributes, etc.
class Foo(object):
def __init__(self):
self._value = "buzz"
def __repr__(self):
return "foo"
@property
def value(self):
return self._value
@pytest.mark.describe("Foo")
class TestFoo(object):
@pytest.mark.it("Instantiates with the .value attribute set to 'buzz'")
def test_set_value_on_instantitation(self):
f = Foo()
assert f.value == "buzz"
@pytest.mark.it("Maintains the .value attribute as a read-only property")
def test_modify_value(self):
f = Foo()
with pytest.raises(AttributeError):
f.value = "newval"
@pytest.mark.it("Returns 'foo' as a string representation of an instance")
def test_str_rep(self):
f = Foo()
assert str(f) == "foo"
Sometimes, it may be convenient to split up the aspects encompassed by the unit above in order to more narrowly define a unit (especially if the only units that would be covered by a class unit all belong to a specific aspect - e.g instantiation)
class Foo(object):
def __init__(self, v):
self.bar = "bar"
self.buzz = "buzz"
self.value = v
@pytest.mark.describe("Foo - Instantiation")
class TestFooInstantiation(object):
@pytest.mark.it("Sets the .bar attribute to 'bar'")
def test_bar(self):
f = Foo("val")
assert f.bar == "bar"
@pytest.mark.it("Sets the .buzz attribute to 'buzz'")
def test_buzz(self):
f = Foo("val")
assert f.buzz == "buzz"
@pytest.mark.it("Sets the .value attribute to the value of the parameter v")
def test_value(self):
v = "val"
f = Foo(v)
assert f.value == v
This unit is used in order to cover any handler methods of a class that respond to an externally triggered event.
class Foo(object):
def __init__(self):
self.httpclient = HttpClient()
self.httpclient.on_request_received = self._handle_request
self.request_counter = 0
def _handle_request(self, request):
self.request_counter += 1
request.is_completed = True
@pytest.mark.describe("Foo - OCCURANCE: HTTP Request Received")
class TestFooHttpRequestReceived(object):
@pytest.mark.it("Increments the request counter")
def test_increments_counter(self):
f = Foo()
assert f.request_counter == 0
f.httpclient.on_request_received(Request())
assert f.request_counter == 1
@pytest.mark.it("Marks the request object as completed")
def test_marks_request_completed(self):
f = Foo()
r = Request()
assert not r.is_completed
f.httpclient.on_request_received(r)
assert r.is_completed
This simple unit should be the go-to approach for testing methods on a class or instance of a class.
class Foo(object):
def buzz(self):
return "buzz"
@pytest.mark.describe("Foo - .buzz()")
class TestFooBuzz(object):
@pytest.mark.it("Returns 'buzz'")
def returns_buzz(self):
f = Foo()
assert f.buzz() == "buzz"
This should only be used if there are sufficiently many differences in behavior of the method given a specific condition. For most situations, a regular method unit is more appropriate - only use this pattern if the differences are sufficiently large that they are, fundamentally, different units.
class Foo(object):
def bar(mode):
if mode == "mode1":
do_thing1()
do_thing2()
elif mode == "mode2":
do_thing3()
do_thing4()
@pytest.mark.describe("Foo - .bar() -- Using mode1")
class TestFooBarMode1(object):
@pytest.mark.it("Does thing1")
def test_thing_1(self, thing1):
foo = Foo()
foo.bar("mode1")
assert thing1.done == True
@pytest.mark.it("Does thing2")
def test_thing_2(self, thing2):
foo = Foo()
foo.bar("mode1")
assert thing2.done == True
@pytest.mark.describe("Foo - .bar() -- Using mode2")
class TestFooBarMode2(object):
@pytest.mark.it("Does thing3")
def test_thing_3(self, thing3):
foo = Foo()
foo.bar("mode2")
assert thing3.done == True
@pytest.mark.it("Does thing4")
def test_thing_4(self, thing4):
foo = Foo()
foo.bar("mode2")
assert thing4.done == True