Generate random values and test them in Python 🐍
Tags: python, programming
Published: 2022-12-14 05:33:00

---
title: Generate random values and test them in Python 🐍
published: true
description: Testing a function that generates random values can be tricky, but we can fix the random numbers.
tags: python, programming
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
published_at: 2022-12-14 05:33 +0000
series: mastermind
---

The next thing I wanted to do is to generate the random values the computer hides that the user will have to guess. In the original description I describe that both the number of the hidden items and the type of their value is flexible, but at first let's use only 4 different digits between 1-6.

## Test the random values

The first thing was to add the a test case to the already existing test file, that will verify that the, not yet existing `new_secret` will return the correct results.

However, we have a problem. How can we test a function that will return a random set of values?

In our case we could check
* if the correct number of values were returned
* if they are distinct
* if they are from the same pool of data


```python
def test_new_secret():
    secret = mm.new_secret()
    assert len(secret) == 4
    assert len(set(secret)) == 4
    assert set(secret).issubset(list(range(1, 7)))
```

If we run `pytest` now it will obviously fail as the `new_secret` has not been implemented yet, but we can do that.

## The implementation

It is actually shorter than the test:

```python
import random

def new_secret():
    pool = list(range(1, 7))
    return random.sample(pool, 4)
```

## Running the tests

Now we can run the tests

```
PYTHONPATH=. pytest
```

Let's see the full files:

`mastermind/game.py`

```python
import random

def play():
    ...

def new_secret():
    pool = list(range(1, 7))
    return random.sample(pool, 4)
```

`tests/test_game.py`

```python
import mastermind.game as mm

def test_game():
    mm.play()
    assert True

def test_new_secret():
    secret = mm.new_secret()
    assert len(secret) == 4
    assert len(set(secret)) == 4
    assert set(secret).issubset(list(range(1, 7)))
```


## The problem of duplication

I feel a bit that this test is a bit of overdoing things, but it will be useful to have when we'll start to allow the user to have different size of hidden value and different types of hidden values.

The more problematic thing is that several values were duplicated. For example the pool and the size of hidden value.

## Create global constants

One solution to this is to move these values to global variables or global constants. Let's see that:

`mastermind/game.py`

```python
import random

POOL = list(range(1, 7))
SIZE = 4

def play():
    ...


def new_secret():
    return random.sample(POOL, SIZE)
```

`tests/test_game.py`

```python
import mastermind.game as mm

def test_game():
    mm.play()
    assert True

def test_new_secret():
    secret = mm.new_secret()
    assert len(secret) == mm.SIZE
    assert len(set(secret)) == mm.SIZE
    assert set(secret).issubset(mm.POOL)
```

I used upper-case variable names as in python the convention is that constants (basically variable that should not change) are named in upper case.

In a way this is better solution as we don't repeat the number 4 and the creation of the POOL, but now that they are global we won't be able to set them without using some nasty code.

We'll have to deal with that later.

In addition now the test depends on the code generating the `POOL` correctly.

## Test the exact returned values

Let's add another test case which is a lot more fixed. In which we check the exact values returned by the `new_secret` function.

The problem is that these are random values so they will be different every time we run the test. To see this I added a `print` statement to the test code:

```python
    secret = mm.new_secret()
    print(secret)
```

Then I ran `pytests` several times with the `-s` flag. That tells `pytest` to let through the output that goes to STDOUT and STDERR.

```
PYTHONPATH=. pytest -s
```

Indeed the result printed was different every time:

```
[6, 5, 1, 4]
[5, 4, 3, 2]
[5, 6, 2, 3]
```

## Test by fixing the random generator

You might already know that the `random` module of Python generates Pseudo-random numbers and that the `random.sample()` function uses these Pseudo-random numbers to select the sample.

You might also know that using `random.seed()` we can fix where the Pseudo-random number generator starts.

So we can write a test where do just that:

```python
import random

def test_fixed_secret():
    random.seed(42)
    secret = mm.new_secret()
    print(secret)
```

The 42 was selected in a totally arbitrary way.

This time, no matter how many times we run the test we will get back the exact same result:

```
[6, 1, 5, 3]
```

So our new test will look like this:

```python
def test_fixed_secret():
    random.seed(42)
    secret = mm.new_secret()
    assert secret == [6, 1, 5, 3]
```

Now we have two separate test functions. One reuses the current values of the POOL and SIZE, the other one expect a very specific result.

## The whole test file

Just for completeness, here is the whole test file as we have it now:

```python
import random
import mastermind.game as mm

def test_game():
    mm.play()
    assert True

def test_new_secret():
    secret = mm.new_secret()
    assert len(secret) == mm.SIZE
    assert len(set(secret)) == mm.SIZE
    assert set(secret).issubset(mm.POOL)

def test_fixed_secret():
    random.seed(42)
    secret = mm.new_secret()
    # print(secret)
    assert secret == [6, 1, 5, 3]

```

## Conclusion

There are choices in the implementation that will impact how nice our code will look like, but having tests will make it much safer to change our code as we will be able to easily verify that we did not break anything.

In the meantime you can try to play with the above code. You could enable the `print` statement, change the value of the  `seed`, run the tests and see how for every seed we get a different response.

See you next part