Quickstart

Eager to start? Make sure that Pynguin is installed properly.

Warning

Pynguin actually executes the code of the module under test. That means, if the code you want to generate tests for does something bad, for example wipes your disk, there is nothing that prevents it from doing so! This also includes code that is transitively imported by the module under test.

To mitigate this issue, we recommend running Pynguin in a Docker container with appropriate mounts from the host system’s file system. See the pynguin-docker.sh script in Pynguin’s source repository for documentation on the necessary mounts.

To help prevent harming the system that runs Pynguin, its CLI will immediately abort unless the environment variable PYNGUIN_DANGER_AWARE is set. In setting this variable, you acknowledge that you are aware of the possible dangers of executing code with random inputs. The assigned value can be arbitrary; Pynguin solely checks whether the variable is defined.

We do not provide any support and are not responsible if you break your computer by executing Pynguin on some random code from the internet! Be careful and check the code before actually executing it—which is good advice anyway.

Developers: If you know of a similar technique to Java’s security manager mechanism in Python, which we can use to mitigate this issue, please let us know.

A Simple Example

For a first impression, we use the bundled example file and generate tests for it. Note that this assumes that you have the source code checked out, installed Pynguin properly—as mentioned before, we recommend a virtual environment, which needs to be sourced manually—and that your shell is pointing to the root directory of Pynguin’s source repository. We run all commands on a command-line shell where we assume that the environment variable PYNGUIN_DANGER_AWARE is set.

Note

We don’t use docker in our examples, because we know that our examples do not contain or use code that might harm our system. But for unknown code we highly recommend using some form of isolation.

First, let’s look at the code of the example file (which is located in docs/source/_static/example.py):

1def triangle(x: int, y: int, z: int) -> str:
2    if x == y == z:
3        return "Equilateral triangle"
4    if x in {y, z} or y == z:
5        return "Isosceles triangle"
6    return "Scalene triangle"

The example is the classical triangle example from courses on Software Testing, which yields for three given integers—assumed to be the lengths of the triangle’s edges—what type of triangle it is. Note that we have annotated all parameter and return types, according to PEP 484.

Before we can start, we create a directory for the output (this assumes you are on a Linux or macOS machine, but similar can be done on Windows) using the command line:

$ mkdir -p /tmp/pynguin-results

We will now invoke Pynguin (using its default test-generation algorithm) to let it generate test cases (we use \ and the line breaks for better readability here, you can just omit them and type everything in one line):

$ pynguin \
    --project-path ./docs/source/_static \
    --output-path /tmp/pynguin-results \
    --module-name example

This runs for a moment without showing any output. Thus, to have some more verbose output we add the -v parameter:

$ pynguin \
    --project-path ./docs/source/_static \
    --output-path /tmp/pynguin-results \
    --module-name example \
    -v

The output on the command line might be something like the following:

