Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
Если же в будущем появится необходимость учитывать титул, второе имя, отчество или еще что-то, усложняющее функцию и добавляющее некую разветвленность в ее поведении — пишете тест и потом начинаете реализацию.
чем на механическое вбивание новых тестов для тривиальных функций
То что пишет программист это модульный тест.
мой код полностью отвечает придуманной мне архитектуре.
mysql_query("SELECT id, name FROM table"); или $templater->render('item.tpl', ['id' => $id, 'name' => $name]);. То есть новичок думает, что пишет юнит-тест, но на самом деле это уже как минимум интеграционный получается. Тестируются библиотеки, SQL-запрос, шаблон, но никак не логика PHP-кода (вернее она только косвенно тестируется). Новичку нужно было написать мок на эти функции, который был проверял, что вызывается mysql_query() с параметром «SELECT id, name FROM table» или $templater->render с параметрами 'item.tpl' и ['id' => $id, 'name' => $name], но не то, что они возвращают.Вы ведь пишите код не потому, что у вас есть тест. Вы пишите тест потому, что у вас есть необходимость в функционале, который вы знаете изначально.
def full_name
"#{first_name} #{last_name}"
end
public int Year
{
get { return 2013; }
set { }
}
it "should return full name based on first name and last name" do
full_name("John","Doe").should == "John Doe"
end
def full_name(first_name,last_name)
"John Doe"
end
it "should return full name based on first name and last name" do
full_name("John","Doe").should == "John Doe"
full_name("Jane","Smith").should == "Jane Smith"
end
def full_name(first_name,last_name)
if first_name == "John" and last_name == "Doe"
"John Doe"
else
"Jane Smith"
end
end
Мне платят за код, который работает, а не за тесты, поэтому моя философия заключается в том, чтобы тестировать настолько мало, насколько это возможно для достижения нужного уровня уверенности (подозреваю, что мой уровень уверенности выше, чем стандарты индустрии, но возможно это просто мое эго). Если я обычно не допускаю ошибок определенного типа (как передача неверных аргументов в конструктор, например), то я не тестирую это.
зачем писать тест для элементарной функции, длина которой едва ли больше названия теста для нее.
Как я, опять же, писал выше, когда к функции возрастут требования, я сразу это пойму.
Если другой разработчик решит править функцию до правки ее теста, то это будет последняя функция, которую он поправит в этом проекте. В противном же случае он заметит отсутствие теста и, если таковой действительно необходим, напишет.
Во-вторых, я все же люблю включать мозг когда пишу код. Если я вижу, что вот эта тривиальная функция возвращает полное имя пользователя, то я не стану переписывать ее на возврат имени сервера.
не вижу бОльшей вины другого разработчика, который правит функцию без теста, чем того, кто не написал тест при написании функции сразу.Я вижу. Мы тут все же TDD занимаемся, а значит при правке нужно первым делом править тест. Теста нет? (Функция элементарна, он ни к чему) Напиши тест, если новый требуемый функционал не тривиален. Не нужно бросаться в крайности.
Мозг не причем. Функция пишется не для самосуществования. Зачем-то используется. И разработчик может увидеть в результате рефакторинга, что по сути, функция делает что-то другое. И не правильно называется.Это, опять же, не refActoring, а refUcktoring. Т.е. порча кода. Если какому-то разработчику вздумалось переименовать функцию или изменить ее функционал наобум, то ему хватит ума и удалить тест от старой функции и написать новый. Давайте для начала предположим, что не все разработчики идиоты, и не будем разрабатывать защиту от кодера-дурака.
а значит при правке нужно первым делом править тест. Теста нет? (Функция элементарна, он ни к чему)
Это, опять же, не refActoring, а refUcktoring. Т.е. порча кода. Если какому-то разработчику вздумалось переименовать функцию или изменить ее функционал наобум, то ему хватит ума и удалить тест от старой функции и написать новый.
Писали функцию вроде «GetPersonFullName», а потом, например, так случилось, что это стало идентификатором машины клиента.Вот это и есть наобум. Как в какой вселенной может случиться такая ситуация?
Поэтому, когда вы пишете тест перед написанием кода функции (какого-то ее функционала) — это одно. А когда вы начинаете переделывать функцию и начинаете с правки теста — это другое.Что за двойные стандарты? Если уж применяем TDD, то будьте добры начинать правку кода с тестов. А то получается, что делать допущения в моем случае — богомерзко, а в вашем — ничего страшного.
Например, на вашу фукнцию уже с десяток тестов, причем косвенных. Те, которые там где-то интеграционно как вы писали проверяют и эту примитивную функцию. У там неявно тест подразумевает, что функция вернет «John Doe», а не «Doe John Doe». Меняя один тест, который, как вам кажется, тестирует нужный функционал (или дописывая ненаписанный тест), вы все тесты делаете противоречивыми. Т.е. разные утверждения (тесты) противоречат друг другу.Омфг. Вот у меня есть тест, проверяющий правильность вывода списка пользователей. Он проверяет, что выводится имя и фамилия. До этого я «ручками» вставлял свойства «first_name» и «last_name». Со временем имена пользователей начали возникать то в одном, то в другом месте, и я, чтобы не нарушать принцип DRY, написал функцию-хелпер full_name. По факту эта функция проверяется во всех местах, где проверяется вывод пользователя. Если я напишу на нее отдельный тест, то это не отменит того, что она будет косвенно проверяться в остальных местах. Если я изменю эту функцию, то у меня завалится на 1 тест больше. Если я поменяю какой-то тест, то у меня станет на 1 противоречивый тест больше. Вот и вся разница.
Как в какой вселенной может случиться такая ситуация?
Что за двойные стандарты? Если уж применяем TDD, то будьте добры начинать правку кода с тестов.
Я в другой вселенной живу. Постоянно такие ситуации. Ничто на века не пишется. И код подсказывает тоже, что и как использовать надо и что может не так называться, как думали заранее.Ничто не пишется на века, да. Но никогда ни в одном проекте я не встречал ситуации, чтобы кто-то менял поведение функции на нечто совершенно с предыдущим поведением не связанное.
Тесты пишутся с нуля до кода, тесты правятся после падений. Вот так логично.Есть 2 ситуации, в которых правится код: Рефакторинг/оптимизация и добавление/изменение функционала. В первом случае правка тестов не нужна, т.к. требования к коду не меняются. Во втором правка кода непременно должна начаться с изменения тестов, т.к. это стандартный цикл тест — код — рефакторинг — тест — … итд.
если функцию при ее текущем функционале нельзя подвергнуть рефакторингу, она тривиальна.
Если предполагаемая реализация функционала не сможет быть подвергнута рефакторингу, то эта реализация тривиальна и тесты на неё не требуютсяДа, так вернее. Тут сразу отражается фат того, что у функции уже есть предполагаемая реализация, которая предельно проста.
Вы решаете изменить код функции. Т.е. не добавить новое требование, а изменить. Так надо изменять и любоваться падением тестов.
Надо изменить тест (изменить требование), полюбоваться на упавший тест, а потом изменить функцию и, возможно, полюбоваться на новые упавшие тесты, в которых функция «косвенно тестировалась».
class FirstTest extends \PHPUnit_Framework_TestCase
{
/** @dataProvider getters */
function testMe($setter, $getter, $value, $exp = null)
{
$obj = new SomeClass();
$this->assertEmpty($obj->$getter(), $getter . '() is empty');
$obj->$setter($value);
$this->assertEquals($exp ?: $value, $obj->$getter(), $getter . '() test');
}
function getters()
{
return array(
array('setOne', 'getOne', 1),
array('setTwo', 'getTwo', 2),
array('setThree', 'getThree', 3, 4),
);
}
}
class SomeClassTests extends TestCase {
public void testSetOne() {
SomeClass x = new SomeClass();
x.setOne(1);
assertEquasl(1, x.getOne());
}
public void testSetTwo() {
SomeClass x = new SomeClass();
x.setTwo(2);
assertEquasl(2, x.getTwo());
}
...
}
$this->assertEquals($exp ?: $value, $obj->$getter(), $getter. '() test');
Не потерять бы за этим всем самое главное качество юнит-тестов: Скорость их выполнения! ;)
a.Verify(Reflect<DateViewModel>.GetProperty<int>(sut => sut.Year));
var sut = new DateViewModel();
sut.Year = 2010;
Assert.Equal(2010, sut.Year);
Роберт Мартин приводит в качестве аргумента то, что геттеры и сеттеры тестируются косвенно через другие тесты, но, несмотря на то, что это может быть верно на этапе объявления члена, не факт, что это будет верно сколь угодно долгое время. Спустя месяцы, эти тесты могут быть удалены, оставляя введённый тривиальный член непокрытым тестами.
Тестирование тривиального кода