Soaper is a testing framework for Python that is loosely based on Jest. It's had a few changes to fit python better, namely limiting the use of anonymous functions. A few personal touches have been added as well.
This is not guaranteed to get future support. This is a one-time personal project and might be abandoned at any point.
To install soaper with pip, run the following command:
pip install git+https://github.com/BlueishSapphire/PySoaper.git
Soaper is organized into test suites that contain individual tests. All test suites must be classes that inherit from the TestSuite
class. Making a new test suite is quite simple:
from soaper import TestSuite
class MyTestSuite(TestSuite):
"""Here's where the description goes"""
# your tests go here
Now this isn't very useful on its own. We need to know how to make the individual tests as well. Fortunately, making a test is also quite simple, you just need to add a method to your test suite that has the @test
decorator. So, extending the previous example, you could add a test like so:
from soaper import TestSuite, test
class MyTestSuite(TestSuite):
"""An example testing suite"""
@test
def my_first_test():
print("Hello, world!")
But this still doesn't have much functionality. We need assertions, and test cases, and all that other stuff that makes the tests actually useful. The main way to do this is using the expect
class. The expect
class has a few static methods, but the main way to interact with it is by providing a value to its constructor. Once you have an expect
object with a value placed inside of it, you can run tests on that object. In the following example, we run a simple test that should always pass:
from soaper import TestSuite, test
class MyTestSuite(TestSuite):
"""An example testing suite"""
@test
def my_first_test():
expect(True).to_not_equal(False)
But if you run this code, you'll notice that it doesn't give any output. This is because soaper doesn't automatically run the tests, you have to manually tell it to. You can use the .run()
method of your test suite, or you could use TestSuite.run_all()
to run all currently loaded test suites.
if __name__ == "__main__":
# because only one test suite is loaded, thsese are both the same
MyTestSuite.run()
TestSuite.run_all()
Here is the finished code:
from soaper import TestSuite, test, expect
class MyTestSuite(TestSuite):
"""An example testing suite"""
@test
def my_first_test():
expect(True).to_not_equal(False)
if __name__ == "__main__":
MyTestSuite.run()
Now you'll get the following output if you run this script:
(This script is available in example.py)
- Run all currently loaded test suites
- Run the given test suite
- Run the current test suite
Example:
@test
def passing_test():
pass
@test.failing
def failing_test():
expect.fail()
@test.skip
def skipped_test():
print("This will never run")
- Makes the given method into a test.
- Makes the given method into a test, and marks it as failing. A test marked as failing has its result flipped: if it passes then it will show up as a fail and vice versa.
- Makes the given method into a test, and marks it to be skipped. A test marked as skipped will not be run when the test suite is ran.
- Fail the current test with the given message.
- Fail the current test with the given message if the condition is true.
- Pass the given text to stdin for the code wihin this block.
Example:
with expect.with_stdin("Hello, world!\n"):
print(input()) # Hello, world!
- Fail the current test if the code within this block does not print the expected string to stdout.
Example:
with expect.to_give_stdout("Hello, world!\n"):
print("Hello, world!")
- Fail the current test if the code within this block does not raise the expected error type.
Example:
with expect.to_raise(NameError):
print(a_variable_that_does_not_exist)
- Fail the current test if the code within this block raises the given error type.
Example:
with expect.to_not_raise(TypeError):
for i in possibly_an_iterator:
print(i)
- Fail the current test if the value is not truthy.
- Fail the current test if the value is not falsy.
- Fail the current test if the given value does not equal the expected value. (using the
==
operator)
- Fail the current test if the given value does equal the expected value. (using the
!=
operator)
- Fail the current test if the given value is not the expected value. (using the
is
operator)
- Fail the current test if the given value is the expected value. (using the
is not
operator)
- Fail the current test if the given value is not less than the expected value. (using the
<
operator)
- Fail the current test if the given value is not less than or equal to the expected value. (using the
<=
operator)
- Fail the current test if the given value is not greater than the expected value. (using the
>
operator)
- Fail the current test if the given value is not greater than or equal to the expected value. (using the
>=
operator)
- Fail the current test if the given value is not close to the expected value (not within
precision
decimal places).
Example:
expect(3.14).close_to(math.pi)
- Fail the current test if the expected value is not an instance of the given value.
Example:
expect(1).to_be_type(int)
expect(1.0).to_be_type(float)
expect("").to_be_type(str)
- Fail the current test if the expected value does not have an attribute named
name
. Ifvalue
is notNone
, then fail the current test if the attribute does not equalvalue
.
Example:
expect(list).to_have_attr("count")
Example:
(expect.function(add)
.describe("given {} and {} as arguments, returns {}")
.run_with_cases(
call_with(0, 0).returns(0),
call_with(1, 1).returns(2),
call_with(2, 2).returns(4),
))
- Give a description of what the given function should do. The description gets formatted using
str.format
and has the arguments and return value of the given case passed to it.
- Run the given function with each of the given cases, failing the test if any of the return values doesn't match.
Example:
(expect.function(add)
.describe("given {} and {} as arguments, returns {}")
.run_with_cases(
call_with(0, 0).returns(0),
call_with(1, 1).returns(2),
call_with(2, 2).returns(4),
))
- Returns a new
call_with
object holding the given arguments.
- Returns the
call_with
object holding the given return value.