[09:01:14] INFO     Starting Pynguin client with master-worker architecture                                                                                         client.py:40
           INFO     [Worker-36230] Worker process started (PID: 36230)                                                                                              worker.py:97
           INFO     [Worker-36230] Start Pynguin Test Generation…                                                                                               generator.py:141
           INFO     [Worker-36230] Collecting static constants from module under test                                                                           generator.py:265
           INFO     [Worker-36230] No constants found                                                                                                           generator.py:268
           INFO     [Worker-36230] Setting up runtime collection of constants                                                                                   generator.py:275
           INFO     [Worker-36230] Analyzed project to create test cluster                                                                                        module.py:1690
           INFO     [Worker-36230] Modules:       1                                                                                                               module.py:1691
           INFO     [Worker-36230] Functions:     1                                                                                                               module.py:1692
           INFO     [Worker-36230] Classes:      11                                                                                                               module.py:1693
           INFO     [Worker-36230] Using seed 1764316873425242000                                                                                               generator.py:245
           INFO     [Worker-36230] Using strategy: Algorithm.DYNAMOSA                                                                          generationalgorithmfactory.py:297
           INFO     [Worker-36230] Instantiated 9 fitness functions                                                                            generationalgorithmfactory.py:385
           INFO     [Worker-36230] Using CoverageArchive                                                                                       generationalgorithmfactory.py:339
           INFO     [Worker-36230] Using selection function: Selection.RANK_SELECTION                                                          generationalgorithmfactory.py:314
           INFO     [Worker-36230] No stopping condition configured!                                                                           generationalgorithmfactory.py:113
           INFO     [Worker-36230] Using fallback timeout of 600 seconds                                                                       generationalgorithmfactory.py:114
           INFO     [Worker-36230] Using crossover function: SinglePointRelativeCrossOver                                                      generationalgorithmfactory.py:327
           INFO     [Worker-36230] Using ranking function: RankBasedPreferenceSorting                                                          generationalgorithmfactory.py:347
           INFO     [Worker-36230] Start generating test cases                                                                                                  generator.py:606
           INFO     [Worker-36230] Initial Population, Coverage: 1.000000                                                                                   searchobserver.py:75
           INFO     [Worker-36230] Algorithm stopped before using all resources.                                                                                generator.py:609
           INFO     [Worker-36230] Stop generating test cases                                                                                                   generator.py:614
           INFO     [Worker-36230] Minimizing test cases                                                                                                        generator.py:623
           INFO     [Worker-36230] Removed 2 statement(s) from test casesusing CASE minimization                                                                generator.py:777
           INFO     [Worker-36230] Coverage after minimization is the same as before: [1.0]                                                                     generator.py:716
           INFO     [Worker-36230] Start generating assertions                                                                                                  generator.py:898
           INFO     [Worker-36230] Setup mutation generator                                                                                                     generator.py:871
           INFO     [Worker-36230] Import module example                                                                                                        generator.py:874
           INFO     [Worker-36230] Build AST for example                                                                                                        generator.py:877
           INFO     [Worker-36230] Mutate module example                                                                                                        generator.py:881
           INFO     [Worker-36230] Generated 13 mutants                                                                                                         generator.py:891
           INFO     [Worker-36230] Running tests on mutant   1/13                                                                                      assertiongenerator.py:276
           INFO     [Worker-36230] Running tests on mutant   2/13                                                                                      assertiongenerator.py:276
           INFO     [Worker-36230] Running tests on mutant   3/13                                                                                      assertiongenerator.py:276
           INFO     [Worker-36230] Running tests on mutant   4/13                                                                                      assertiongenerator.py:276
           INFO     [Worker-36230] Running tests on mutant   5/13                                                                                      assertiongenerator.py:276
           INFO     [Worker-36230] Running tests on mutant   6/13                                                                                      assertiongenerator.py:276
           INFO     [Worker-36230] Running tests on mutant   7/13                                                                                      assertiongenerator.py:276
           INFO     [Worker-36230] Running tests on mutant   8/13                                                                                      assertiongenerator.py:276
           INFO     [Worker-36230] Running tests on mutant   9/13                                                                                      assertiongenerator.py:276
           INFO     [Worker-36230] Running tests on mutant  10/13                                                                                      assertiongenerator.py:276
           INFO     [Worker-36230] Running tests on mutant  11/13                                                                                      assertiongenerator.py:276
           INFO     [Worker-36230] Running tests on mutant  12/13                                                                                      assertiongenerator.py:276
           INFO     [Worker-36230] Running tests on mutant  13/13                                                                                      assertiongenerator.py:276
           INFO     [Worker-36230] Mutant 0 killed by Test(s): 0, 1, 2                                                                                 assertiongenerator.py:378
           INFO     [Worker-36230] Mutant 1 killed by Test(s): 0, 1, 2                                                                                 assertiongenerator.py:378
           INFO     [Worker-36230] Mutant 2 killed by Test(s): 0, 2                                                                                    assertiongenerator.py:378
           INFO     [Worker-36230] Mutant 3 killed by Test(s): 1, 2                                                                                    assertiongenerator.py:378
           INFO     [Worker-36230] Mutant 4 killed by Test(s): 1, 2                                                                                    assertiongenerator.py:378
           INFO     [Worker-36230] Mutant 5 killed by Test(s): 0, 1, 2                                                                                 assertiongenerator.py:378
           INFO     [Worker-36230] Mutant 6 killed by Test(s): 0, 1, 2                                                                                 assertiongenerator.py:378
           INFO     [Worker-36230] Mutant 7 killed by Test(s): 2                                                                                       assertiongenerator.py:378
           INFO     [Worker-36230] Mutant 8 killed by Test(s): 2                                                                                       assertiongenerator.py:378
           INFO     [Worker-36230] Mutant 9 killed by Test(s): 0, 1, 2                                                                                 assertiongenerator.py:378
           INFO     [Worker-36230] Mutant 10 killed by Test(s): 1, 2                                                                                   assertiongenerator.py:378
           INFO     [Worker-36230] Mutant 11 killed by Test(s): 0, 1, 2                                                                                assertiongenerator.py:378
           INFO     [Worker-36230] Mutant 12 killed by Test(s): 1, 2                                                                                   assertiongenerator.py:378
           INFO     [Worker-36230] Number of Surviving Mutant(s): 0 (Mutants: )                                                                        assertiongenerator.py:390
           INFO     [Worker-36230] Calculating resulting FinalBranchCoverage                                                                                    generator.py:534
           INFO     [Worker-36230] Written 3 test cases to /private/tmp/pynguin-results/test_example.py                                                        generator.py:1007
           INFO     [Worker-36230] Writing statistics                                                                                                               stats.py:368
           INFO     [Worker-36230] Stop Pynguin Test Generation…                                                                                                generator.py:146
           INFO     [Worker-36230] Worker completed task: test_gen_1764316874.5722592                                                                              worker.py:110
           INFO     Received result for task test_gen_1764316874.5722592: WorkerReturnCode.OK                                                                      master.py:133
           INFO     Stopping master-worker system                                                                                                                   client.py:44
           INFO     Master-worker system stopped                                                                                                                    client.py:46

