Модульные тесты не выполняются в режиме Run All, но проходят, когда они запускаются один раз - PullRequest
0 голосов
/ 30 декабря 2018

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

Я использую платформы NUnit и Moq.

Вот код:

using System.Security;
using DebtDiary.Core;
using DebtDiary.DataProvider;
using Moq;
using NUnit.Framework;

namespace DebtDiary.Tests.ViewModels
{
    [TestFixture]
    public class LoginPageViewModelTests
    {
        [Test]
        public void TestLoginCommandCallsLoginUserInClientDataStoreWhenDataIsValid()
        {
            Mock<IApplicationViewModel> stubApplicationVM = new Mock<IApplicationViewModel>();
            Mock<IDiaryPageViewModel> stubDiaryPageVM = new Mock<IDiaryPageViewModel>();
            Mock<IDialogFacade> stubDialogFacadeVM = new Mock<IDialogFacade>();
            Mock<IClientDataStore> mockClientDataStore = new Mock<IClientDataStore>();
            Mock<IDataAccess> stubDataAccess = new Mock<IDataAccess>();
            var loginPageVM = new LoginPageViewModel(stubApplicationVM.Object, stubDiaryPageVM.Object, stubDialogFacadeVM.Object, mockClientDataStore.Object, stubDataAccess.Object);
            loginPageVM.Username = "test";
            Mock<IHavePassword> stubPassword = new Mock<IHavePassword>();
            SecureString ss = new SecureString();
            ss.AppendChar('t');
            stubPassword.Setup(x => x.Password).Returns(ss);
            User user = new User();
            stubDataAccess.Setup(x => x.UserExist(It.IsAny<string>(), It.IsAny<string>())).Returns(true);
            stubDataAccess.Setup(x => x.TryGetUser(It.IsAny<string>(), It.IsAny<string>(), out user)).Returns(true);

            loginPageVM.LoginCommand.Execute(stubPassword.Object);

            mockClientDataStore.Verify(x => x.LoginUser(It.IsAny<User>()), Times.Once());
        }

        [Test]
        public void TestLoginCommandUpdatesDebtorsListInDiaryPageViewModelWhenDataIsValid()
        {
            Mock<IApplicationViewModel> stubApplicationVM = new Mock<IApplicationViewModel>();
            Mock<IDiaryPageViewModel> mockDiaryPageVM = new Mock<IDiaryPageViewModel>();
            Mock<IDialogFacade> stubDialogFacadeVM = new Mock<IDialogFacade>();
            Mock<IClientDataStore> stubClientDataStore = new Mock<IClientDataStore>();
            Mock<IDataAccess> stubDataAccess = new Mock<IDataAccess>();
            var loginPageVM = new LoginPageViewModel(stubApplicationVM.Object, mockDiaryPageVM.Object, stubDialogFacadeVM.Object, stubClientDataStore.Object, stubDataAccess.Object);
            loginPageVM.Username = "test";
            Mock<IHavePassword> stubPassword = new Mock<IHavePassword>();
            SecureString ss = new SecureString();
            ss.AppendChar('t');
            stubPassword.Setup(x => x.Password).Returns(ss);
            User user = new User();
            stubDataAccess.Setup(x => x.UserExist(It.IsAny<string>(), It.IsAny<string>())).Returns(true);
            stubDataAccess.Setup(x => x.TryGetUser(It.IsAny<string>(), It.IsAny<string>(), out user)).Returns(true);

            loginPageVM.LoginCommand.Execute(stubPassword.Object);

            mockDiaryPageVM.Verify(x => x.UpdateDebtorsList(), Times.Once());
        }

        [Test]
        public void TestLoginCommandUpdatesUsersDataInDiaryPageViewModelWhenDataIsValid()
        {
            Mock<IApplicationViewModel> stubApplicationVM = new Mock<IApplicationViewModel>();
            Mock<IDiaryPageViewModel> mockDiaryPageVM = new Mock<IDiaryPageViewModel>();
            Mock<IDialogFacade> stubDialogFacadeVM = new Mock<IDialogFacade>();
            Mock<IClientDataStore> stubClientDataStore = new Mock<IClientDataStore>();
            Mock<IDataAccess> stubDataAccess = new Mock<IDataAccess>();
            var loginPageVM = new LoginPageViewModel(stubApplicationVM.Object, mockDiaryPageVM.Object, stubDialogFacadeVM.Object, stubClientDataStore.Object, stubDataAccess.Object);
            loginPageVM.Username = "test";
            Mock<IHavePassword> stubPassword = new Mock<IHavePassword>();
            SecureString ss = new SecureString();
            ss.AppendChar('t');
            stubPassword.Setup(x => x.Password).Returns(ss);
            User user = new User();
            stubDataAccess.Setup(x => x.UserExist(It.IsAny<string>(), It.IsAny<string>())).Returns(true);
            stubDataAccess.Setup(x => x.TryGetUser(It.IsAny<string>(), It.IsAny<string>(), out user)).Returns(true);

            loginPageVM.LoginCommand.Execute(stubPassword.Object);

            mockDiaryPageVM.Verify(x => x.UpdateUsersData(), Times.Once());
        }

