Приветствую.
Скажите, пожалуйста, какие правила используете Вы / Ваша компания при выделении кода в отдельные классы и функции используемого Вами testing framework'а?
Приведу пару примеров.
Например, у нас есть проект на Python, и в качестве testing framework'а используется unittest из стандартной библиотеки Python.
В этом проекте имеется файл utils.py, который содержит свободные функции совершенно разной направленности -- например, dict_join и split_first_n.
Я взял да написал для этого дела следующий класс:
class TestUtils(unittest.TestCase):
def test_dict_join(self):
d = {
'foo': 0,
'bar': 'baz'
}
res = common.utils.dict_join(d, kv_delim='=', elems_delim='&')
res.should.equal('foo=0&bar=baz')
def test_split_first_n(self):
res = common.utils.split_first_n('012345', 3)
res.should.equal([
'012',
'345'
])
res = common.utils.split_first_n('012345', 0)
res.should.equal([
'',
'012345'
])
res = common.utils.split_first_n('012345', 10)
res.should.equal([
'012345',
''
])
res = common.utils.split_first_n('012345', -1)
res.should.be.none
Как видите, внутри функций test_dict_join и test_split_first_n находится код, проверяющий одновременно как ожидаемые входные значения, так и "исключительные" (всякие None, -1, 0 и т.д.).
Такой подход нормален? Вот, почему я спрашиваю. Не было бы логичнее / правильнее / выбери_своё написать отдельные классы для каждой из этих функций (dict_join и split_first_n), в которых будут отдельные методы на каждый набор входных параметров? Т.е. имеется ввиду что-то вроде
class TestDictJoin(unittest.TestCase):
def test_expected_args(self):
pass
def test_unexpected_args(self):
pass
class TestSplitFirstN(unittest.TestCase):
def test_expected_args(self):
pass
def test_unexpected_args(self):
pass
А все эти test case'ы можно было бы объединить в один test suite, который бы как раз назывался TestUtils.
Другой пример. Имеется класс TCPSocket, являющийся обёрткой над socket из стандартной библиотеки Python (с поддержкой with-statement'ов, read_until и прочим). Для него я поступил схожим образом:
class TestTCPSocket(unittest.TestCase):
def setUp(self):
with mock.patch('socket.socket') as mock_socket:
# Note that mock_socket is a function that returns a Socket object, not a Socket object itself
mock_socket.return_value.recv.return_value = 'some_data\nsome_other_data'
self.sock = TCPSocket()
# Whatever
host = '127.0.0.1'
port = 12345
self.sock.connect(host, port)
self.sock.sock.connect.assert_called_with((host, port))
def test_recv_bytes(self):
received = self.sock.recv_bytes(4)
received.should.equal('some')
self.sock.buf.should.equal('_data\nsome_other_data')
def test_recv_until(self):
received = self.sock.recv_until('\n')
received.should.equal('some_data')
self.sock.buf.should.equal('some_other_data')
Вы бы поступили точно так же?
В общем, интересуют подходы к декомпозиции тестового кода.
Какой функционал выносите в отдельные функции test case'ов?
Тестируете ли одновременно несколько входных значений внутри одного такого метода?
Когда и в каких ситуациях объединяете test case'ы в test suite?