The first few lines show that Pynguin starts using a master-worker architecture, which allows automatic restarting the test generation in case of unexpected errors. Then, we see that Pynguin tries collecting constants from the module under test, but none were found in this case. Pynguin analyzes the module and finds one function, namely triangle, which it will try to cover with generated test cases. The eleven found classes are the built-in types that are always present. Pynguin has not gotten any seed for its (pseudo) random-number generator, so it generates one itself. We see some information about the (default) configuration options used. We use, for example, the DYNAMOSA algorithm. Pynguin ran zero iterations of that algorithm, i.e., the initial random test cases were sufficient to cover all branches. This was to be expected, since the triangle example can be trivially covered with tests. Pynguin minimizes the test suite and then generates assertions using Mutation Analysis. Before stopping the master-worker system, the results are printed: Three test cases were written to /tmp/pynguin/results/test_example.py, which look like the following (the result can differ on your machine):

 1import example as module_0
 2import builtins as module_1
 3
 4
 5def test_case_0():
 6    float_0 = 2530.058
 7    int_0 = 3137
 8    str_0 = module_0.triangle(float_0, float_0, int_0)
 9    assert str_0 == "Isosceles triangle"
10
11
12def test_case_1():
13    bool_0 = True
14    str_0 = module_0.triangle(bool_0, bool_0, bool_0)
15    assert str_0 == "Equilateral triangle"
16    bool_1 = False
17    str_1 = module_0.triangle(bool_1, bool_0, bool_0)
18    assert str_1 == "Isosceles triangle"
19
20
21def test_case_2():
22    object_0 = module_1.object()
23    bool_0 = True
24    bool_1 = True
25    str_0 = module_0.triangle(bool_0, bool_1, bool_0)
26    assert str_0 == "Equilateral triangle"
27    int_0 = -2898
28    bool_2 = False
29    str_1 = module_0.triangle(bool_2, bool_2, int_0)
30    assert str_1 == "Isosceles triangle"
31    none_type_0 = None
32    str_2 = module_0.triangle(object_0, int_0, none_type_0)
33    assert str_2 == "Scalene triangle"

We can see that each test case consists of one or more invocations of the triangle function and that there are assertions that check for the correct return value. We can now run the generated test cases using pytest with coverage enabled to see that indeed all code is covered:

$ pytest \
    --cov=example \
    --cov-branch docs/source/_static/test_example.py

Note

Pynguin uses ruff-format for formatting.

