Испытания асинхронного вызова метода из конструктора


У меня есть проект, где я хочу построить более сложный список задач - в основном личная система управления проектами. Я только начинаю с проекта, и я хотел бы некоторую обратную связь о том, мои методы испытания ОК, как я довольно новыми для ТДД. До сих пор, на главной странице, где перечислены ежедневных todo пунктов, у меня есть следующие модели представления:

public class MainVM : ViewModelBase
{
    private IEnumerable<ToDoItem> _toDoItems;
    private IRepository<ToDoItem> _toDoItemRepo;
    private bool _dataIsLoaded;

    public bool DataIsLoaded
    {
        get { return _dataIsLoaded; }
        set { Set(ref _dataIsLoaded, value, true); }
    }

    public IEnumerable<ToDoItem> ToDoItems
    {
        get { return _toDoItems; }
        set { Set(ref _toDoItems, value); }
    }

    public MainVM(IRepository<ToDoItem> toDoItemRepo)
    {
        _toDoItemRepo= toDoItemRepo;
        LoadData().ContinueWith(t => FinishedLoadingData(t));
    }

    private void FinishedLoadingData(Task loadTask)
    {
        switch (loadTask.Status)
        {
            case TaskStatus.RanToCompletion:
                DataIsLoaded = true;
                break;
            default:
                DataIsLoaded = false;
                break;
        }
    }

    public async Task LoadData()
    {
        if (!DataIsLoaded)
            ToDoItems= await _toDoItemRepo.GetAsync();
    }
}

Как вы можете видеть, я хочу, чтобы загрузить мои данные при создании ViewModel является.

Я использую Set способ от MVVMLight инструментарий для установки DataIsLoaded свойство - она устанавливает поле позади и вызывает событие PropertyChanged для меня.

У меня следующий тест создан для тест для успешного нагрузки:

    public void DataIsLoaded_is_true_if_loading_task_ran_to_completion()
    {
        AutoResetEvent testTrigger = new AutoResetEvent(false);

        TaskCompletionSource<IEnumerable<ToDoItem>> taskCompletion = new TaskCompletionSource<IEnumerable<ToDoItem>>();
        taskCompletion.SetResult(null);

        Mock<IRepository<ToDoItem>> repoMock = new Mock<IRepository<ToDoItem>>();
        repoMock.Setup(s => s.GetAsync()).Returns(taskCompletion.Task);

        MainVM vm = new MainVM(repoMock.Object);
        vm.PropertyChanged += (s, e) =>
        {
            if (e.PropertyName == nameof(MainVM.DataIsLoaded))
                testTrigger.Set();
        };

        testTrigger.WaitOne(5000);

        Assert.IsTrue(vm.DataIsLoaded);
    }

Теперь я вижу, что есть потенциал гонки здесь. В LoadData способ может завершить прежде, чем обработчик событий прилагается. В качестве альтернативы я мог бы использовать Set метод, который отправляет сообщение в MVVMLight сообщения обработчик в моей модели представления, и изменить мой метод для этого:

    [Test]
    public void DataIsLoaded_is_true_if_loading_task_ran_to_completion()
    {
        AutoResetEvent testTrigger = new AutoResetEvent(false);

        TaskCompletionSource<IEnumerable<ToDoItem>> taskCompletion = new TaskCompletionSource<IEnumerable<ToDoItem>>();
        taskCompletion.SetResult(null);

        Mock<IRepository<ToDoItem>> repoMock = new Mock<IRepository<ToDoItem>>();
        repoMock.Setup(s => s.GetAsync()).Returns(taskCompletion.Task);

        Messenger.Default.Register<PropertyChangedMessage<bool>>(
           this,
           message =>
           {
               if (message.PropertyName == nameof(MainVM.DataIsLoaded))
               {
                   testTrigger.Set();
               }
           });
        MainVM vm = new MainVM(repoMock.Object);

        testTrigger.WaitOne(5000);

        Assert.IsTrue(vm.DataIsLoaded);
    }

Теперь у меня нет состояния гонки, но я немного раздражен, что я транслирую сообщение о том, что я на самом деле не нужно для моего кода для работы. Я мог бы обернуть все это в препроцессор директивы, конечно, но это просто неуклюжим.

Есть ли лучший способ пойти об этом?



Комментарии
1 ответ

Пытаюсь загрузить асинхронно данные в конструкторе-плохая идея и на мой взгляд противоречит тому, что конструктор для.

Конструкторы не могут быть асинхронным, и асинхронной инициализации может рассматриваться как деталь реализации.

Я предлагаю отделять инициализации и загрузки данных из ...

public class MainVM : ViewModelBase {
private readonly IRepository<ToDoItem> repository;
private IEnumerable<ToDoItem> items;
private bool _dataIsLoaded;
private bool loading = false;

public bool DataIsLoaded {
get { return _dataIsLoaded; }
set { Set(ref _dataIsLoaded, value, true); }
}

public IEnumerable<ToDoItem> ToDoItems {
get { return items; }
set { Set(ref items, value); }
}

public MainVM(IRepository<ToDoItem> repository) {
this.repository = repository;
}

public async Task LoadData() {
if (loading) return;

if (!DataIsLoaded) {
try {
loading = true;
ToDoItems = await repository.GetAsync();
loading = false;
DataIsLoaded = true; // task Ran To Completion
} catch (Exception ex) {
loading = false;
//TODO: Log error
DataIsLoaded = false; // task did not complete
}
}
}
}

... и то, что является обязательным/использование к модели представления (т. е. представления, другая модель, тест и т. д.) загрузка данных после инициализации.

Это позволяет проводить тестирование гораздо проще и по существу

Например

[TestClass]
public class MainVmTests {
[TestMethod]
public async Task DataIsLoaded_is_true_if_loading_task_ran_to_completion() {
//Arrange
var repoMock = new Mock<IRepository<ToDoItem>>();
repoMock.Setup(_ => _.GetAsync()).Returns(Task.FromResult(Enumerable.Empty<ToDoItem>()));
var subject = new MainVM(repoMock.Object);

//Act
await subject.LoadData();

//Assert
Assert.IsTrue(subject.DataIsLoaded);
Assert.IsNotNull(subject.ToDoItems);
}

[TestMethod]
public async Task DataIsLoaded_is_false_if_loading_task_failed() {
//Arrange
var repoMock = new Mock<IRepository<ToDoItem>>();
repoMock.Setup(_ => _.GetAsync()).Throws(new AggregateException());
var subject = new MainVM(repoMock.Object);

//Act
await subject.LoadData();

//Assert
Assert.IsFalse(subject.DataIsLoaded);
Assert.IsNull(subject.ToDoItems);
}
}

3
ответ дан 3 марта 2018 в 11:03 Источник Поделиться