СУХОЕ с Rhino Mocks
Я ищу способы сделать следующее более кратким.
public class MyTests
{
IPresenter presenter;
[SetUp]
public void SetUp()
{
presenter = MockRepository.GenerateStub<IPresenter>();
}
...
}
В частности, повторное указание типа при создании макета представляется излишним. Например, я могу написать это так и использовать отражение, чтобы получить тип и автоматически создать заглушку:
public class MyTests
{
IPresenter presenter;
[SetUp]
public void SetUp()
{
Stub(x => x.presenter);
}
void Stub(Expression<Func<MyTests, object>> expression)
{
...
}
}
Это будет работать, но компилятор больше не сможет определить, назначен ли докладчик, и начнет выдавать предупреждения. Это также делает ReSharper очень несчастным.
Кто-нибудь может предложить лучший подход?
4 ответа
Это может быть спорным, но я предпочитаю удобочитаемость, а не DRY-ness* в модульных тестах.
Другими словами, методы настройки отсутствуют в моих модульных тестах. Они используются только для интеграционных тестов. Я считаю, что XUnit.NET также занимает эту позицию.
Поэтому, чтобы ответить на ваш вопрос, я бы не стал беспокоиться о том, чтобы настраивать докладчиков в каждом из ваших тестов, для которых он нужен. Некоторым тестам может не понадобиться фиктивный докладчик, поэтому его настройка перед запуском теста не требуется.
** Естественно, мои юнит-тесты, скажем, в среднем на десять строк, если это увеличивается или объем настройки теста (в соответствии с AAA - Arrange, Act Assert) велик, только тогда я уберу дублирование и создаю вспомогательные методы. Чтобы прояснить этот момент, для более чистых тестов вы можете создать базовый класс тестирования, который содержит вспомогательные методы и другой код настройки.*
Да не пользуюсь [Setup]
и переменные-члены вообще, вместо этого пишите объекты Fixture с методами создания.
Объект Fixture будет просто содержать соответствующий макет и другие части Fixture.
Я лично использую AutoFixture в качестве объекта Fixture и настроил его в качестве Auto-Mocking Container для загрузки, поэтому мне не нужно писать никакой фиктивный код, если мне не нужно явно определить какое-либо поведение.
Вот недавний примерный тестовый модуль:
[TestMethod]
public void DeleteProductWillDeleteProductFromRepository()
{
// Fixture setup
var fixture = new ServiceFixture();
var id = fixture.CreateAnonymous<int>();
var repMock = fixture.FreezeMoq<ProductRepository>();
var sut = fixture.CreateAnonymous<ProductManagementService>();
// Exercise system
sut.DeleteProduct(id);
// Verify outcome
repMock.Verify(r => r.DeleteProduct(id));
// Teardown
}
В этом случае, repMock
создан Moq, но я мог бы настроить его на использование Rhino Mocks.
У Майкла Фезерса есть выдающийся взгляд на это (см. Его презентацию http://www.ndc2010.no/index.aspx?id=361621). Создайте Builders и используйте это в тестах вместо всевозможных настроек.
Подобно:
//The Class to Test
public class ObjectY
{
public string DoThis(IObjectX objectX)
{
return objectX.id + objectX.name;
}
}
[Test]
//The test
public void CreaeteTestData()
{
//Almost prosa style creation of test data
var testData = new ObjectXBuilder().WithId(123).WithName("ABC").Build();
Assert.That(new ObjectY().DoThis(testData), Is.EqualTo("123ABC"));
}
//The Builder class - Provides easy creation testdata.
internal class ObjectXBuilder
{
private MockRepository _mockRepository;
private IObjectX _objectX;
public ObjectXBuilder()
{
_mockRepository = new MockRepository();
_objectX = _mockRepository.Stub<IObjectX>();
}
public ObjectXBuilder WithName(string name)
{
_objectX.name = name;
return this;
}
public ObjectXBuilder WithId(long id)
{
_objectX.id = id;
return this;
}
public IObjectX Build()
{
_mockRepository.ReplayAll();
return _objectX;
}
}
Общая проблема с C# в том, что он не выводит тип из результата метода (в отличие от Java), и это болезненно во многих ситуациях (просто приведу другой пример, где вы хотите реализовать метод десериализации). Лично мне не нравится использовать ключевое слово var, потому что я хочу точно знать, какой тип моих объектов, я бы предпочел пропустить имя типа в шаблоне.
В любом случае, если мои тесты хорошие и маленькие, я склонен инициализировать все макеты внутри каждого теста. Таким образом, вы можете посмотреть на каждый тест отдельно от других тестов и сразу увидеть, что там происходит. Если они станут длиннее, хотя я просто использую мерзкий способ, который вы вставили в начале вашего вопроса.