A more complex example

The above triangle example is really simple and could also be covered by a simple fuzzing tool. Thus, we now look at a more complex example: An implementation of a Queue for int elements. (located in docs/source/_static/queue_example.py):

 1import array
 2
 3
 4class Queue:
 5    def __init__(self, size_max: int) -> None:
 6        assert size_max > 0
 7        self.max = size_max
 8        self.head = 0
 9        self.tail = 0
10        self.size = 0
11        self.data = array.array("i", range(size_max))
12
13    def empty(self) -> bool:
14        return self.size == 0
15
16    def full(self) -> bool:
17        return self.size == self.max
18
19    def enqueue(self, x: int) -> bool:
20        if self.size == self.max:
21            return False
22        self.data[self.tail] = x
23        self.size += 1
24        self.tail += 1
25        if self.tail == self.max:
26            self.tail = 0
27        return True
28
29    def dequeue(self) -> int | None:
30        if self.size == 0:
31            return None
32        x = self.data[self.head]
33        self.size -= 1
34        self.head += 1
35        if self.head == self.max:
36            self.head = 0
37        return x

Testing this queue is more complex. One needs to instantiate it, add items, etc. Similar to the triangle example, we start Pynguin with the following command:

$ pynguin \
    --project-path ./docs/source/_static/ \
    --output-path /tmp/pynguin-results \
    --module-name queue_example \
    -v

The command yields the following output:

[09:18:31] INFO     Starting Pynguin client with master-worker architecture                                                                                         client.py:40
           INFO     [Worker-37545] Worker process started (PID: 37545)                                                                                              worker.py:97
           INFO     [Worker-37545] Start Pynguin Test Generation…                                                                                               generator.py:141
           INFO     [Worker-37545] Collecting static constants from module under test                                                                           generator.py:265
           INFO     [Worker-37545] No constants found                                                                                                           generator.py:268
           INFO     [Worker-37545] Setting up runtime collection of constants                                                                                   generator.py:275
           INFO     [Worker-37545] Analyzed project to create test cluster                                                                                        module.py:1690
           INFO     [Worker-37545] Modules:       2                                                                                                               module.py:1691
           INFO     [Worker-37545] Functions:     0                                                                                                               module.py:1692
           INFO     [Worker-37545] Classes:      13                                                                                                               module.py:1693
           INFO     [Worker-37545] Using seed 1764317910386968000                                                                                               generator.py:245
           INFO     [Worker-37545] Using strategy: Algorithm.DYNAMOSA                                                                          generationalgorithmfactory.py:297
           INFO     [Worker-37545] Instantiated 14 fitness functions                                                                           generationalgorithmfactory.py:385
           INFO     [Worker-37545] Using CoverageArchive                                                                                       generationalgorithmfactory.py:339
           INFO     [Worker-37545] Using selection function: Selection.RANK_SELECTION                                                          generationalgorithmfactory.py:314
           INFO     [Worker-37545] No stopping condition configured!                                                                           generationalgorithmfactory.py:113
           INFO     [Worker-37545] Using fallback timeout of 600 seconds                                                                       generationalgorithmfactory.py:114
           INFO     [Worker-37545] Using crossover function: SinglePointRelativeCrossOver                                                      generationalgorithmfactory.py:327
           INFO     [Worker-37545] Using ranking function: RankBasedPreferenceSorting                                                          generationalgorithmfactory.py:347
           INFO     [Worker-37545] Start generating test cases                                                                                                  generator.py:606
           INFO     [Worker-37545] Initial Population, Coverage: 0.642857                                                                                   searchobserver.py:75
           INFO     [Worker-37545] Local search complete, the coverage hasn't changed                                                                   dynamosaalgorithm.py:152
           INFO     [Worker-37545] Iteration:       1, Coverage: 0.857143                                                                                   searchobserver.py:81
           INFO     [Worker-37545] Local search complete, the coverage hasn't changed                                                                   dynamosaalgorithm.py:152
           INFO     [Worker-37545] Iteration:       2, Coverage: 0.928571                                                                                   searchobserver.py:81
           INFO     [Worker-37545] Local search complete, the coverage hasn't changed                                                                   dynamosaalgorithm.py:152
           INFO     [Worker-37545] Iteration:       3, Coverage: 0.928571                                                                                   searchobserver.py:81
           INFO     [Worker-37545] Local search complete, the coverage hasn't changed                                                                   dynamosaalgorithm.py:152
           INFO     [Worker-37545] Iteration:       4, Coverage: 1.000000                                                                                   searchobserver.py:81
           INFO     [Worker-37545] Algorithm stopped before using all resources.                                                                                generator.py:609
           INFO     [Worker-37545] Stop generating test cases                                                                                                   generator.py:614
           INFO     [Worker-37545] Minimizing test cases                                                                                                        generator.py:623
           INFO     [Worker-37545] Removed 28 statement(s) from test casesusing CASE minimization                                                               generator.py:777
           INFO     [Worker-37545] Coverage after minimization is the same as before: [1.0]                                                                     generator.py:716
           INFO     [Worker-37545] Start generating assertions                                                                                                  generator.py:898
           INFO     [Worker-37545] Setup mutation generator                                                                                                     generator.py:871
           INFO     [Worker-37545] Import module queue_example                                                                                                  generator.py:874
           INFO     [Worker-37545] Build AST for queue_example                                                                                                  generator.py:877
           INFO     [Worker-37545] Mutate module queue_example                                                                                                  generator.py:881
           INFO     [Worker-37545] Generated 31 mutants                                                                                                         generator.py:891
