Tutorials / Framework Features

Parallel execution

Test Junkie has three independent axes of parallelism: suites running concurrently, tests within a suite running concurrently, and parameter variants of a single test running concurrently. Each is opt-in — parallelized=True on @Suite or @test, or parallelized_parameters=True on @test — with thread limits set on runner.run().

Suite-level parallelism

parallelized=True on @Suite flags that suite as eligible to run alongside other suites. The runner starts as many as allowed by suite_multithreading_limit. Suites without parallelized=True always run sequentially, even when other suites are running in parallel.

suites.pypython
@Suite(parallelized=True)
class LoginSuite:
    ...

@Suite(parallelized=True)
class CheckoutSuite:
    ...

@Suite(parallelized=True)
class SearchSuite:
    ...
run.pypython
from test_junkie.runner import Runner

runner = Runner([LoginSuite, CheckoutSuite, SearchSuite])
runner.run(suite_multithreading_limit=3)
# all three suites run at the same time

Test-level parallelism

parallelized=True on @test runs individual tests within a suite concurrently. Control the thread count with test_multithreading_limit:

checkout_suite.pypython
@Suite(parallelized=True)
class CheckoutSuite:

    @test(parallelized=True)
    def test_add_to_cart(self):
        ...

    @test(parallelized=True)
    def test_checkout_guest(self):
        ...

    @test(parallelized=True)
    def test_checkout_logged_in(self):
        ...
run.pypython
runner.run(
    suite_multithreading_limit=5,
    test_multithreading_limit=2,
)
# up to 5 suites run concurrently,
# up to 2 tests per suite run concurrently
Test-level parallelism requires thread-safe tests
When tests within a suite run concurrently, they share the same class instance. Any state written in @beforeClass (like self.driver) is shared. Either keep tests stateless, or use per-test setup in @beforeTest/@afterTest instead.

Parallelizing parameter variants

parallelized_parameters=True on @test runs that test's parameter variants concurrently rather than sequentially. This is independent of whether the test itself is parallelized — a test can be sequential across methods but still fan out its variants in parallel:

checkout_suite.pypython
@Suite(parallelized=True)
class CheckoutSuite:

    @test(
        parameters=["visa", "mastercard", "paypal"],
        parallelized_parameters=True,
    )
    def test_checkout(self, parameter):
        ...

runner = Runner([CheckoutSuite])
runner.run(
    suite_multithreading_limit=5,
    test_multithreading_limit=3,
)
# all 3 payment method variants run concurrently
parallelized_parameters vs parallelized
parallelized=True on @test controls whether different test methods in the same suite run concurrently. parallelized_parameters=True controls whether the parameter variants of one test method run concurrently. Both require test_multithreading_limit to be set on runner.run().

Combining with parametrization

Parallel execution and parametrization compose naturally. Each parameter variant of a parallelized suite runs as an independent thread:

checkout_suite.pypython
@Suite(
    parallelized=True,
    parameters=[
        {"env": "staging"},
        {"env": "prod"},
    ],
)
class CheckoutSuite:
    ...

runner = Runner([CheckoutSuite])
runner.run(suite_multithreading_limit=2)
# staging and prod suites run at the same time