0 added
0 removed
Original
2026-01-01
Modified
2026-02-21
1
<p>Most tests that test a given functionality are very similar, especially in initial data preparation. In the last lesson, each test started with the line: stack = []. It isn't duplication yet, but it's a step in a dangerous direction.</p>
1
<p>Most tests that test a given functionality are very similar, especially in initial data preparation. In the last lesson, each test started with the line: stack = []. It isn't duplication yet, but it's a step in a dangerous direction.</p>
2
<p>This lesson will learn how to prevent duplication when preparing identical data for different tests. Let's say we are developing the<a>funcy</a>library, which provides functions for working with collections in Python.</p>
2
<p>This lesson will learn how to prevent duplication when preparing identical data for different tests. Let's say we are developing the<a>funcy</a>library, which provides functions for working with collections in Python.</p>
3
<p>Among them, we can find such functions as:</p>
3
<p>Among them, we can find such functions as:</p>
4
<ul><li>compact</li>
4
<ul><li>compact</li>
5
<li>select</li>
5
<li>select</li>
6
<li>flatten</li>
6
<li>flatten</li>
7
</ul><p>How do we test them? For these functions to work, you need a prepared collection. Let's create the necessary collection, pass it to the function, and look at the result:</p>
7
</ul><p>How do we test them? For these functions to work, you need a prepared collection. Let's create the necessary collection, pass it to the function, and look at the result:</p>
8
<p>Then we'll do the same for all the other functions:</p>
8
<p>Then we'll do the same for all the other functions:</p>
9
<p>Next, we perform this operation for all other tested functions. The collection initialization fragment will start moving from place to place, generating more and more of the same code.</p>
9
<p>Next, we perform this operation for all other tested functions. The collection initialization fragment will start moving from place to place, generating more and more of the same code.</p>
10
<p>The easiest way to avoid this is to move the collection definition to the module level:</p>
10
<p>The easiest way to avoid this is to move the collection definition to the module level:</p>
11
<p>This simple solution eliminates unnecessary duplication. Keep in mind that this only works within a single module. The collection still needs to be defined in each test module. In our case, this is more of a plus than a minus. However, it's not always possible to transfer data to the module level.</p>
11
<p>This simple solution eliminates unnecessary duplication. Keep in mind that this only works within a single module. The collection still needs to be defined in each test module. In our case, this is more of a plus than a minus. However, it's not always possible to transfer data to the module level.</p>
12
<p>First of all, what about dynamic data? What happens if at least one of the test functions changes this collection?</p>
12
<p>First of all, what about dynamic data? What happens if at least one of the test functions changes this collection?</p>
13
<p>We use one collection for all tests. If it changes, it means that the tests are dependent on the order of execution. Subsequent tests will receive the modified collection. These situations are unacceptable in testing because they lead to fragile and hard-to-debug tests.</p>
13
<p>We use one collection for all tests. If it changes, it means that the tests are dependent on the order of execution. Subsequent tests will receive the modified collection. These situations are unacceptable in testing because they lead to fragile and hard-to-debug tests.</p>
14
<p>Test frameworks provide<strong>hooks</strong>- special functions that run before or after tests to solve this problem. In Pytest, we refer to them as<strong>fixtures</strong>.</p>
14
<p>Test frameworks provide<strong>hooks</strong>- special functions that run before or after tests to solve this problem. In Pytest, we refer to them as<strong>fixtures</strong>.</p>
15
<p>Let us look at an example that creates a collection before each test:</p>
15
<p>Let us look at an example that creates a collection before each test:</p>
16
<p>The @pytest.fixture decorator adds an arbitrary function to the test execution process. The fixture runs before the tests requested by function parameters. The argument name must be the same as the fixture name.</p>
16
<p>The @pytest.fixture decorator adds an arbitrary function to the test execution process. The fixture runs before the tests requested by function parameters. The argument name must be the same as the fixture name.</p>
17
<p>Now for another example. Consider code that works with the current time:</p>
17
<p>Now for another example. Consider code that works with the current time:</p>
18
<p>The output will show two identical dates. The catch is that the code module is loaded into memory exactly once. It means any code defined at the module level will run once. In the example, we define the now variable before the tests, and only then does Pytest start executing them.</p>
18
<p>The output will show two identical dates. The catch is that the code module is loaded into memory exactly once. It means any code defined at the module level will run once. In the example, we define the now variable before the tests, and only then does Pytest start executing them.</p>
19
<p>With each subsequent test, the discrepancy between the now variable and the actual value of now becomes larger:</p>
19
<p>With each subsequent test, the discrepancy between the now variable and the actual value of now becomes larger:</p>
20
<ol><li>Pytest loads test modules into memory</li>
20
<ol><li>Pytest loads test modules into memory</li>
21
<li>The now variable is filled with the date when that piece of code runs</li>
21
<li>The now variable is filled with the date when that piece of code runs</li>
22
<li>Pytest starts executing tests using the date from the previous step</li>
22
<li>Pytest starts executing tests using the date from the previous step</li>
23
</ol><p>Why might this be a problem? Code that works with the concept of now can expect now to be almost a snapshot of a particular moment. But in the example above, the new variable is starting to lag behind the real one.</p>
23
</ol><p>Why might this be a problem? Code that works with the concept of now can expect now to be almost a snapshot of a particular moment. But in the example above, the new variable is starting to lag behind the real one.</p>
24
<p>The more tests and the more difficult they are, the greater the lag:</p>
24
<p>The more tests and the more difficult they are, the greater the lag:</p>
25
<p>Fixtures are ideal for extracting common data needed in different tests. However, using fixtures can lead to more complex code than without them if the data slightly differs.</p>
25
<p>Fixtures are ideal for extracting common data needed in different tests. However, using fixtures can lead to more complex code than without them if the data slightly differs.</p>