# Testing module for measuring time of function

Hello everyone.

I wrote my own test module, which is responsible for measuring the timing of algorithms, I used it mainly for sorting algorithms this is so to say a very early beta version, which I would like to expand with various functionalities, in general I would like to take care of the appearance of the code. I look forward to your opinions and comments, possibly your own ideas.

So here is my testing_module code:

import random
import matplotlib.pyplot as plt

accident = ["x", "random.randint(1, 100)", "x"]
history = ["Optimistic", "Average", "Pessimistic"]

def make_data(numbers, y):
data = []
for element in numbers:
data.append([eval(accident[y]) for x in range(element)])
return data

def make_plot(numbers, measures, name):
for x in range(3):
plt.plot(numbers, measures[x], label = history[x])
plt.title(name)
plt.legend()
plt.show()

def test(method, numbers, measures):
name_function = method.__name__
for x in range(3):
if x != 2:
for element in make_data(numbers, x):
method(element, x)
else:
for element in make_data(numbers, x):
element.reverse()
method(element, x)

make_plot(numbers, measures, name_function)


And this is how i was using it in my code:

import testing_module as test
import time

measures = [[], [], []]
numbers = [10, 100, 300, 500, 700]

def insertion_sort(array, x=0):
start = time.time()
for j in range(1, len(array)):
key = array[j]
i = j - 1
while (i > -1) and key < array[i]:
array[i + 1] = array[i]
i = i - 1
array[i + 1] = key
end = time.time()
measures[x].append(end - start)

test.test(insertion_sort, numbers, measures)


Thanks in advance for any tips and ideas how to improve my solution. Have a great day!

If I had to summarise this code, it would be: magical, spooky and puzzling. It works (which is a great start!) but you can make life easier on yourself and your maintainers.

You've invoked the root of all eval. Don't do this. There are many, many alternatives; the one I'll show uses simple polymorphism.

Many of your variable names need work. element is actually equivalent to your array; numbers is actually lengths; accident is really a sequence of test series generators.

Reduce your reliance on inferred figures and axes via the plt. functions, and instead use your figure and axis objects explicitly.

Avoid forcing a plt.show() inside of your make_plot; let the caller decide what to do with the figure.

Do not use plot(). For these data, Optimistic is basically flat. Use loglog instead.

Do not do timing and measures construction on the inside of your UUT (unit under test).

Do not call test.test from with the global namespace; use a __main__ guard. Similarly, do not leave measures and numbers in the global namespace.

Introduce PEP484 type hints.

Avoid calling reverse(). Your generated test series are just as easily made via range calls, which will be more efficient and avoid mutation.

## Suggested

from random import randrange
from time import time
from typing import Callable, Iterable, List, Iterator, Sequence, Tuple

import matplotlib.pyplot as plt

Method = Callable[[List[int]], None]

class History:
def __init__(self, lengths: Sequence[int], method: Method) -> None:
self.measures: Tuple[float] = tuple(self.test(lengths, method))

@staticmethod
def make_data(length: int) -> Iterable[int]:
raise NotImplementedError()

@classmethod
def test(cls, lengths: Sequence[int], method: Method) -> Iterator[float]:
for length in lengths:
array = list(cls.make_data(length))
start = time()
method(array)
end = time()
yield end - start

class Optimistic(History):
@staticmethod
def make_data(length: int) -> Iterable[int]:
return range(length)

class Pessimistic(History):
@staticmethod
def make_data(length: int) -> Iterable[int]:
return range(length-1, -1, -1)

class Average(History):
@staticmethod
def make_data(length: int) -> Iterable[int]:
for _ in range(length):
yield randrange(1000)

def make_plot(
lengths: Sequence[int],
histories: Iterable[History],
name: str,
) -> plt.Figure:
fig, ax = plt.subplots()
for history in histories:
ax.loglog(lengths, history.measures, label=type(history).__name__)
ax.set_title(name)
ax.legend()
return fig

def test(
method: Method,
lengths: Sequence[int],
) -> Iterator[History]:
for history_t in (Optimistic, Average, Pessimistic):
yield history_t(lengths, method)

def insertion_sort(array: List[int]) -> None:
for j in range(1, len(array)):
key = array[j]
i = j - 1
while (i > -1) and key < array[i]:
array[i + 1] = array[i]
i = i - 1
array[i + 1] = key

def main() -> None:
lengths = (10, 100, 300, 500, 700)
histories = test(method=insertion_sort, lengths=lengths)
make_plot(
lengths=lengths, histories=histories,
name=insertion_sort.__name__,
)
plt.show()

if __name__ == '__main__':
main()


• Perhaps useful if OP is not used to working with figure/axes: pyplot vs object-oriented interface
– tdy
Commented Nov 4, 2021 at 23:18
• I was testing it right now on the Python 3.9.8 and results are pretty weird... Everything's fine on 3.6.7 Commented Nov 14, 2021 at 21:56