Когда вы объявляете ссылочную переменную (т. е. объект), вы действительно создаете указатель на объект. Рассмотрим следующий код, в котором вы объявляете переменную примитивного типа int
:
int x;
x = 10;
В этом примере переменная x является int
, и Java инициализирует ее для 0. Когда вы назначаете его 10 во второй строке, ваше значение 10 записывается в ячейку памяти, на которую указывает x.
Но когда вы пытаетесь объявить ссылочный тип, произойдет что-то другое. Возьмите следующий код:
Integer num;
num = new Integer(10);
Первая строка объявляет переменную с именем num
, но она не содержит примитивного значения. Вместо этого он содержит указатель (потому что тип Integer
является ссылочным типом). Поскольку вы еще не указали, что указать на Java, он устанавливает значение null, что означает «Я ничего не указываю».
Во второй строке ключевое слово new
используется для создания экземпляра (или создания ) объекту типа Integer и переменной указателя num
присваивается этот объект. Теперь вы можете ссылаться на объект, используя оператор разыменования .
(точка).
Exception
, о котором вы просили, возникает, когда вы объявляете переменную, но не создавали объект. Если вы попытаетесь разыменовать num
. Перед созданием объекта вы получите NullPointerException
. В самых тривиальных случаях компилятор поймает проблему и сообщит вам, что «num не может быть инициализирован», но иногда вы пишете код, который непосредственно не создает объект.
Например, вы можете имеют следующий метод:
public void doSomething(SomeObject obj) {
//do something to obj
}
В этом случае вы не создаете объект obj
, скорее предполагая, что он был создан до вызова метода doSomething
. К сожалению, этот метод можно вызвать следующим образом:
doSomething(null);
В этом случае obj
имеет значение null. Если метод предназначен для того, чтобы что-то сделать для переданного объекта, целесообразно бросить NullPointerException
, потому что это ошибка программиста, и программисту понадобится эта информация для целей отладки.
Альтернативно, там могут быть случаи, когда цель метода заключается не только в том, чтобы работать с переданным в объекте, и поэтому нулевой параметр может быть приемлемым. В этом случае вам нужно будет проверить нулевой параметр и вести себя по-другому. Вы также должны объяснить это в документации. Например, doSomething
может быть записано как:
/**
* @param obj An optional foo for ____. May be null, in which case
* the result will be ____.
*/
public void doSomething(SomeObject obj) {
if(obj != null) {
//do something
} else {
//do something else
}
}
Наконец, Как определить исключение & amp; причина использования Трассировки стека
Воздержитесь от использования вашего контейнера DI в рамках ваших модульных тестов. В модульных тестах вы пытаетесь протестировать один класс или модуль изолированно, и мало что нужно для контейнера DI в этой области.
. Тестирование интеграции отличается от теста интеграции, поскольку вы хотите проверить, как компоненты в вашей системе интегрируются и работают вместе. В этом случае вы часто используете конфигурацию вашего производства DI и меняете некоторые из своих услуг для поддельных сервисов (например, ваш MailService
), но придерживайтесь как можно ближе к реальной вещи. В этом случае вы используете свой контейнер для разрешения всего графика объекта.
Желание использовать контейнер DI в модульных тестах часто также связано с неэффективными шаблонами. Например, если вы попытаетесь создать тестируемый класс со всеми его зависимостями в каждом тесте, вы получите много дублированного кода инициализации, и небольшое изменение в тестируемом классе может в этом случае пульсировать через систему и потребовать от вас смените десятки единичных тестов. Это, очевидно, вызывает проблемы с ремонтопригодностью.
Один шаблон, который помог мне в этом много в прошлом, - это использование простого тестового отраслевого метода SUT. Этот метод централизует создание тестируемого класса и минимизирует количество изменений, которые необходимо внести, когда изменяются зависимости тестируемого класса. Так может выглядеть такой заводской метод:
private ClassUnderTest CreateClassUnderTest(
ILogger logger = null, IMailSender mailSender = null, IEventPublisher publisher = null)
{
return new ClassUnderTest(
logger ?? new FakeLogger(),
mailSender ?? new FakeMailer(),
publisher ?? new FakePublisher());
}
Этот заводский метод дублирует аргументы конструктора класса, но делает их необязательными. Для любой конкретной неподходящей зависимости будет введена новая поддельная реализация.
Это работает очень хорошо, так как в большинстве тестов вас интересует только одна или две зависимости. Другие зависимости могут потребоваться для функционирования класса, но не интересны для этого конкретного теста. Таким образом, заводский метод позволяет вам предоставлять только те зависимости, которые интересны для теста, при устранении шума неиспользуемых зависимостей. Таким образом, заводский метод позволяет вам написать следующий тест:
public void Test() {
// Arrange
var logger = new ListLogger();
ClassUnderTest sut = CreateClassUnderTest(logger: logger);
// Act
sut.DoSomething();
// Arrange
Assert.IsTrue(logger.Count > 0);
}
Если вы заинтересованы в том, чтобы узнать, как писать тесты, читаемые, заслуживающие доверия и поддерживаемые ( RTM ), я советую вы должны прочитать книгу Роя Ошерове «Искусство модульного тестирования» (второе издание). Это очень помогло мне.
Для истинных модульных тестов (т. е. те, которые проверяют только один класс и издеваются над всеми его зависимостями), нет смысла использовать структуру DI. В этих тестах:
new
, используя экземпляр вашего класса со всеми созданными вами mocks, одна полезная стратегия - создайте все свои mocks и создайте экземпляр для субъекта-теста в вашем методе установки (все это могут быть частные поля экземпляра), а затем область «организовать» каждого отдельного теста просто должна вызвать соответствующий код Setup()
на методы, которые он должен издеваться. Таким образом, вы получаете только один оператор new PersonController(...)
для каждого тестового класса. var person = new PersonBuilder().Build()
, возможно, с помощью нескольких цепочек вызовов для фрагментов данных, которые вам особенно интересны в этот тест. Вы также можете быть заинтересованы в AutoFixture , но я никогда не использовал его, поэтому я не могу ручаться за него. Если вы пишете Integration , где вам нужно протестировать взаимодействие между несколькими частями системы, но вы все равно должны иметь возможность издеваться над определенными частями, подумайте о создании классов Builder для своих служб, чтобы вы могли сказать, например var personController = new PersonControllerBuilder.WithRealDatabase(connection).WithAuthorization(new AllowAllAuthorizationService()).Build()
.
Если вы пишете сквозные или «сценарийные» тесты, где вам нужно протестировать всю систему, тогда имеет смысл настроить вашу инфраструктуру DI, используя тот же код конфигурации, который использует ваш реальный продукт. Вы можете немного изменить конфигурацию, чтобы обеспечить себе лучший программный контроль над такими вещами, как пользователь, который входит в систему и т. Д. Вы также можете использовать другие классы строителей, которые вы создали для создания данных.
var user = new PersonBuilder().Build();
using(Login.As(user))
{
var controller = Container.Get<PersonController>();
var result = controller.GetCurrentUser();
Assert.AreEqual(result.Username, user.Username)
}