Вы должны разбить класс под вопросом.
Каждый класс должен выполнить простую задачу. Если ваша задача слишком сложна для тестирования, то задача, которую выполняет класс, слишком велика.
Не обращая внимания на глупость этого дизайна:
class NewYork
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class Miami
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class MyProfit
{
MyProfit(NewYork new_york, Miami miami);
boolean bothProfitable();
}
ОБНОВИТЬ
Проблема с методами-заглушками в классе заключается в том, что вы нарушаете инкапсуляцию. Ваш тест должен проверять, соответствует ли внешнее поведение объекта спецификациям. Что бы ни происходило внутри объекта, это не его дело.
Тот факт, что FullName использует FirstName и LastName, является деталью реализации. Ничто вне класса не должно волновать, что это правда. Посмеиваясь над публичными методами для тестирования объекта, вы делаете предположение о том, что объект реализован.
В какой-то момент в будущем это предположение может перестать быть верным. Возможно, вся логика имени будет перемещена в объект Name, который Person просто вызывает. Возможно, FullName будет напрямую обращаться к переменным-членам first_name и last_name, а не вызывать FirstName и LastName.
Второй вопрос: почему вы чувствуете необходимость сделать это? В конце концов, ваш класс может быть протестирован примерно так:
Person person = new Person("John", "Doe");
Test.AssertEquals(person.FullName(), "John Doe");
Вы не должны чувствовать необходимость что-либо заглушки для этого примера. Если вы это сделаете, то вы счастливы и здоровы ... остановите это! Нет смысла издеваться над этими методами, потому что у вас все равно есть контроль над тем, что в них.
Единственный случай, когда кажется, что методы, которые FullName использует для имитации, имеет смысл, если каким-то образом FirstName () и LastName () были нетривиальными операциями. Возможно, вы пишете один из этих генераторов случайных имен, или FirstName и LastName запрашивают у базы данных ответ, или что-то в этом роде. Но если это то, что происходит, это говорит о том, что объект делает что-то, что не принадлежит классу Person.
Иными словами, насмешка над методами - это взятие объекта и разбивание на две части. Одна часть подвергается насмешкам, в то время как другая часть испытывается. То, что вы делаете, - это, по сути, случайное разрушение объекта. Если это так, просто разбейте объект.
Если у вас простой урок, вы не должны испытывать необходимость его макетировать во время теста. Если ваш класс достаточно сложен, и вы чувствуете необходимость издеваться над ним, вы должны разбить его на более простые части.
ОБНОВЛЕНИЕ СНОВА
На мой взгляд, объект имеет внешнее и внутреннее поведение. Внешнее поведение включает вызовы значений в другие объекты и т. Д. Очевидно, что все в этой категории должно быть проверено. (иначе, что бы вы проверили?) Но внутреннее поведение не должно быть проверено.
Теперь внутреннее поведение проверяется, потому что именно оно приводит к внешнему поведению. Но я не пишу тесты непосредственно на внутреннее поведение, только косвенно через внешнее поведение.
Если я хочу что-то проверить, я полагаю, что это нужно переместить, чтобы оно стало внешним поведением. Вот почему я думаю, что если вы хотите что-то издеваться, вы должны разделить объект так, чтобы вещь, которую вы хотите высмеять, теперь была во внешнем поведении рассматриваемых объектов.
Но какая разница? Если FirstName () и LastName () являются членами другого объекта, действительно ли это изменит проблему FullName ()? Если мы решим, что необходимо издеваться над FirstName, а LastName действительно помогает им оказаться на другом объекте?
Я думаю, что если вы используете ваш насмешливый подход, то вы создаете шов на объекте. У вас есть такие функции, как FirstName () и LastName (), которые напрямую взаимодействуют с внешним источником данных. У вас также есть FullName (), который не имеет. Но так как они все в одном классе, это не очевидно. Некоторые части не должны иметь прямой доступ к источнику данных, а другие. Ваш код будет понятнее, если просто разбить эти две группы.
РЕДАКТИРОВАТЬ
Давайте сделаем шаг назад и спросим: почему мы высмеиваем объекты при тестировании?
- Сделайте тесты запущенными последовательно (избегайте доступа к вещам, которые меняются от запуска к запуску)
- Избегайте доступа к дорогостоящим ресурсам (не обращайтесь к сторонним сервисам и т. Д.)
- Упростить тестируемую систему
- Упростите тестирование всех возможных сценариев (например, имитация сбоя и т. Д.)
- Избегайте зависимости от деталей других частей кода, чтобы изменения в этих других частях кода не нарушали этот тест.
Теперь, я думаю, причины 1-4 не относятся к этому сценарию. Насмешка внешнего источника при проверке полного имени устраняет все эти причины насмешек. Единственная не обработанная часть - это простота, но кажется, что объект достаточно прост, и это не проблема.
Я думаю, что вы беспокоитесь о причине номер 5. Проблема заключается в том, что в какой-то момент в будущем изменение реализации FirstName и LastName нарушит тест. В будущем FirstName и LastName могут получать имена из другого местоположения или источника. Но FullName, вероятно, всегда будет FirstName() + " " + LastName()
. Вот почему вы хотите протестировать FullName, высмеивая FirstName и LastName.
То, что у вас есть, это некоторое подмножество объекта персонажа, которое может измениться больше, чем другие. Остальная часть объекта использует это подмножество. Это подмножество в настоящее время извлекает свои данные, используя один источник, но может извлечь эти данные совершенно другим способом на более позднем этапе. Но для меня это звучит так, будто это подмножество - особый объект, пытающийся выбраться.
Мне кажется, что если вы издеваетесь над методом объекта, вы разделяете его на части. Но вы делаете это на специальной основе. Ваш код не дает понять, что внутри вашего объекта Person есть две отдельные части. Просто разделите этот объект в вашем реальном коде, чтобы из чтения кода было понятно, что происходит. Выберите фактическое разделение объекта, которое имеет смысл, и не пытайтесь разделить объект по-разному для каждого теста.
Я подозреваю, что вы можете возражать против разделения вашего объекта, но почему?
РЕДАКТИРОВАТЬ
Я был неправ.
Вы должны разбивать объекты, а не вводить произвольные разбиения, издеваясь над отдельными методами. Тем не менее, я был слишком сосредоточен на одном методе расщепления объектов. Однако OO предоставляет несколько методов разбиения объекта.
Что бы я предложил:
class PersonBase
{
abstract sring FirstName();
abstract string LastName();
string FullName()
{
return FirstName() + " " + LastName();
}
}
class Person extends PersonBase
{
string FirstName();
string LastName();
}
class FakePerson extends PersonBase
{
void setFirstName(string);
void setLastName(string);
string getFirstName();
string getLastName();
}
Может быть, это то, что вы делали все это время. Но я не думаю, что этот метод будет иметь проблемы, которые я видел с методами насмешки, потому что мы четко определили, на чьей стороне находится каждый метод. И используя наследование, мы избегаем неловкости, которая возникнет, если мы будем использовать дополнительный объект-обертку.
Это вносит некоторую сложность, и только для пары служебных функций я, вероятно, просто протестирую их, высмеивая основной сторонний источник. Конечно, им грозит повышенная опасность взлома, но переставлять их не стоит. Если у вас есть достаточно сложный объект, который вам нужно разделить, я думаю, что-то вроде этого - хорошая идея.