[09:18:32] INFO     [Worker-37545] Running tests on mutant   1/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant   2/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant   3/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant   4/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant   5/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant   6/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant   7/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant   8/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant   9/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  10/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  11/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  12/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  13/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  14/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  15/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  16/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  17/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  18/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  19/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  20/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  21/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  22/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Skipping mutant  23/31 because it created an invalid module                                                         assertiongenerator.py:269
           INFO     [Worker-37545] Running tests on mutant  24/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  25/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  26/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  27/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  28/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  29/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  30/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Running tests on mutant  31/31                                                                                      assertiongenerator.py:276
           INFO     [Worker-37545] Mutant 0 killed by Test(s): 2, 4, 5, 6                                                                              assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 1 killed by Test(s): 4                                                                                       assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 2 killed by Test(s): 4, 5                                                                                    assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 3 killed by Test(s): 4                                                                                       assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 4 killed by Test(s): 2, 4, 5, 6                                                                              assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 5 killed by Test(s): 4                                                                                       assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 6 killed by Test(s): 4, 5, 6                                                                                 assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 7 killed by Test(s): 4                                                                                       assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 8 killed by Test(s): 0, 2, 3, 5, 6                                                                           assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 9 killed by Test(s): 0, 2, 3, 4, 5, 6                                                                        assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 10 killed by Test(s): 0, 2, 3, 4, 5, 6                                                                       assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 11 killed by Test(s): 0, 2, 3, 4, 5, 6                                                                       assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 12 killed by Test(s): 0, 2, 3, 4, 5, 6                                                                       assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 13 killed by Test(s): 0, 2, 3, 4, 5, 6                                                                       assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 14 killed by Test(s): 0, 2, 3, 4, 5, 6                                                                       assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 15 killed by Test(s): 2, 4, 5, 6                                                                             assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 16 killed by Test(s): 4                                                                                      assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 18 killed by Test(s): 4, 5, 6                                                                                assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 19 killed by Test(s): 4, 5                                                                                   assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 20 killed by Test(s): 4                                                                                      assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 23 killed by Test(s): 0, 2, 3, 4, 5, 6                                                                       assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 24 killed by Test(s): 0, 1, 2, 3, 6                                                                          assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 25 killed by Test(s): 0, 2, 3, 4, 5, 6                                                                       assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 26 killed by Test(s): 2, 3, 4, 5, 6                                                                          assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 27 killed by Test(s): 2, 4, 5, 6                                                                             assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 28 killed by Test(s): 4                                                                                      assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 29 killed by Test(s): 4, 5, 6                                                                                assertiongenerator.py:378
           INFO     [Worker-37545] Mutant 30 killed by Test(s): 4                                                                                      assertiongenerator.py:378
           INFO     [Worker-37545] Number of Surviving Mutant(s): 3 (Mutants: 17, 21, 22)                                                              assertiongenerator.py:390
           INFO     [Worker-37545] Calculating resulting FinalBranchCoverage                                                                                    generator.py:534
           INFO     [Worker-37545] Written 7 test cases to /private/tmp/pynguin-results/test_queue_example.py                                                  generator.py:1007
           INFO     [Worker-37545] Writing statistics                                                                                                               stats.py:368
           INFO     [Worker-37545] Stop Pynguin Test Generation…                                                                                                generator.py:146
           INFO     [Worker-37545] Worker completed task: test_gen_1764317911.477082                                                                               worker.py:110
