| krlz ( @ 2007-11-05 18:15:00 |
На мой взгляд, unit-тесты являются полезным иснтрументом при программировании. Но вместе с тем, очень часто они используются неправильно, что ведет к снижению качества кода и понижению производительности разработчиков. Сначала я расскажу о том, как я познакомился с unit-тестами, как и где на мой взгляд их стоит использовать и расскажу о некоторых приемах, который позволяют сделать их использование более оптимальным.
Когда я только начал работать программистом, я практически сразу попал в проект, который писался по большей части test-first. Я прочитал несколько книг про тесты, и постоянно применял знания полученные из них при написании кода. К моему сожалению, от тестов было немного прока. Некоторые из них постоянно ломались, и действительно оправдывали себя, но большинство валились один раз за свою жизнь: в тот момент, когда они есть, но реализация, которую они тестируют еще не написана. Было понятно, что я использую что-то не так.
Когда я начал работать в команде MPS, я был очень удивлен. Программа было достаточно сложной, но тестов на нее не было вообще. При этом код был достаочно прост и понятен, и проблем с качеством и стабильностю не возникало. Было очевидно, что можно писать совершенно работающие программы совсем без тестов, что бы не говорили авторы книжек про TDD. Более того, даже с рефакторингом проблем не возникало, обычно несколько дней после крупной реорганизации кода, все было совершенно работающим. С другой стороны, у нас были компоненты, в которых ошибки находились чаще, чем в других. Более того, часто многие из них повторялись. Вобщем, жить совсем без тестов это не выход. Иногда они могут облегчить жизнь.
Одна крайность в применении тестирования это когда тестируется абсолютно все. Как говорил один из гуру тестирования, тестировать нужно то, что может сломаться.Это утверждение можно довести до крайности: сломаться может, понятное дело, практически все, поэтому тестировать надо абсолютно все. Даже геттеры и сеттеры. Опасно это по нескольким причинам. Во первых программисты тратят свое драгоценное время, за которое они могли бы написать полезный код. Во вторых, и это по моему самое важное, при тестировании прерывется поток мыслей, программист теряет контекст, из-за чего делает еще больше ошибок и производительность еще больше падает. В третьих, тесты это код. Код требует поддержки, и если код приходится менять, то с ним нужно менять еще и тесты. Вобщем, при неправильном использовании тестов, можно получить кучу проблем.
Другая крайность это не тестировать ничего. Практическ в любой программе есть компоненты, ошибки в которых происходят чаще других. Более того, эти ошибки бывает повтоярются снова и снова. Программисты в проектах, не применяющие тестирования, проверяют то, что эти компоненты работаю руками. На это может уходить много времени, так что как и в тестировании всего подряд, мы имеем проблемы: поток мыслей прерывается на ручное тестирование, при ручном тестировании можно легко допустить ошибку, к тому же, тестирование руками это механическая работа, и тут можно пропустить ошибку, особенно если ты делаешь это 5 раз за день. Вобщем, случаи в который имеет смысл применять тесты, безусловно имеются.
Тесты полезная вещь, но использовать их надо только там где это действительно необходимо. Во первых, стоит использовать статически типизированный язык. Компилятор такого языка может обеспечить вам большое количество проверок бесплатно, и было бы глупо этим не воспользоваться. Не нужно идти на поводу у моды, используя популярные сейчас динамически типизируемые языки. В нех нет ничего такого, чего бы не было в Java или C#. Хуже того, к динамически типизированным языкам очень тяжело написать хорошее IDE, и то что в Java делается при помощи IDE за 2 секунды, в Ruby будет делаться гораздо дольше.
Не стоит тестировать вещи, которые практически никогда не ломаются. Например UI, OR mapping, и другой код, с несложной логикой. Тесты не позволят вам гарантировать, что программа будет работать правильно. Это можно сделать только при помощи доказательства правильности программы, что для 99.9% программ неоправданно и просто нереально.
Если у вас имеется компонента, в которой часто встречаются ошибки, имеет смысл задумать о том, чтобы ее тестировать. Это именно тот случай, когда писать тесты оправдано. При этом, важно тестировать компоненту на достаточно высоком уровне, чтобы тесты не приходилось менять при каждом рефакторинге. Например, если вы пишете компилятор, не стоит проверять, что он сгенерировал именно тот код, какой указан в тестах, это то, что будет обязательно менятся в процессе разработки компилятора. Лучше проверить, а правильно ли работает программа выданная компилятором.
При тестировании часто используют средства code coverage, такие как EMMA и Clover. Они позволяют определить то, какой код выполнялся при исполнении тестов. Обычно они используются для оценки качества тестов. В сложном коде, в том коде, который я призываю покрывать unit-тестами часто бывают сложная структура потока управления. Например, это могут быть вложенные if-ы со сложными условиями. Среди них часто бывают те, которые никогда не исполняются. Понять это бывает трудно, программист может забыть зачем он писал этот кусочек кода, а средства статического анализа могут не распознать недостигаемый код. Вот тут-то и бывают особенно полезны code coverage тулзы. Если у вас есть достаточно полный набор тестов, код который не исполняется будет виден. А когда видно какой код не исполняется, бывает достаточно несложно понять почему это происходит. Таким образом, имея хорошо оттестираванный код, можно упростить его сопровождения, не только через проверку его правильности на тестовых данных.
Вобщем, unit-тесты это мощный и полезный инструмент, но как и всякий инструмент его нужно использовать только там, где это реально оправдано.