Вот простой пример использования иерархии наследования.
Учитывая простую иерархию классов:
Giraffe
/
LifeForm <- Animal <-
\
Zebra
В коде:
public abstract class LifeForm { }
public abstract class Animal : LifeForm { }
public class Giraffe : Animal { }
public class Zebra : Animal { }
Инвариантность ( Общий с параметризованным типом, украшенным ни in
, ни out
)
По-видимому, такой метод, как этот
public static void PrintLifeForms(IList<LifeForm> lifeForms)
{
foreach (var lifeForm in lifeForms)
{
Console.WriteLine(lifeForm.GetType().ToString());
}
}
..., должен принимать гетерогенный набор: (который это делает)
var myAnimals = new List<LifeForm>
{
new Giraffe(),
new Zebra()
};
PrintLifeForms(myAnimals); // Giraffe, Zebra
Однако передача набора из более производного типа не выполняется!
var myGiraffes = new List<Giraffe>
{
new Giraffe(), // "Jerry"
new Giraffe() // "Melman"
};
PrintLifeForms(myGiraffes); // Compile Error!
blockquote>
cannot convert from 'System.Collections.Generic.List<Giraffe>' to 'System.Collections.Generic.IList<LifeForm>'
Почему? Поскольку общий параметр
IList<LifeForm>
не является ковариантным -IList<LifeForm>
является инвариантным и принимает только коллекции (которые реализуют IList), где параметризованный типT
должен бытьLifeForm
.Если я злонамеренно изменяю реализацию метода
PrintLifeForms
(но оставляю одну и ту же сигнатуру метода), причина, по которой компилятор предотвращает передачуList<Giraffe>
, становится очевидным:public static void PrintLifeForms(IList<LifeForm> lifeForms) { lifeForms.Add(new Zebra()); }
Поскольку
IList
позволяет добавлять или удалять элементы, любой подклассLifeForm
может быть добавлен к параметруlifeForms
и будет нарушать тип любой коллекции производных типов, переданных методу. (В этом случае вредоносный метод попытается добавитьZebra
вvar myGiraffes
). К счастью, компилятор защищает нас от этой опасности.Ковариация (общий с параметризованным типом, украшенный
out
)Ковариация широко используется с неизменяемыми коллекциями (то есть, когда новые элементы не могут быть добавлено или удалено из коллекции)
Решение вышеприведенного примера заключается в том, чтобы использовать ковариантный общий тип, например
IEnumerable
(определяется какIEnumerable<out T>
). Это предотвращает изменение коллекции, и в результате теперь может быть передана любая коллекция с подтипомLifeForm
:public static void PrintLifeForms(IEnumerable<LifeForm> lifeForms) { foreach (var lifeForm in lifeForms) { Console.WriteLine(lifeForm.GetType().ToString()); } }
PrintLifeForms()
теперь можно вызвать с помощьюZebras
,Giraffes
и любойIEnumerable<>
любого подклассаLifeForm
Контравариантность (общий с параметризованным типом, украшенным
in
)Контравариантность часто используется, когда функции передаются как Параметры.
Вот пример функции, которая принимает параметр
Action<Zebra>
в качестве параметра и вызывает его на известном экземпляре Zebra:public void PerformZebraAction(Action<Zebra> zebraAction) { var zebra = new Zebra(); zebraAction(zebra); }
Как и ожидалось, это прекрасно работает:
var myAction = new Action<Zebra>(z => Console.WriteLine("I'm a zebra")); PerformZebraAction(myAction); // I'm a zebra
Интуитивно это не удастся:
var myAction = new Action<Giraffe>(g => Console.WriteLine("I'm a giraffe")); PerformZebraAction(myAction);
blockquote>
cannot convert from 'System.Action<Giraffe>' to 'System.Action<Zebra>'
Однако, это преуспевает
var myAction = new Action<Animal>(a => Console.WriteLine("I'm an animal")); PerformZebraAction(myAction); // I'm an animal
, и даже это также удается:
var myAction = new Action<object>(a => Console.WriteLine("I'm an amoeba")); PerformZebraAction(myAction); // I'm an amoeba
Почему? Поскольку
Action
определяется какAction<in T>
, то естьcontravariant
.Хотя это может быть сначала неинтуитивным (например, как можно передать
Action<object>
как параметр, требующийAction<Zebra>
]?), если вы распакуете этапы, вы заметите, что сама вызываемая функция (PerformZebraAction
) отвечает за передачу данных (в данном случае экземпляраZebra
) в функцию - данные не поступают из вызывающий код.Из-за инвертированного подхода к использованию функций более высокого порядка таким образом, к тому времени, когда вызывается
Action
, это более производный экземпляр объекта, который вызывается против функцииzebraAction
(передается как параметр), который сам использует менее производный тип.
Прежде всего, вы должны действительно прочитать хороший учебник о Mockito, например, из vogella . Видите ли, вы просто складываете много вещей, которые бессмысленны .
Например:
@Mock
private TextQueue textQueue;
, чтобы в вашем тестовом примере было
textQueue = spy(textQueue);
. Вы должны быть действительно ясно об этом. Шпион строится на реальном экземпляре тестируемого вами класса. Создание шпиона, который шпионит за издевательством, как сказано: это не имеет смысла.
Тогда:
}catch(final Exception e){
Logger.error("add text to queue threw an error" + e);
Опять бессмысленно. Вся идея ваших модульных тестов заключается в том, что они не справляются , когда что-то не так. Когда ваш производственный код выдает неожиданные исключения, вы не регистрируете их, а просто позволяете им в конце пройти ваш тестовый случай.
Чтобы ответить на реальный вопрос: выглядит , как будто ваш производственный код использует конкретный «постоянный» экземпляр регистратора. Учитывая этот дизайн, единственный способ проверить ваш производственный код состоит в том, чтобы:
underTest
вашего производственного кода класс underTest
(и каким-то образом заставляет метод генерировать исключение) error()
Мы не можем дать лучший совет, потому что вашего кода недостаточно, мы действительно не знаем, что делает ваш производственный класс (например: мы не знаем, что такое LOGGER и где он исходит из., если это статическая переменная, то, скорее всего, вы не можете получить «контроль» над ней с помощью Mockito).
В любом случае, вам, вероятно, действительно нужен концепт шпион . Чтобы протестировать addTextToQueue()
, вам нужен способ вызвать «реальную» реализацию addTextToQueue()
, но вызов addTser()
внутри должен перейти к макету (чтобы вы могли контролировать, что делает этот вызов). [1124 ]
Но как уже было сказано: начните с действительно исследования того, как работает Mockito, вместо того, чтобы собирать воедино вещи, которые не имеют смысла в каком-то подходе «проб и ошибок». Правильное модульное тестирование с использованием насмешек сложно, вы не можете узнать это методом «проб и ошибок».