Is this good way of creating tests? I have no experience how they do it in companies. Ignore uppercase names of variables.. I may change it eventually.
reduction_strategy.py
defines my ListArray
class, which I've written unit tests for.
ListArray
defines object that takes care of matrices containing symbolic elements. Basic matrix operations adding and multiplying, syntax for using this object slicing, Changing rows and columns. This class let me keep 2d object, and make slice
's easier, Matrix
object has indexing from 0 to n
as 1d always.
Examples:
Matrix equations Multiply
# reduction_strategy.py
import numpy as np
from copy import copy, deepcopy
from sympy import Matrix, zeros, ones, eye, diag
from itertools import permutations
class ListArray:
def __init__(self, array):
# self.__class__ = 'ListArray'
if type(array) == int:
array = [array]
if type(array[0]) != list:
self.single = True
self.size = (1, len(array))
array = [array] # Matrix always uses 2d !
else:
self.single = False
self.size = (len(array), len(array[0]))
if len(array) == 1:
self.single = True
for row in array:
if type(row[0]) == list:
print('To deep Array:', array)
break
self.array = Matrix(array)
self.history = [{'created': array}]
def __add__(self, other):
return ListArray([[
self.array[r * self.size[1] + c] +
other.array[r * self.size[1] + c]
for c in range(self.size[1])
]
for r in range(self.size[0])
])
def __eq__(self, other):
return self.array == other.array
def __getitem__(self, i):
# Condition to extract row!
if type(i) is slice:
rstart = i.start if i.start else 0
rstop = i.stop if i.stop else self.size[0]
rstep = i.step if i.step else 1
cstart = 0
cstop = self.size[1]
cstep = 1
# Condition to Slice with number -> one Row
elif type(i) is int:
rstart = i
rstop = i + 1
rstep = 1
cstart = 0
cstop = self.size[1]
cstep = 1
# Condition Double slice
elif type(i[0]) is slice and type(i[1]) is slice:
rslice = i[0]
cslice = i[1]
rstart = rslice.start if rslice.start else 0
rstop = rslice.stop if rslice.stop else self.size[0]
rstep = rslice.step if rslice.step else 1
cstart = cslice.start if cslice.start else 0
cstop = cslice.stop if cslice.stop else self.size[1]
cstep = cslice.step if cslice.step else 1
else:
raise ValueError(f"Incorrect Slice params: {i}")
array = self.array
if self.single:
# array = array]
out = [
array[r * self.size[1] + c]
for c in range(cstart, cstop, cstep)
for r in range(rstart, rstop, rstep)
]
else:
out = [[
array[r * self.size[1] + c]
for c in range(cstart, cstop, cstep)
]
for r in range(rstart, rstop, rstep)
]
return ListArray(out)
def __repr__(self):
txt = '['
for row in range(self.size[0]):
if row == 0:
txt += f"{'[':>1}"
else:
txt += f'\n{"[":>3}'
for col in range(self.size[1]):
txt += f' {self.array[self.size[1]*row +col]},'
if row >= self.size[0] - 1:
txt += ']'
else:
txt += f'],'
txt += f']'
return txt
def __sub__(self, other):
return ListArray([[
self.array[r * self.size[1] + c] -
other.array[r * self.size[1] + c]
for c in range(self.size[1])
]
for r in range(self.size[0])
])
# def __class__(self):
# pass
def __len__(self):
# print(len(self.array))
if self.single:
return self.size[1]
return self.size[0]
def __mul__(self, other):
result = self.array * other.array
result = [result[other.size[1]*row: other.size[1]*row + other.size[1]]
for row in range(self.size[0])
]
return ListArray(result)
def switch_Rows(self):
print("Finish this")
def switch_cols(self):
print("Finish this")
def transp(self):
c = self.size[0]
r = self.size[1]
array_t = self.array.T
return ListArray([array_t[row*c:row*c + c] for row in range(r)])
def match_patern_any(self):
# A | B
# --|--
# C | D
if self.size[0] % 2 == 1 or self.size[1] % 2 == 1:
raise ValueError("Matrix has to be even size!")
half_rows = int(self.size[0]/2)
half_cols = int(self.size[1]/2)
square_a = self[0:half_rows, 0:half_cols]
square_b = self[0:half_rows, half_cols:self.size[1]]
square_c = self[half_rows:self.size[0], 0:half_cols]
square_d = self[half_rows:self.size[0], half_cols:self.size[1]]
return any([
square_a == square_b,
square_a == square_c,
square_a == square_d,
square_b == square_c,
square_b == square_d,
square_c == square_d
])
class Reduktor(ListArray):
def __init__(self, matrix_input):
ListArray.__init__(self, array=matrix_input)
assert (self.size[0] == self.size[0] and self.size[0] % 2 == 0)
print("R:", len(matrix_input))
print("C:", len(matrix_input[0]))
self.matrix = matrix_input
def col_order(self):
pass
if __name__ == "__main__":
a = [['a', 'b', 'c', 'g'],
['d', 'e', 'f', 'x'],
['q', 't', 'i', 'y'],
['i', 'o', 'p', 's']]
b = [['d', 'e', 'f'], ['g', 'h', 'i']]
c = ['x', 'y', 'z']
d = [['i', 'b', 'c', 'd']]
A = ListArray(a)
print(type(A))
red = Reduktor(a)
testing_ListArray.py
contains the unit tests:
# testing_ListArray.py
from reduction_strategy import ListArray
import unittest
class TestinglistArray(unittest.TestCase):
def setUp(self):
self.a = \
[['a', 'b', 'c', 'g'],
['d', 'e', 'f', 'x'],
['q', 't', 'i', 'y'],
['i', 'o', 'p', 's']]
self.b = \
[['a', 'c', 'j', 'v'],
['d', 'e', 'f', 'x'],
['q', 't', 'i', 'y'],
['i', 'o', 'p', 's']]
def test_equal(self):
A = ListArray(self.a)
B = ListArray(self.b)
Aprim = ListArray(self.a)
cut_A = A[2:, 0:2]
cut_B = B[2:, 0:2]
assert A == Aprim
assert not A == B
assert cut_A == cut_B
assert A[2] == ListArray([['q', 't', 'i', 'y']])
assert A[2] == ListArray(['q', 't', 'i', 'y'])
def test_fit(self):
a = \
[['a', 'b', 'a', 'b'],
['a', 'b', 'a', 'b']]
b = [['a', 'b'],
['c', 'd']]
c = ['a', 'b', 'c']
A = ListArray(a)
B = ListArray(b)
C = ListArray(c)
assert A.match_patern_any()
assert not B.match_patern_any()
self.assertRaises(ValueError, C.match_patern_any)
def test_traspose(self):
a = [['a', 'g'],
['b', 'x']]
b = [['a', 'b'],
['g', 'x']]
a2 = [['a', 'g', 'j'],
['b', 'x', 'k']]
b2 = [['a', 'b'],
['g', 'x'],
['j', 'k']]
A = ListArray(a)
B = ListArray(b)
A2 = ListArray(a2)
B2 = ListArray(b2)
assert A.transp() == B # Transposed A = A'
assert A2.transp() == B2 # Transposed A2 = A2'
def test_mult(self):
a = [['a', 'g'],
['b', 'x']]
b = [['b', 'b'],
['d', 'b']]
res1 = [['a*a + b*g', 'a*g + x*g'],
['b*a + x*b', 'g*b + x*x']]
res2 = [['a*b + d*g', 'a*b + b*g'],
['b*b + d*x', 'b*b + x*b']]
A = ListArray(a)
B = ListArray(b)
Res1 = ListArray(res1)
Res2 = ListArray(res2)
assert(A*A == Res1)
assert(A*B == Res2)
def test_arra_length(self):
A = ListArray(self.a)
assert A.size[0] == len(A)
assert A.size[0] == 4
assert A.size[1] == len(A[0])
assert A.size[1] == 4
if __name__ == '__main__':
unittest.main()
1 Answer 1
If you are using the unittest
module, you should use it to its full potential. You are already using assertRaises
, but there is also assertEqual
, assertTrue
and many more. The difference is in the amount of information you get when the test case fails:
import unittest
class Test(unittest.TestCase):
def setUp(self):
self.a, self.b = [1, 2, 3], [2, 3, 4]
def test_a(self):
assert self.a == self.b
def test_b(self):
self.assertEqual(self.a, self.b)
if __name__ == "__main__":
unittest.main()
produces this output:
$ python3 /tmp/test.py
FF
======================================================================
FAIL: test_a (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 7, in test_a
assert self.a == self.b
AssertionError
======================================================================
FAIL: test_b (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 10, in test_b
self.assertEqual(self.a, self.b)
AssertionError: Lists differ: [1, 2, 3] != [2, 3, 4]
First differing element 0:
1
2
- [1, 2, 3]
+ [2, 3, 4]
----------------------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (failures=2)
Note how test_a
, which uses assert
like your code, gives you almost no information on what went wrong, whereas test_b
tells you very explicitly what went wrong.
ListArray
does, make the title of your post describe the purpose of that code, and then clean up the variable names, because reviewers are expecting to see your finished, working, production-ready code. As it stands, there's very little context, and it's debatable whether there's enough for reviewers to go on: FYI a reviewer has voted to close for lack of concrete context. Please take a few moments to fix the typos and add the missing information; reviewers will be spending much more time than that reviewing. \$\endgroup\$