Package: vampytest
VampytestVampytest is a testing framework that allows, but is not limited to, writing relative import based tests.Table of contentsIntroductionInstallationWriting testsRunning testsFeaturesAssertionsEquality assertionsBoolean assertionsContainer assertionsIdentity assertionsException assertionsType assertionsReversed assertionsParameterized testsReturning and raising testsSkipping testsReversing test resultsGarbage collectionCapturing outputAdvanced featuresTesting environmentsIntroductionHere are several advantages why you would want to use a relative import supporting testing framework such as vampytest:Simplicity and ease of useRelative import based tests simplifies the process of writing and executing tests.
Relative imports provide a straightforward and intuitive way to import and utilize testing functionalities without
the need for complex configuration or setup.
Developers can quickly get started with writing tests and running them, making the testing process more accessible
and efficient.Seamless integration with codebaseBy using relative imports the testing framework seamlessly integrates with the codebase being tested.
It allows the developers to import and test modules or components in a manner that reflects the same import
structure used in the actual code. This integration enhances readability and maintainability.Encourages modular and isolated testingRelative import based testing promotes modular and isolated testing practices.
It's encouraging the testing of individual units in isolation.
This isolation makes it easier to pinpoint issues, debug problems, and maintain the codebase.
It also supports the principles of unit testing, where individual units of code are tested independently for their
expected behavior.Enhanced collaboration and teamworkRelative import based testing facilitate collaboration among team members.
By utilizing the same import structure as the codebase, it allows multiple developers to work on tests concurrently
without conflicts. It makes coordinating testing efforts easier within a team.Easy test refactoringWhen refactoring or restructuring the codebase, relative import based testing can make it easier to refactor tests
as well.
Since the test code closely follows the structure of the codebase, the required changes in test imports are often
straightforward, resulting in less effort and potential errors during refactoring.Ease of migration and adoptionIf a codebase already follows a relative import structure, using a testing framework that supports them too
simplifies the adoption and integration of testing practices.
Developers can directly use relative imports in their test code, resulting in a smoother transition to the
testing framework.[↑ Back to top ↑]InstallationTo install vampytest, you can use pip. Open your terminal and run the following command:python3-mpipinstallvampytestOnce installed you are ready to run your tests with vampytest.Writing testsCreate a directory for your test suite in your project.
The directory should be calledtestsor havetest_ortests_prefix.Create a new python file. The file should havetest_prefix for example:test_example.py.In the file import the necessary libraries includingvampytest.Write test functions within the file. Each test function should start with the prefixtest_.importvampytestdeftest_addition():vampytest.assert_eq(2+2,4)deftest_subtraction():vampytest.assert_eq(5-3,2)To check whether the actual behavior matches the expected behavior useassert_...functionalities such asassert_eq.
Using python builtin options such asassertworks as well, but the report will not be as detailed.Running testsTo run the tests, navigate to the directory where your project is located using the terminal.
Then enter the following command:vampytestIf the project has a setup file, it will detect which are your project's directories and import its files beforehand.
If you do not have a file like that, you might want to navigate into your project or pass the target path where the
tests should be loaded from:vampytest*directory*By navigating into a specific directory it is possible to limit the test lookup only to the directories / files under
it.
The same can be achieved by passing the path to the location.vampytest*directory/sub_directory/etc*Note: To test vampytest itselfvampytestcommand wont work. Usepython3 -m vampytestinstead.[↑ Back to top ↑]FeaturesAssertionsVampytest provides a rich set of assertion methods to validate expected behavior and outcomes.
These assertions include checking for equality, inequality, truthiness, exceptions, containment, and more.Here is a list of available assertions:NameAliasesassert_eqassert_equalsassert_falseassert_notassert_inassert_containsassert_instanceN / Aassert_isassert_id,assert_identicalassert_is_notassert_not_id,assert_not_identical,assert_not_isassert_neassert_not_eq,assert_not_equalsassert_not_inassert_not_containsassert_raisesN / Aassert_subtypeN / Aassert_trueassert_Here are them in examples:Equality assertions# Asserts a == bvampytest.assert_eq(a,b)# Asserts c != dvampytest.assert_ne(c,d)Boolean assertions# Asserts bool(e)vampytest.assert_true(e)# Asserts not bool(f)vampytest.assert_false(f)Container assertions# Asserts g in hvampytest.assert_in(g,h)# Asserts i not in jvampytest.assert_not_in(i,j)Identity assertions# Asserts k is lvampytest.assert_is(k,l)# Asserts m is not nvampytest.assert_is_not(m,n)Exception assertions# Asserts that the code inside the context manager raises the defined exceptionwithvampytest.assert_raises(ValueError):raiseValueError# Asserts that the code inside the context manager raises an equal exception as the defined one.withvampytest.assert_raises(ValueError('aya')):raiseValueError('aya')# Also asserts whether the given condition is satisfiedwithvampytest.assert_raises(ValueError,where=lambdae:'aya'inrepr(e)):raiseValueError('ayaya')Type assertions# Asserts isinstance(o, str)vampytest.assert_instance(o,str)# Asserts q is None or isinstance(q, str)vampytest.assert_instance(q,str,nullable=True)# Asserts type(r) is strvampytest.assert_instance(r,str,accept_subtypes=False)# Asserts isinstance(s, type) and issubclass(s, str)vampytest.assert_subtype(s,str)# Asserts t is None or isinstance(t, type) and issubclass(t, str)vampytest.assert_subtype(t,str,nullable=True)Reversed assertions# Assert not u == vvampytest.assert_eq(u,v,reverse=True)# Asserts not isinstance(w, str)vampytest.assert_instance(w,str,reverse=True)[↑ Back to top ↑]Parameterized testsVampytest allows you to write parameterized tests that run the same test function with different sets of inputs.
This allows for more comprehensive testing with fewer lines of code.importvampytest@vampytest.call_with(2,2)@vampytest.call_with(3,3)deftest_values_equal(value_0,value_1):vampytest.assert_eq(value_0,value_1)Multiple inputs can also be parameterised with one decorator.
This can be useful when the input is more complex, and you might want to define a generator for it.importvampytestdefinput_generator():a=object()yielda,ab='apple'yieldb,bc=intyieldc,c@vampytest.call_from(input_generator())deftest_values_identical(value_0,value_1):vampytest.assert_is(value_0,value_1)[↑ Back to top ↑]Returning and raising testsWith vampytest, it is possible to assert the output of tests.
This assertion can be done using thereturningandraisingdecorators.importvampytest@vampytest.returning(True)deftest_values_equal():return2==2@vampytest.raising(ValueError)deftest_convert_to_int():returnint('apple')These decorators pair-up well with parameterised tests.importvampytest@vampytest.returning(True)@vampytest.call_with(2,2)@vampytest.call_with(3,3)deftest_values_equal(value_0,value_1):returnvalue_0==value_1@vampytest.raising(ValueError)@vampytest.call_with('apple')@vampytest.call_with('peach')deftest_convert_to_int(fruit):returnint(fruit)It is also possible to define the expected output for each set of input.importvampytest@vampytest.call_with('apple').raising(ValueError)@vampytest.call_with('peach').raising(ValueError)@vampytest.call_with('12').returning(12)deftest_convert_to_int(fruit):returnint(fruit)For cases when the returned value could be easily calculated from the input parameters thereturning_transformedoption might be the solution.importoperatorimportvampytest@vampytest.call_with(2,1).returning_transformed(operator.add)@vampytest.call_with(3,1).returning_transformed(operator.add)@vampytest.call_with(4,3).returning(0)deftest_sum_if_lt_5(value_0,value_1):output=value_0+value_1ifoutput>=5:output=0returnoutputWhen usingcall_from, aside from the already mentionedraising,returning,returning_transformedoptions,
we'll also havereturning_lastandraising_lastas available.
As their name implies they take the last input parameter and expect it to be either raised or returned.importvampytest@vampytest.call_from(['apple','peach']).raising(ValueError)@vampytest.call_from(['12','12']).returning(12)@vampytest.call_from(['6','42']).returning_transformed(int)deftest_convert_to_int(fruit):returnint(fruit)definput_and_return_generator():yield{'a':'b'},'a','b'yield{'b':'c'},'b','c'definput_and_exception_generator():yieldNone,None,TypeErroryield{},'a',KeyErroryield{},{},TypeError@vampytest.call_from(input_and_return_generator()).returning_last()@vampytest.call_from(input_and_exception_generator()).raising_last()deftest_get_item_fails(container,key):returncontainer[key][↑ Back to top ↑]Skipping testsVampytest provides decorators to skip or mark tests as skipped in certain conditions.importvampytestclassMyType:defa():return1@vampytest.skip()deftest_repr():instance=MyType()vampytest.assert_instance(repr(instance),str)@vampytest.skip_if(nothasattr(MyType,'b'))deftest_repr():instance=MyType()vampytest.assert_eq(instance.b(),2)[↑ Back to top ↑]Reversing test resultsIf you have a test where you expect the assertions to fail they can be marked with thereversedecorator.
These tests will show up as passing when the tests' assertions fail. On the other hand they will show up as failing
if the assertions pass.importvampytest@vampytest.reverse()deftest_addition():vampytest.assert_eq(9+10,21)[↑ Back to top ↑]Garbage collectionBy default, garbage collection is not explicitly called between each test case since it could easily increase the
time required to run the tests by 10000% on large projects. By usingwith_gcit is possible to explicitly call
garbage collection before or after a test.importvampytest@vampytest.with_gc(after=True,before=True)deftest_addition():vampytest.assert_eq(2+2,4)[↑ Back to top ↑]Capturing outputCapturingstdoutandstderrin tests is useful in scenarios where you need to verify the content, format, or
structure of the output generated by your code.
This technique allows you to intercept and store the output that would normally be printed to the console during the
execution of your code, enabling you to inspect and assert against it in your tests.importvampytestdeftest_print():capture=vampytest.capture_output()withcapture:print('apple')vampytest.assert_eq(capture.get_value(),'apple\n')Vampytest is capturing thestdoutandstderrby default. If a test fails the captured output will show
up in its report. If the test passes its captured output will only show up if all tests passed. This is to help the
developer focus on the failing tests firsts. This feature can be useful to help debug failing tests and to catchwarningsand forgottenprintcalls.[↑ Back to top ↑]Advanced featuresTesting environmentsVampytest supports tests to be run in a specific environment to ensure that the code behaves correctly and consistently.Environments can be set to be used on a specific scope using theset_global_environment,set_directory_environmentset_file_environmentfunctions. They can also be set to apply to just a specific test using thein_environmentdecorator.fromvampytestimportDefaultEnvironment,ResultState,in_environment,returningfromvampytest.core.environment.constantsimportENVIRONMENT_TYPE_GENERATOR# We create an environment that unpacks generators into a listclassGenerativeReturnTestEnvironment(DefaultEnvironment):__slots__=()# Define that this environment is only applicable for generators and will propagate the tests' results like thatidentifier=ENVIRONMENT_TYPE_GENERATOR# Run is called to run the test with the given parameters and we except it to return a `ResultState`defrun(self,test,positional_parameters,keyword_parameters):try:returned_value=[*test(*positional_parameters,**keyword_parameters)]exceptBaseExceptionasraised_exception:returnResultState().with_raise(raised_exception)returnResultState().with_return(returned_value)# Shutdown is called when we do not need this environment anymore. Can be useful when using global environments.defshutdown(self):pass@in_environment(GenerativeReturnTestEnvironment())@returning([1,2])deftest_generator_in_environment():yield1yield2Vampytest only defines 2 environments by default:defaultandscarletio coroutine.defaultenvironment applies to normal non-generator non-coroutine tests.scarletio coroutineenvironment applies to coroutine tests.
Vampytest assumes that every coroutine test is meant to run on a scarletio event loop.For generators and coroutine generators there is no environment defined by default.Scarletio based projects might use the same event loop for their whole lifecycle. To use their event loop in
the tests use a global environment for it:importsysif'vampytest'insys.modules:fromvampytestimportScarletioCoroutineEnvironment,assert_is,set_global_environment# import event loopfromsomewhereimportEVENT_LOOPset_global_environment(ScarletioCoroutineEnvironment(event_loop=EVENT_LOOP))Test whether the event loops indeed match:importvampytestfromscarletioimportget_event_loop# import event loopfromsomewhereimportEVENT_LOOP# Test whether we are indeed on the correct event loopasyncdeftest_event_loop_same():vampytest.assert_is(EVENT_LOOP,get_event_loop())[↑ Back to top ↑]