簡體   English   中英

單元測試在“全部運行”時失敗,但在單次運行時通過

[英]Unit tests fail on Run All but pass when are run single

我有這個測試類,當我一次運行一次這些測試時,但是當我嘗試運行該類中的所有測試時,總是會通過第一個測試,而其他測試通常會失敗(有時會其中一個通過),而當我運行時我項目中的所有測試全部失敗。

我使用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());
        }
    }
}

你知道是什么原因嗎? 如您所見,我將所有可重復的代碼移到了這些方法中以避免依賴,並且它不起作用。

正如您提到的那樣,由於每個測試都在創建自己的數據,並且沒有任何共享對象,因此有一些可能的原因導致這些失敗:

選項A

被測系統(即LoginPageViewModel)正在以線程安全的方式重用其依賴項。

如果是這種情況 ,那么將使用在給定線程上運行的第一個測試中創建的模擬接口實例化LoginPageViewModel。 然后,它將在那些線程上進行的任何其他測試中重新使用這些依賴項。 這將導致該線程上的第一個測試通過(因此,當您單獨運行每個測試時,它們都將通過)。 但是,在該線程上運行的任何后續測試都將失敗,因為即使您為每個測試傳遞了新的模擬接口,該線程上第一個測試的模擬接口也將被重用。

解決此問題的方法是:

  1. 刪除代碼中的所有鎖定或Singelton實現,而使用IoC容器定義應用程序中對象的使用期限。 然后,您可以控制測試中使用的對象的生活方式。
  2. 使用NUnit中的RequiresThread屬性來確保每個測試都在其自己的線程上運行,從而消除了實例的重用。

選項B (在進一步查看您的代碼后,似乎是這種情況)

您的被測系統正在調用一些異步代碼,而無需等待結果。 這將導致異步代碼繼續運行,同時將控件返回給調用者(在本例中為單元測試)。 如果在異步代碼完成之前執行測試中的斷言,則可能會導致爭用情況。 當由於某些負載(例如一次運行多個測試)而導致代碼執行速度變慢時,更可能出現這些競爭條件。

在您正在測試的代碼中似乎就是這種情況。 LoginPageViewModel的構造函數構造一個RelayParameterizedCommand ,傳入一個異步委托。 但是,您的單元測試然后調用RelayParameterizedCommand來執行傳入的委托,而無需等待結果。

解決方案是:

  1. 將傳遞給RelayParameterizedCommand的委托從Action<object>Func<object, Task> 那你可以
    1. 使RelayParameterizedCommand的Execute方法異步。 然后使您的單元測試aysnc並等待您在被測系統中調用該方法。 要么,
    2. 保持RelayParameterizedCommand上的Execute方法同步,但仍從委托的Task中獲取結果: _action(parameter).GetAwaiter().GetResult();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM