Декомпозиция юнит-тестов
От: FrozenHeart  
Дата: 28.04.16 08:07
Оценка:
Приветствую.

Скажите, пожалуйста, какие правила используете Вы / Ваша компания при выделении кода в отдельные классы и функции используемого Вами 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')


Вы бы поступили точно так же?

В общем, интересуют подходы к декомпозиции тестового кода.

юнит-тесты unit tests тестирование
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.