Package: unittest-specs
unittest_specsTable of contentFunSpecSimpleFlatSpecWithSpecFunSpecThis style can be used to write highly readable test cases helpful for good documentation in code.Due to itslimitations, this style is mostly suited for compact unit tests, since no elaborate
test structure can be written.UsageFunSpecprovides the three functionsdescribe(),it()andexpect()to constructRSpecinspired test cases,
which might feel more familiar to e.g. TypeScript developers. Specifications written this way are picked up byunittest's auto-discovery (python3 -m unittest <directory>), since they are converted intounittest.TestCasesub-classes and are therefore validunittesttests.Setting up test cases is pretty straight forward:fromunittest_specsimportdescribe,it,expectdescribe("Python numbers",it("5 should be of type int",expect(5).to_be_of_type(int)))Above example dynamically creates a type equivalent to:importunittestclassPythonNumbers(unittest.TestCase):deftest_5_should_be_of_type_int(self):self.assertIsInstance(5,int)The constructed type is hooked into the executing module (the one callingdescribe()), so that unittest
picks it up while performing its auto-discovery.For everydescribe()block an arbitrary amount ofit()statements can be defined, with each resulting in a
separate test function. Keep in mind that eachit()should have a description unique to itsdescribe()block
(seeLimitationsfor further detail). This might look something like this:fromunittest_specsimportdescribe,it,expectdescribe("Python arithmatic",it("should multiply correctly",expect(2*5).to_be(10)),it("should square correctly",expect(4**2).to_be(16)),it("should subtract correctly",expect(1340-3).to_be(1337)),)Which, again, translates to an equivalent of:importunittestclassPythonArithmatic(unittest.TestCase):deftest_should_multiply_correctly(self):self.assertEqual(2*5,10)deftest_should_square_correctly(self):self.assertEqual(4**2,16)deftest_should_subtract_correctly(self):self.assertEqual(1340-3,1337)LimitationsDescriptions and IdentifiersThe descriptions provided indescribe()andit()are used to dynamically generate test classes based onunittest.TestCase, with the description specified indescribe()being converted into the class name and all
followingit()descriptions being used for test function names. Since both are identifiers, which Python requires
to be unique in their appropriate context, some unexpected behaviour might occur when violating this requirement.it() and BlocksAs of now, there is no way to specify anonymous blocks in Python syntax. This means that, unfortunately, in the
current implementation, every test case specified init()needs to only consist of one single evaluation.
Therefore, the recommended usage is to utilizeexpect()and its assertions to formulate the test.SimpleFlatSpecThe simple flat spec style is visually very close to plainunittest.TestCasecode and can, in fact be used
as a drop-in replacement. Its main advantage is a set of more readable assertions, as well as some utility
decorators.UsageTheSimpleFlatSpecclass provides an easy and readable way to write tests. Much like inunittestitself, each test case should be specified in a separate test function with a name
outlining the expected behaviour. In the function body, any required setup can be performed, before
callingself.expect()to provide the computed actual value. This declaration should be followed by
an assertion, of which there is a range to choose from (see the following section for a complete list).A very simple test case could look something like this:fromunittest_specsimportSimpleFlatSpecclassMyTest(SimpleFlatSpec):deftest_should_identify_even_number(self):is_even_number=4%2==0self.expect(is_even_number).to_be_true()Available AssertionsAssertion FunctionDescriptionEquivalent unittest functionto_be()checks for equality between actual and expected valueassertEqual()to_not_be()checks for inequality between actual and expected valueassertNotEqual()to_be_of_type()checks whether the actual value is of a specified typeassertIsInstance()to_equal_list()checks an actual and expected list for equalityassertListEqual()to_contain()checks whether the actual collection contains an expected valueassertTrue(expected_element in self._actual_value)to_contain_all()checks whether the actual collection contains all expected valuescustom implementationto_be_true()checks whether the actual value is of valueTrueassertTrue()to_be_false()checks whether the actual value is of valueFalseassertFalse()to_be_none()checks whether the actual value isNoneassertIsNone()to_not_be_none()checks whether the actual value is notNoneassertIsNotNone()to_be_a_list()checks whether the actual value is of typelistassertIsInstance(actual_value, list)to_be_a_dict()checks whether the actual value is of typedictassertIsInstance(actual_value, dict)to_be_a_set()checks whether the actual value is of typesetassertIsInstance(actual_value, set)to_raise()intercepts anExceptionexpected to be raised in the test functionassertRaises()to_be_of_length()checks for an expected length of an object supporting len()assertEqual(len(actual_value), expected_value)Parameterizing TestsInstead of writing several test function covering the same functionality with different parameters, one can use
parameterization to provide an arbitrarily long list of test data sets. This is implemented using theunittest.TestCase.subTestfunction, with the first element in each being used as the label.A parameterized test might look like the following:fromunittest_specsimportSimpleFlatSpecclassMyTest(SimpleFlatSpec):@SimpleFlatSpec.parameterize(params=[('hello',5),('world!',6),])deftest_should_check_string_length(self,given_string,expected_length):self.expect(given_string).to_be_of_length(expected_length)WithSpecTheWithSpecprovides a structure to set up multi-step test scenarios optionally including setup and/or teardown.UsageSimilar toFunSpecWithSpecalso utilizes a strongly modified DSL to construct test cases. They, too,
are dynamically converted into validunittesttest cases and executed by the  auto-discovery
(python3 -m unittest <directory>). AWithSpectest scenario does not require any class to be set up.There are four building blocks that help you constructWithSpectest scenarios:setupWith the optionalsetupstep(s) one or more actions can be defined in order to set up the
actual test run. For instance, if your test case is to delete a table row, you might want to create it
first.runThis is the only mandatory step for a test scenario. Sets up the actual action to be tested.assertionOptionally run an assertion statement after therunstep. This step is compatible withunittest.expect, but instead of passing a value, a value providing lambda needs to be passed
instead, since Python always evaluates function arguments eagerly.WithSpecenforces the philosophy of only having a single assertion statement per test case. Therefore
trying to define multipleassertionsteps in the same scenario will result in an exception.teardownIn case your test case produces any side effects, you might want to clean them up afterwards. This
can be done by defining one or moreteardownsteps. Since not every test needs a cleanup, this step
is optional.A complete scenario might look something like this:fromunittest_specsimportscenario,setup,run,teardown,assertion,expectclassAdder:def__init__(self):self.__result=0defperform_addition(self,a,b):self.__result=a+bdefreset_result(self):self.__result=0@propertydefresult(self):returnself.__resultwithscenario("adding 1 and 4 together results in 5")astest_scenario:adder=Adder()test_scenario@setup<<(lambda:adder.reset_result())test_scenario@run<<(lambda:adder.perform_addition(1,4))test_scenario@assertion<<expect(lambda:adder.result).to_be(5)test_scenario@teardown<<(lambda:adder.reset_result())