[09:18:32] INFO     Received result for task test_gen_1764317911.477082: WorkerReturnCode.OK                                                                       master.py:133
           INFO     Stopping master-worker system                                                                                                                   client.py:44
           INFO     Master-worker system stopped                                                                                                                    client.py:46

We can see that the DYNAMOSA algorithm had to perform four iterations to fully cover the Queue example. We can also see that Pynguin generated seven test cases:

  1# Test cases automatically generated by Pynguin (https://www.pynguin.eu).
  2import pytest
  3import queue_example as module_0
  4
  5
  6def test_case_0():
  7    bool_0 = True
  8    queue_0 = module_0.Queue(bool_0)
  9    assert (
 10            f"{type(queue_0).__module__}.{type(queue_0).__qualname__}"
 11            == "queue_example.Queue"
 12    )
 13    assert queue_0.max is True
 14    assert queue_0.head == 0
 15    assert queue_0.tail == 0
 16    assert queue_0.size == 0
 17    assert (
 18            f"{type(queue_0.data).__module__}.{type(queue_0.data).__qualname__}"
 19            == "array.array"
 20    )
 21    assert len(queue_0.data) == 1
 22    queue_0.dequeue()
 23    queue_1 = module_0.Queue(bool_0)
 24    assert queue_1.head == 0
 25    assert queue_1.tail == 0
 26    assert queue_1.size == 0
 27    bool_1 = queue_1.empty()
 28    assert bool_1 is True
 29    bool_2 = False
 30    with pytest.raises(AssertionError):
 31        module_0.Queue(bool_2)
 32
 33
 34def test_case_1():
 35    bool_0 = False
 36    with pytest.raises(AssertionError):
 37        module_0.Queue(bool_0)
 38
 39
 40def test_case_2():
 41    bool_0 = True
 42    bool_1 = True
 43    queue_0 = module_0.Queue(bool_1)
 44    assert (
 45            f"{type(queue_0).__module__}.{type(queue_0).__qualname__}"
 46            == "queue_example.Queue"
 47    )
 48    assert queue_0.max is True
 49    assert queue_0.head == 0
 50    assert queue_0.tail == 0
 51    assert queue_0.size == 0
 52    assert (
 53            f"{type(queue_0.data).__module__}.{type(queue_0.data).__qualname__}"
 54            == "array.array"
 55    )
 56    assert len(queue_0.data) == 1
 57    bool_2 = queue_0.empty()
 58    assert bool_2 is True
 59    bool_3 = queue_0.full()
 60    assert bool_3 is False
 61    bool_4 = queue_0.enqueue(bool_0)
 62    assert bool_4 is True
 63    assert queue_0.size == 1
 64    bool_5 = False
 65    with pytest.raises(AssertionError):
 66        module_0.Queue(bool_5)
 67
 68
 69def test_case_3():
 70    bool_0 = False
 71    bool_1 = True
 72    queue_0 = module_0.Queue(bool_1)
 73    assert (
 74            f"{type(queue_0).__module__}.{type(queue_0).__qualname__}"
 75            == "queue_example.Queue"
 76    )
 77    assert queue_0.max is True
 78    assert queue_0.head == 0
 79    assert queue_0.tail == 0
 80    assert queue_0.size == 0
 81    assert (
 82            f"{type(queue_0.data).__module__}.{type(queue_0.data).__qualname__}"
 83            == "array.array"
 84    )
 85    assert len(queue_0.data) == 1
 86    bool_2 = queue_0.empty()
 87    assert bool_2 is True
 88    bool_3 = queue_0.full()
 89    assert bool_3 is False
 90    with pytest.raises(AssertionError):
 91        module_0.Queue(bool_0)
 92
 93
 94def test_case_4():
 95    int_0 = 922
 96    queue_0 = module_0.Queue(int_0)
 97    assert (
 98            f"{type(queue_0).__module__}.{type(queue_0).__qualname__}"
 99            == "queue_example.Queue"
100    )
101    assert queue_0.max == 922
102    assert queue_0.head == 0
103    assert queue_0.tail == 0
104    assert queue_0.size == 0
105    assert (
106            f"{type(queue_0.data).__module__}.{type(queue_0.data).__qualname__}"
107            == "array.array"
108    )
109    assert len(queue_0.data) == 922
110    queue_0.dequeue()
111    bool_0 = queue_0.empty()
112    assert bool_0 is True
113    bool_1 = queue_0.enqueue(int_0)
114    assert bool_1 is True
115    assert queue_0.tail == 1
116    assert queue_0.size == 1
117    var_0 = queue_0.dequeue()
118    assert var_0 == 922
119    assert queue_0.head == 1
120    assert queue_0.size == 0
121    bool_2 = queue_0.full()
122    assert bool_2 is False
123
124
125def test_case_5():
126    bool_0 = True
127    queue_0 = module_0.Queue(bool_0)
128    assert (
129            f"{type(queue_0).__module__}.{type(queue_0).__qualname__}"
130            == "queue_example.Queue"
131    )
132    assert queue_0.max is True
133    assert queue_0.head == 0
134    assert queue_0.tail == 0
135    assert queue_0.size == 0
136    assert (
137            f"{type(queue_0.data).__module__}.{type(queue_0.data).__qualname__}"
138            == "array.array"
139    )
140    assert len(queue_0.data) == 1
141    bool_1 = queue_0.empty()
142    assert bool_1 is True
143    bool_2 = queue_0.enqueue(bool_0)
144    assert bool_2 is True
145    assert queue_0.size == 1
146    var_0 = queue_0.dequeue()
147    assert var_0 == 1
148    assert queue_0.size == 0
149    bool_3 = queue_0.full()
150    assert bool_3 is False
151
152
153def test_case_6():
154    bool_0 = True
155    bool_1 = True
156    queue_0 = module_0.Queue(bool_1)
157    assert (
158            f"{type(queue_0).__module__}.{type(queue_0).__qualname__}"
159            == "queue_example.Queue"
160    )
161    assert queue_0.max is True
162    assert queue_0.head == 0
163    assert queue_0.tail == 0
164    assert queue_0.size == 0
165    assert (
166            f"{type(queue_0.data).__module__}.{type(queue_0.data).__qualname__}"
167            == "array.array"
168    )
169    assert len(queue_0.data) == 1
170    queue_0.dequeue()
171    bool_2 = queue_0.empty()
172    assert bool_2 is True
173    bool_3 = queue_0.full()
174    assert bool_3 is False
175    bool_4 = queue_0.enqueue(bool_0)
176    assert bool_4 is True
177    assert queue_0.size == 1
178    bool_5 = queue_0.enqueue(bool_3)
179    assert bool_5 is False
180    int_0 = 0
181    with pytest.raises(AssertionError):
182        module_0.Queue(int_0)

When running the generated test cases with coverage enabled, we see once more that all code is covered:

$ pytest \
    --cov=queue_example \
    --cov-branch docs/source/_static/test_queue_example.py

Note

Generated test cases may contain redundant assertions. Minimising these is open for a future release of Pynguin.

Logging

Pynguin provides the ability to write logging output to a log file in addition to STDOUT. This can be configured using the --log-file parameter:

$ pynguin \
    --project-path ./docs/source/_static/ \
    --output-path /tmp/pynguin-results \
    --module-name example \
    -v \
    --log-file /tmp/pynguin-log.txt

This will write all log messages to the specified file, making it easier to analyze the test generation process or troubleshoot issues without cluttering the console output.