Package: pyguarantees
pyguaranteesGuarantee testcases for callables, constrain parameters and return values of callables.ProjectInfoTestsInstallation—Pippip3installpyguaranteestests"I will have to write a unittest for this function later", you say. This package ensures that you
won't forget."Why does this function fail? I've tested it... omg I didn't even call it in my TestCase." Use this
package to make sure that a function/method will be called or a class instance constructed
in its respectiveTestCase.Can be used forunittestandpytest.Example unittestimportunittestimportpyguaranteesaspgfromsome_packageimportsome_fct_with_test_guarantee@pg.testcase.guaranteed()@pg.testcase.calls()# make sure that this is called in all its testsdefadd_one(a):returna+1@pg.testcase.guaranteed()@pg.testcase.calls()# Makes sure that __init__ is called in the testclassRegularClass:def__init__(self):self.x=2classExampleTest(unittest.TestCase):@pg.testcase.covers(add_one,some_fct_with_test_guarantee)deftest_some_stuff(self):val=add_one(1)self.assertEqual(val,2)# some_fct_with_test_guarantees has no @pg.testcase.call#   -> doesn't have to be called here.@pg.testcase.covers(RegularClass)deftest_regular_class(self):regular_class=RegularClass()...if__name__=='__main__':pg.unittests.main()As in the example,pyguaranteeswill be abbreviated withpgfrom here on out.Failing to use an@pg.testcase.coversfor a function or method decorated with@pg.testcase.guaranteedleads to atg.exceptions.testcase.TestsNotImplementedError, while failing to use this function or method in the
corresponding test will lead to atg.exceptions.testcase.NotUsedInTestsErrorif it is decorated by@tg.guarantee_usage. These exceptions are only raised if theunittest.TestCaseare called first
and then checked bypg.unittests.enforce, orpg.unittests.mainis called to do both automatically.Currently doesn't work with nested functions (defined inside of other callables). This might be fixed at some point.Example pytest# These imports are unused but necessary for pytest to find the tests that#  enforce the guarantees from tgfrompyguarantees.pytestsimporttest_all_tests_implemented,test_functions_used_in_testsimportpyguaranteesaspgclassExampleClass:@pg.testcase.guaranteed()# tg.main will raise exception if there is not test for this methoddefmethod(self):returnself@classmethod# works for classmethods@pg.testcase.guaranteed()# @pg.testcase.calls possible in any of these methods, but optionaldefclass_method(cls):returncls@staticmethod# works for staticmethods@pg.testcase.guaranteed()@pg.testcase.calls()defstatic_method():return"static!"@pg.testcase.covers(ExampleClass.method,ExampleClass.class_method,ExampleClass.static_method)deftest_example_class():assertExampleClass.static_method()=="static!"This is even simpler: just use@pg.testcase.guaranteed,@pg.testcase.calls,
and@pg.testcase.coversas inExample unittest. No
need to callpg.unittests.mainortg.enforce; instead, import (but don't use)pg.pytests.test_all_tests_implementedandpg.pytests.test_functions_used_in_testsand then runpytest.IMPORTANT: If you usepytest.mark.orderfrom thepytest-order-package,
don't usepytest.mark.order(-1)orpytest.mark.order(-2)on your tests—it is important thattest_all_tests_implementedandtest_functions_used_in_testsare used last bypytest.DecoratorsThe three decorators shown below have no effect without thefunctionsof this package, specifically runningmainorenforceforunittest,
and simply importingtest_all_tests_implementedandtest_functions_used_in_testsinto one of your test-files.guaranteedTakes no arguments.Any function, method, or class (except, for the moment, ones nested inside of other callables)
decorated with@pg.testcase.guaranteedthat is in the scope of unittest will force unittest to throw an exception should
it not be in an@pg.testcase.covers.Currently, it is necessary to include the brackets—()— so that
the function is registered. This executes the decorator once but not the callable that
it decorates, making it computationally inexpensive.Having a function (or method) decorated like follows:@pg.testcase.guaranteed()deffoo():passbut not having a test in yourunittest.TestCasedecorated by@pg.testcase.covers(foo)would lead to aTestsNotImplementedErrorbeing raised.The same works for classes.callsTakes no arguments.Must be used below@pg.testcase.guaranteed, otherwise it is ignored.Just like with@pg.testcase.guaranteed, brackets are not optional, but the execution of
the decorator is computationally inexpensive.A function decorated as follows:@pg.testcase.guaranteed()@pg.testcase.calls()deffoo():passwith a unittest that looks something like this:# for unittest:classTestExample(unittest.TestCase):@pg.testcase.covers(foo)deftest_foo(self):...# some code that doesn't call foo# for pytest:@pg.testcase.covers(foo)deftest_foo():...# some code that doesn't call foowould lead to aNotUsedInTestsErrorbeing raised.In this scenario, iffoois an argument in several@pg.testcase.covers,@pg.testcase.callsmakes certain thatfoois used in every test-function
decorated in such a way.Special case: For classes,@pg.testcase.callsguarantees that__init__is called.
For a callable class, this still holds; to guarantee that the__call__-method is called
in the test, it has to be decorated by@pg.testcase.callsitself, not the class it belongs to.coversargs: Give any function or method that the corresponding test is meant for.kwargs: The value will be used like anarg, while the key will be ignored.Functions and methods that weren't decorated by@pg.testcase.guaranteedlead to a user-warning but are ignored otherwise.Usage might look as follows:# for unittest:classTestExample(unittest.TestCase):@pg.testcase.covers(function1,function2,this_key_is_ignored=function3)deftest_example(self):...# for pytest:@pg.testcase.covers(function1,function2,this_key_is_ignored=function3)deftest_example():...FunctionsForunittest, at least one ofmainorenforcehas to be used for thedecoratorsto have an effect.Forpytest, bothtest_all_tests_implementedandtest_functions_used_in_testshave to be imported
into one of your test-files.enforceTakes no arguments. This will likely change in the future to make it more adaptable.Run this after running all your unittests. This runs additional unittests that
check which functions violated their guarantees and raiseexceptionsaccordingly.It is recommended to only use this function when using a complicated unittest-setup.
When usingunittest.main(), it is recommended to usepg.testcase.main()instead.mainTakes no arguments. This will likely change in the future to make it more adaptable.Callsunittest.main()followed bypg.unittests.enforce.test_all_tests_implementedTakes no arguments.Import this into one of your files for@pg.testcase.guaranteedand@pg.testcase.coversto have any effect.test_functions_used_in_testsTakes no arguments.Import this andtest_all_tests_implementedinto one of your files for@pg.testcase.guaranteedto
have any effect.exceptions.testcaseExceptionsare located underpg.exceptions.testcase.There are two customExceptionsas presented below.TestsNotImplementedErrorArguments oftg.exceptions.testcase.TestsNotImplementedError:functions(typecallable): The callables that weren't mentioned in a@pg.testcase.covers.Members oftg.exceptions.testcase.TestsNotImplementedError:functions(typecallable): The callables that weren't mentioned in a@pg.testcase.covers.description(typestr): The error string printed when the exception is raised
and not caught.The output of raising this exception might look something like:<Traceback...>

pyguarantees.exceptions.testcase.TestsNotImplementedError: 

    No tests were implemented for the following methods and functions: 

    1. Missing test-case for the following callable: 
        Name: 		foo
        Module: 	__main__
    2. Missing test-case for the following callable: 
        Name: 		bar
        Module: 	__main__NotUsedInTestsErrorArguments oftg.exceptions.testcase.NotUsedInTestsError:functions(type:callable): The callables that were mentioned in a@pg.testcase.coversbut not used in the corresponding
test.Members oftg.exceptions.NotUsedInTestsError:functions(type:callable): The callables that were mentioned in a@pg.testcase.coversbut not used in the corresponding
test.description(type:str): The error string printed when the exception is raised
and not caught.A possible error message might look like the following:<Traceback...>

pyguarantees.exceptions.testcase.NotUsedInTestsError:

The following objects were not used in their assigned tests: 

1. The following callable was not called in its assigned tests: 
	Name: 		foo
	Module: 	__main__
	This callable is tested but not called in the following test-cases: 
				- Name: 	TestFoo.test_foo1
				   Module: 	__main__
				- Name: 	TestFoo.test_foo2
				   Module: 	__main__
2. The following callable was not called in its assigned tests: 
	Name: 		bar
	Module: 	some_module
	This callable is tested but not called in the following test-cases: 
				- Name: 	TestBar.test_bar
				   Module: 	test_some_moduleconstraintsFew things are more useful in programming than the ability to constrain a program's possible behaviors
and communicate those constraints clearly in code. Statically typed languages do this with types, scope modifiers,
and lifetime modifiers, among others (int,static,private,const, etc.). These are static constraints
in that they are evaluated statically, before runtime.Oftentimes, a program also has dynamic constraints, evaluated during runtime—assertions, for example.
A function dealing with division, for example, has to deal with the special case of division by zero.pyguarantees, abbreviated bypgagain, enables both types of guarantees to be
defined in Python where they should happen: function (or method) signatures. This is where statically typed
languages put their static constraints (a typical function signature looks something likescope-etc-modifiers return-type function-name(parameter-type parameter-name)) and where in my opinion, dynamic
constraints belong as well.This might have the following advantages:Make code more readable by having constraints in a predefined place.Make code easier to write by providing important information about APIs in a glancable way.Make it possible to include information on dynamic constraints in automatically generated documentation.Encourage programmers to think about these constraints while writing the functions—a type of
test-driven development directly at the function (seeing parts of the "tests" in the function-signature
might assist readability of code, as well).This package is an attempt to open up at least some of these advantages to Python-users at least partially,
given the constraints of the Python-language.Exampleimportnumpyasnpimportpyguaranteesaspgfrompyguarantees.constraintsimportIsInt,IsClass,DynamicCheckfromyour_moduleimportyour_custom_error_callback# One of many built-in guarantees using one of many built-in options@pg.constrain.parameters(num=IsInt(minimum=3))defadd_one(num):returnnum+1# Use IsClass to guarantee all types and classes that don't have specific constraints#  in pg.constraints. If they do, it is recommended to use those specific constraints.@pg.constrain.parameters(X=IsClass(class_type=np.ndarray,dynamic_checks=[DynamicCheck(check=lambdax:x.min()>0,description="min: 0"),DynamicCheck(check=lambdax:x.var()<5,description="var < 5"),DynamicCheck(check=lambdax:x.shape==(3,80,80),description="shape (3, 80, 80")],error_callback=your_custom_error_callback),mean=IsClass(class_type=np.ndarray),std=IsClass(class_type=np.ndarray))@pg.constrain.returns(IsClass(class_type=np.ndarray))defnormalize(X,mean,std):return(X-mean)/stdthis README is currently under development. More is coming.