Постановка
Рассмотрим небольшой класс Student
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public bool HasLicense { get; set; }
}
и класс Security, занимающийся проверкой лицензий
public class Security
{
public bool CheckLicense(Student student)
{
return student.HasLicense;
}
}
Нам необходимо протестировать функциональность метода CheckLicense.
Стандартный тест
Основываясь на опыте предыдущей статьи, мы могли бы написать что-то в духе
[Fact]
public void CheckLicense_Success()
{
var student = FakeFactory.Fixture.Create<Student>();
//
var result = security.CheckLicense(student);
//
Assert.True(result == student.HasLicense);
}
Выглядит неплохо, но что если усложнить ситуацию и включить в схему некий сервис, выдающий статус студента в соответствии с датой запроса?
public interface IStudentLicenseStore
{
bool GetLicense(string name, DateTime date);
}
public class Security
{
private readonly IStudentLicenseStore _store;
public Security(IStudentLicenseStore store)
{
_store = store;
}
public bool CheckLicense(Student student, DateTime date)
{
return _store.GetLicense(student.Name, date);
}
}
Конечно, bool здесь выглядит странно, но для наших целей сойдет и такая упрощенная модель. Как же изменится наш тест?
[Fact]
public void CheckLicense_Success()
{
var student = FakeFactory.Fixture.Create<Student>();
var serviceResult = FakeFactory.Fixture.Create<bool>();
var date = FakeFactory.Fixture.Create<DateTime>();
_store.Setup(s => s.GetLicense(student.Name, date)).Returns(serviceResult);
//
var result = security.CheckLicense(student, date);
//
Assert.True(result == serviceResult);
}
Вводных данных все больше и тест становится громоздким, хотя, в действительности, строчки с объявлением переменных не несут никакой смысловой нагрузки. Можно ли упростить этот код?
AutoMoqDataAttribute
На выручку нам могли бы придти атрибуты Theory и AutoData, они сами по себе могут обеспечить подделку всех нужных нам данных, однако в этом случае мы теряем возможность использовать класс FakeFactory
и настраивать подделки по своему усмотрению. Учитывая то, что эти настройки нам очень пригодятся в будущих статьях, отказываться от них явно не стоит)
Создадим в тестовом проекте класс AutoMoqDataAttribute
public class AutoMoqDataAttribute : AutoDataAttribute
{
public AutoMoqDataAttribute() : base(FakeFactory.Fixture)
{
}
}
Как видно, этот класс является легковесной оберткой над AutoDataAttribute и единственная его задача - передать наши настройки подделок, что очень пригодится нам в будущем.
Изменим наш тест
[Theory, AutoMoqData]
public void CheckLicense_Success(Student student, bool serviceResult, DateTime date)
{
_store.Setup(s => s.GetLicense(student.Name, date)).Returns(serviceResult);
//
var result = security.CheckLicense(student, date);
//
Assert.True(result == serviceResult);
}
Тест выглядит гораздо более опрятно и не перегружен несущественными данными, победа?
InlineAutoDataAttribute
При написании тестов иногда возникают ситуации, когда автоматически сформированные подделки не соответствуют требованиям приложения, так у Student
может быть поле Phone
, которое является значимым для тестируемого метода. В обычной ситуации мы могли бы использовать InlineData
, но AutoMoqDataAttribute
блокирует использование InlineData
и не позволит использовать указанные значения.
Для того, чтобы решить эту проблему добавим еще один класс поддержки.
public class InlineAutoMoqDataAttribute : InlineAutoDataAttribute
{
public InlineAutoMoqDataAttribute(params object[] objects) : base(new AutoMoqDataAttribute(), objects)
{
}
}
И снова обертка над базовым классом AutoFixture
, для нас важны два момента.
- Передача
AutoMoqDataAttribute
позволит использовать искомый экземплярFakeFactory
. - Передача параметров
InlineData
. При таком подходе будет выполнен проход по всем входящим параметрам теста; нулевой параметр будет взят из нулевого индексаobjects
, первый из первого и т.д. Еслиobjects
закончится, а параметры все еще требуются, вступит в силуAutoMoqDataAttribute
и будет формировать рандомные подделки.
В итоге наш тест будет выглядеть как-то так:
[Theory]
[InlineAutoMoqData("123456789")]
public void CheckLicense_Success(string phone, Student student, bool serviceResult, DateTime date)
{
_store.Setup(s => s.GetLicense(student.Name, date)).Returns(serviceResult);
//
var result = security.CheckLicense(student, date);
//
Assert.True(result == serviceResult);
}
Обратите внимание, что AutoMoqDataAttribute
в данном случае не используется напрямую.
Вывод
Использование двух описанных классов должно, по моему мнению, войти в стандарт организации тестирующего кода, способствуя улучшению читаемости и упрощению написания тестов.