Package: exam
ExamExam is a Python toolkit for writing better tests.  It aims to remove a lot of the boiler plate testing code one often writes, while still following Python conventions and adhering to the unit testing interface.InstallationA simplepip install examshould do the trick.RationaleAside from the obvious “does the code work?”, writings tests has many additional goals and benefits:If written semantically, reading tests can help demonstrate how the code is supposed to work to other developers.If quick running, tests provide feedback during development that your changes are working or not having an adverse side effects.If they’re easy to write correctly, developers will write more tests and they will be of a higher quality.Unfortunately, the common pattern for writing Python unit tests tends to not offer any of these advantages.  Often times results in inefficient and unnecessarily obtuse testing code.  Additionally, common uses of themocklibrary can often result in repetitive boiler-plate code or inefficiency during test runs.examaims to improve the state of Python test writing by providing a toolkit of useful functionality to make writing quick, correct and useful tests and as painless as possible.UsageExam features a collection of useful modules:exam.decoratorsExam has some useful decorators to make your tests easier to write and understand.  To utilize the@before,@after,@aroundand@patcherdecorators, you must mixin theexam.cases.Examclass into your test case.  It implements the appropriatesetUp()andtearDown()methods necessary to make the decorators work.Note that the@fixturedecorator works without needing to be defined inside of an Exam class.  Still, it’s a best practice to add theExammixin to your test cases.All of the decorators inexam.decorators, as well as theExamtest case are available for import from the mainexampackage as well. I.e.:fromexamimportExamfromexamimportfixture,before,after,around,patcherexam.decorators.fixtureThe@fixturedecorator turns a method into a property (similar to the@propertydecorator, but also memoizes the return value). This lets you reference the property in your tests, i.e.self.grounds, and it will always reference the exact same instance every time.fromexam.decoratorsimportfixturefromexam.casesimportExamclassMyTest(Exam,TestCase):@fixturedefuser(self):returnUser(name='jeff')deftest_user_name_is_jeff(self):assertself.user.name=='jeff'As you can see,self.userwas used to reference theuserproperty defined above.If all your fixture method is doing is constructing a new instance of type or calling a class method, exam provides a shorthand inlinefixturesyntax for constructing fixture objects.  Simply set a class variable equal tofixture(type_or_class_method)and exam will automatically call your type or class method.fromexam.decoratorsimportfixturefromexam.casesimportExamclassMyTest(Exam,TestCase):user=fixture(User,name='jeff')deftest_user_name_is_jeff(self):assertself.user.name=='jeff'Any*argsor**kwargspassed tofixture(type_or_class_method)will be passed to thetype_or_class_methodwhen called.exam.decorators.beforeThe@beforedecorator adds the method to the list of methods which are run as part of the class’ssetUp()routine.fromexam.decoratorsimportbeforefromexam.casesimportExamclassMyTest(Exam,TestCase):@beforedefreset_database(self):mydb.reset()@beforealso hooks works through subclasses - that is to say, if a parent class has a@beforehook in it, and you subclass it and define a 2nd@beforehook in it, both@beforehooks will be called.  Exam runs the parent’s@beforehook first, then runs the childs’.  Also, if your override a@beforehook in your child class, the overridden method is run when the rest of the child classes@beforehooks are run.For example, with hooks defined as such:fromexam.decoratorsimportbeforefromexam.casesimportExamclassMyTest(Exam,TestCase):@beforedefreset_database(self):print'parent reset_db'@beforedefparent_hook(self):print'parent hook'classRedisTest(MyTest):@beforedefreset_database(self):print'child reset_db'@beforedefchild_hook(self):print'child hook'When Exam runs these hooks, the output would be:"prent hook""child reset_db""child hook"As you can see even though the parent class defines areset_database, because the child class overwrote it, the child’s version is run instead, and also at the same time as the rest of the child’s@beforehooks.@beforehooks can also be constructed with other functions in your test case, decorating actual test methods.  When this strategy is used, Exam will run the function@beforeis constructed with before running that particular test method.fromexam.decoratorsimportbefore,fixturefromexam.casesimportExamfrommyappimportUserclassMyTest(Exam,TestCase):user=fixture(User)@beforedefcreate_user(self):self.user.create()defconfirm_user(self):self.user.confirm()@before(confirm_user)deftest_confirmed_users_have_no_token(self):self.assertFalse(self.user.token)deftest_user_display_name_exists(self):self.assertTrue(self.user.display_name)In the above example, theconfirm_usermethod is run immediately before thetest_confirmed_users_have_no_tokenmethod, butnotthetest_user_display_name_existsmethod.  The@beforeglobally decoratedcreate_usermethod still runs before each test method.@beforecan also be constructed with multiple functions to call before running the test method:classMyTest(Exam,TestCase):@before(func1,func2)deftest_does_things(self):does_things()In the above example,func1andfunc2are called in order beforetest_does_thingsis run.exam.decorators.afterThe compliment to@before,@afteradds the method to the list of methods which are run as part of the class’stearDown()routine. Like@before,@afterruns parent class@afterhooks before running ones defined in child classes.fromexam.decoratorsimportafterfromexam.casesimportExamclassMyTest(Exam,TestCase):@afterdefremove_temp_files(self):myapp.remove_temp_files()exam.decorators.aroundMethods decorated with@aroundact as a context manager around each test method.  In your around method, you’re responsible for callingyieldwhere you want the test case to run:fromexam.decoratorsimportaroundfromexam.casesimportExamclassMyTest(Exam,TestCase):@arounddefrun_in_transaction(self):db.begin_transaction()yield# Run the testdb.rollback_transaction()@aroundalso follows the same parent/child ordering rules as@beforeand@after, so parent@aroundswill run (up until theyieldstatement), then child@around``swill run.  After the test method has finished, however, the rest of the child's``@aroundwill run, and then the parents’.  This is done to preserve the normal behavior of nesting with context managers.exam.decorators.patcherThe@patcherdecorator is shorthand for the following boiler plate code:frommockimportpatchdefsetUp(self):self.stats_patcher=patch('mylib.stats',new=dummy_stats)self.stats=self.stats_patcher.start()deftearDown(self):self.stats_patcher.stop()Often, manually controlling a patch’s start/stop is done to provide a test case property (here,self.stats) for the mock object you are patching with.  This is handy if you want the mock to have default behavior for most tests, but change it slightly for certain ones – i.e absorb all calls most of the time, but for certain tests have it raise an exception.Using the@patcherdecorator, the above code can simply be written as:fromexam.decoratorsimportpatcherfromexam.casesimportExamclassMyTest(Exam,TestCase):@patcher('mylib.stats')defstats(self):returndummy_statsExam takes care of starting and stopping the patcher appropriately, as well as constructing thepatchobject with the return value from the decorated method.If you’re happy with the default constructed mock object for a patch (MagicMock), thenpatchercan simply be used as an inline as a function inside the class body.  This method still starts and stops the patcher when needed, and returns the constructedMagicMockobject, which you can set as a class attribute.  Exam will add theMagicMockobject to the test case as an instance attribute automatically.fromexam.decoratorsimportpatcherfromexam.casesimportExamclassMyTest(Exam,TestCase):logger=patcher('coffee.logger')exam.decorators.patcher.objectThepatcher.objectdecorator provides the same features as thepatcherdecorator, but works with patching attributes of objects (similar to mock’smock.patch.object).  For example, here is how you would use patcher to patch theobjectsproperty of theUserclass:fromexam.decoratorsimportpatcherfromexam.casesimportExamfrommyappimportUserclassMyTest(Exam,TestCase):manager=patcher.object(User,'objects')As with the vanillapatcher, in your test case,self.managerwill be the mock object thatUser.objectswas patched with.exam.helpersThehelpersmodule features a collection of helper methods for common testing patterns:exam.helpers.trackThetrackhelper is intended to assist in tracking call orders of independent mock objects.trackis called with kwargs, where the key is the mock name (a string) and the value is the mock object you want to track.trackreturns a newly constructedMagicMockobject, with each mock object attached at a attribute named after the mock name.For example, belowtrack()creates a new mock withtracker.cool` as the ``cool_mockandtracker.heatas theheat_mock.fromexam.helpersimporttrack@mock.patch('coffee.roast.heat')@mock.patch('coffee.roast.cool')deftest_roasting_heats_then_cools_beans(self,cool_mock,heat_mock):tracker=track(heat=heat_mock,cool=cool_mock)roast.perform()tracker.assert_has_calls([mock.call.heat(),mock.call.cool()])exam.helpers.rm_fThis is a simple helper that just removes all folders and files at a path:fromexam.helpersimportrm_frm_f('/folder/i/do/not/care/about')exam.helpers.mock_importRemoves most of the boiler plate code needed to mock imports, which usually consists of making apatch.dictfromsys.modules.  Instead, thepatch_importhelper can simply be used as a decorator or context manager for when certain modules are imported.fromexam.helpersimportmock_importwithmock_import('os.path')asmy_os_path:importos.pathasimported_os_pathassertmy_os_pathisimported_os_pathmock_importcan also be used as a decorator, which passed the mock value to
the testing method (like a normal@patch) decorator:fromexam.helpersimportmock_import@mock_import('os.path')deftest_method(self):importos.pathasimported_os_pathassertmy_os_pathisimported_os_pathexam.helpers.effectHelper class that is itself callable, whose return values when called are configured via the tuples passed in to the constructor. Useful to buildside_effectcallables for Mock objects. Raises TypeError if called with arguments that it was not configured with:>>> from exam.objects import call, effect
>>> side_effect = effect((call(1), 'with 1'), (call(2), 'with 2'))
>>> side_effect(1)
'with 1'
>>> side_effect(2)
'with 2'Call argument equality is checked via equality (==) of thecall`object, which is the 0th item of the configuration tuple passed in to theeffectconstructor.  By default,callobjects are justmock.callobjects.If you would like to customize this behavior, subclasseffectand redefine your owncall_classclass variable.  I.e.classmyeffect(effect):call_class=my_call_classexam.mockExam has a subclass of the normalmock.Mockobject that adds a few more useful methods to your mock objects.  Use it in place of a normalMockobject:fromexam.mockimportMockmock_user=Mock(spec=User)The subclass has the following extra methods:assert_called()- Asserts the mock was called at least once.assert_not_called()- Asserts the mock has never been called.assert_not_called_with(*args,**kwargs)- Asserts the mock was not most recently called with the specified*argsand**kwargs.assert_not_called_once_with(*args,**kwargs)- Asserts the mock has only every been called once with the specified*argsand**kwargs.assert_not_any_call(*args,**kwargs)- Asserts the mock has never been called with the specified*argsand**kwargs.exam.fixturesHelpful fixtures that you may want to use in your tests:exam.fixtures.two_px_square_image- Image data as a string of a 2px square image.exam.fixtures.one_px_spacer- Image data as a string of a 1px square spacer image.exam.objectsUseful objects for use in testing:exam.objects.noop- callable object that always returnsNone. no matter how it was called.exam.assertsTheassertsmodule contains anAssertsMixinclass, which is mixed into the mainExamtest case mixin.  It contains additional asserts beyond the ones in Python’sunittest.assertChangesUsed when you want to assert that a section of code changes a value.  For example, imagine if you had a function that changed a soldier’s rank.To properly test this, you should save that soldier’s rank to a temporary variable, then run the function to change the rank, and then finally assert that the rank is the new expected value, as well asnotthe old value:test_changes_rank(self):old_rank=self.soldier.rankpromote(self.soldier,'general')self.assertEqual(self.soldier.rank,'general')self.assertNotEqual(self.soldier.rank,old_rank)Checking the old rank is not the same is the new rank is important.  If, for some reason there is a bug or something to whereself.soldieris created with the rank ofgeneral, butpromoteis not working, this test would still pass!To solve this, you can use Exam’sassertChanges:deftest_changes_rank(self):withself.assertChanges(getattr,self.soldier,'rank',after='general'):promote(self.soldier,'general')This assert is doing a few things.It asserts that the rank once the context is run is the expectedgeneral.It asserts that the contextchangesthe value ofself.soldier.rank.It doesn’t actually care what the old value ofself.soldier.rankwas, as long as it changed when the context was run.The definition ofassertChangesis:defassertChanges(thing,*args,**kwargs)You pass it athing, which which be a callable.assertChangesthen calls yourthingwith any*argsand**kwargsadditionally passed in and captures the value as the “before” value.The context is run, and then the callable is captured again as the “after” value.If before and after are not different, anAssertionErroris raised.Additionally, if the special kwargbeforeorafterare passed, those values are extracted and saved.  In this case anAssertionErrorcan also be raised if the “before” and/or “after” values provided do not match their extracted values.assertDoesNotChangeSimilar toassertChanges,assertDoesNotChangeasserts that the code inside the context does not change the value from the callable:deftest_does_not_change_rank(self):withself.assertDoesNotChange(getattr,self.soldier,'rank'):self.soldier.march()UnlikeassertChanges,assertDoesNotChangedoes not takebeforeorafterkwargs.  It simply asserts that the value of the callable did not change when the context was run.LicenseExam is MIT licensed.  Please see theLICENSEfile for details.