        [Test]
        public void TestLoginCommandResetsCurrentSubpageInApplicationViewModelWhenDataIsValid()
        {
            Mock<IApplicationViewModel> mockApplicationVM = new Mock<IApplicationViewModel>();
            Mock<IDiaryPageViewModel> stubDiaryPageVM = new Mock<IDiaryPageViewModel>();
            Mock<IDialogFacade> stubDialogFacadeVM = new Mock<IDialogFacade>();
            Mock<IClientDataStore> stubClientDataStore = new Mock<IClientDataStore>();
            Mock<IDataAccess> stubDataAccess = new Mock<IDataAccess>();
            var loginPageVM = new LoginPageViewModel(mockApplicationVM.Object, stubDiaryPageVM.Object, stubDialogFacadeVM.Object, stubClientDataStore.Object, stubDataAccess.Object);
            loginPageVM.Username = "test";
            Mock<IHavePassword> stubPassword = new Mock<IHavePassword>();
            SecureString ss = new SecureString();
            ss.AppendChar('t');
            stubPassword.Setup(x => x.Password).Returns(ss);
            User user = new User();
            stubDataAccess.Setup(x => x.UserExist(It.IsAny<string>(), It.IsAny<string>())).Returns(true);
            stubDataAccess.Setup(x => x.TryGetUser(It.IsAny<string>(), It.IsAny<string>(), out user)).Returns(true);

            loginPageVM.LoginCommand.Execute(stubPassword.Object);

            mockApplicationVM.Verify(x => x.ResetCurrentSubpage(), Times.Once());
        }
    }
}

Знаете ли выв чем может быть причина?Как видите, я переместил весь повторяемый код в эти методы, чтобы избежать зависимостей, и он не работает.

1 Ответ

0 голосов
/ 31 декабря 2018

Поскольку, как вы упомянули, каждый тест создает свои собственные данные и не имеет общих объектов, существует несколько вероятных причин, вызывающих эти сбои:

Опция A

Тестируемая система (т. Е. LoginPageViewModel) повторно использует свои зависимости потокобезопасным способом.

Если это так , то LoginPageViewModel создается с помощью фиктивных интерфейсовсоздается в первом тесте, запущенном в заданном потоке.Затем он будет повторно использовать эти зависимости для любого другого запуска теста в этом потоке.Это приведет к тому, что первый тест в этом потоке будет пройден (поэтому, когда вы запускаете каждый тест по отдельности, они все проходят).Однако любые последующие тесты, запущенные в этом потоке, завершатся неудачно, потому что, несмотря на то, что вы передаете новые фиктивные интерфейсы для каждого теста, фиктивные интерфейсы из первого теста в этом потоке используются повторно.было бы:

  1. Удалите все блокировки или реализации Singelton в вашем коде и вместо этого используйте контейнер IoC, чтобы определить образ жизни ваших объектов в вашем приложении.Затем вы можете контролировать образ жизни объектов, используемых в ваших тестах.
  2. Используйте атрибут requireThread в NUnit, чтобы каждый тест выполнялся в собственном потоке, удаляяповторное использование экземпляра.

Опция B (это похоже на случай после дальнейшего изучения вашего кода)

Тестируемая система вызывает некий асинхронный вызовкод, не дожидаясь результата.Это заставит асинхронный код продолжать работать, пока элемент управления возвращается вызывающей стороне (в данном случае, модульному тестированию).Это может потенциально привести к состязанию, если утверждения в тесте будут выполнены до завершения асинхронного кода.И эти условия гонки, скорее всего, возникнут, когда код будет выполняться медленнее из-за некоторой нагрузки, такой как одновременное выполнение нескольких тестов.

Это, похоже, имеет место в коде, который вы тестируете.Конструктор для LoginPageViewModel создает RelayParameterizedCommand , передавая асинхронный делегат.Однако ваши модульные тесты затем вызывают RelayParameterizedCommand для выполнения того делегата, который был передан, без ожидания результата.

Решения для этого будут следующие:

  1. Обновите делегат, переданный в RelayParameterizedCommand, с Action<object> до Func<object, Task>.Затем вы можете либо
    1. Сделать метод Execute в асинхронном RelayParameterizedCommand.Затем сделайте ваши юнит-тесты aysnc и срочно вызовите метод в тестируемой системе.Или
    2. Сохраняйте метод Execute в RelayParameterizedCommand синхронным, но все же получите результат из Задачи делегата: _action(parameter).GetAwaiter().GetResult();
...