简体   繁体   English

单元测试在“全部运行”时失败,但在单次运行时通过

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

I have this test class and when I run these tests one at a time they pass but when I try to run all tests from this class, first test is always passed and others usually fail (sometimes randomly one of them pass) and when I run all tests from my project all of these tests fail. 我有这个测试类,当我一次运行一次这些测试时,但是当我尝试运行该类中的所有测试时,总是会通过第一个测试,而其他测试通常会失败(有时会其中一个通过),而当我运行时我项目中的所有测试全部失败。

I use NUnit and Moq frameworks. 我使用NUnit和Moq框架。

Here is the code: 这是代码:

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

Do you know what can be the reason? 你知道是什么原因吗? As you can see I moved all repeatable code to these methods to avoid dependencies and it doesnt work. 如您所见,我将所有可重复的代码移到了这些方法中以避免依赖,并且它不起作用。

Since, as you mentioned, each test is creating it's own data and there aren't any shared objects, there a few likely reasons causing these failures: 正如您提到的那样,由于每个测试都在创建自己的数据,并且没有任何共享对象,因此有一些可能的原因导致这些失败:

Option A 选项A

Your system under test (ie LoginPageViewModel) is reusing its dependencies in a thread-safe way. 被测系统(即LoginPageViewModel)正在以线程安全的方式重用其依赖项。

If that is the case , then LoginPageViewModel is being instantiated with the mock interfaces created on the first test being run on a given thread. 如果是这种情况 ,那么将使用在给定线程上运行的第一个测试中创建的模拟接口实例化LoginPageViewModel。 It would then re-use those dependencies for any other test run on that thread. 然后,它将在那些线程上进行的任何其他测试中重新使用这些依赖项。 This would result in the first test on that thread to pass (so when you run each test individually, they all pass). 这将导致该线程上的第一个测试通过(因此,当您单独运行每个测试时,它们都将通过)。 However, any subsequent tests run on that thread will fail because even though your passing in new mock interfaces for each test, the mock interfaces from the first test on that thread are being re-used. 但是,在该线程上运行的任何后续测试都将失败,因为即使您为每个测试传递了新的模拟接口,该线程上第一个测试的模拟接口也将被重用。

Solutions to this problem would be to: 解决此问题的方法是:

  1. Remove any locking or Singelton implementations in your code and instead use an IoC container to define the lifestyle of your objects in your application. 删除代码中的所有锁定或Singelton实现,而使用IoC容器定义应用程序中对象的使用期限。 Then you can be in control of the lifestyle of the objects being used in your tests. 然后,您可以控制测试中使用的对象的生活方式。
  2. Use the RequiresThread attribute in NUnit to ensure that each test is run on it's own thread, removing the instance re-use. 使用NUnit中的RequiresThread属性来确保每个测试都在其自己的线程上运行,从而消除了实例的重用。

Option B (this seems to be the case after looking further at your code) 选项B (在进一步查看您的代码后,似乎是这种情况)

Your system under test is calling some async code without awaiting the result. 您的被测系统正在调用一些异步代码,而无需等待结果。 This will cause the async code to continue to run while the control is returned to the caller (in this case, the unit test). 这将导致异步代码继续运行,同时将控件返回给调用者(在本例中为单元测试)。 This would then potentially cause race conditions if the assertions in the test are executed before the async code finishes. 如果在异步代码完成之前执行测试中的断言,则可能会导致争用情况。 And these race conditions would be more likely to occur when the code is executing slower due to some load, such as running multiple tests at once. 当由于某些负载(例如一次运行多个测试)而导致代码执行速度变慢时,更可能出现这些竞争条件。

This seems to be the case in the code that you're testing. 在您正在测试的代码中似乎就是这种情况。 The constructor for LoginPageViewModel constructs a RelayParameterizedCommand , passing in an async delegate. LoginPageViewModel的构造函数构造一个RelayParameterizedCommand ,传入一个异步委托。 However, your unit tests then call RelayParameterizedCommand to execute that delegate that was passed in, without awaiting the result. 但是,您的单元测试然后调用RelayParameterizedCommand来执行传入的委托,而无需等待结果。

The solutions for that would be: 解决方案是:

  1. Update the delegate passed into RelayParameterizedCommand from Action<object> to Func<object, Task> . 将传递给RelayParameterizedCommand的委托从Action<object>Func<object, Task> Then you can, either 那你可以
    1. Make the Execute method on RelayParameterizedCommand async. 使RelayParameterizedCommand的Execute方法异步。 Then make your unit tests aysnc and call the method in your system under test with an await. 然后使您的单元测试aysnc并等待您在被测系统中调用该方法。 Or, 要么,
    2. Keep the Execute method on RelayParameterizedCommand synchronous, but still get the result from the Task of the delegate: _action(parameter).GetAwaiter().GetResult(); 保持RelayParameterizedCommand上的Execute方法同步,但仍从委托的Task中获取结果: _action(parameter).GetAwaiter().GetResult();

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Flurl&HttpTest:单元测试在全部运行时失败,但在单独运行时通过 - Flurl & HttpTest: Unit tests fail when Run All, but pass when run individually 单元测试在一起运行时失败,单独传递 - Unit Tests fail when run together, pass individually 单元测试在Visual Studio中“全部运行”但单独传递时失败 - Unit Tests Fail when “Run All” in Visual Studio but passes individually 为什么使用HostType(“ Moles”)进行的单元测试中的断言在单独运行时可以通过,而在一组测试中运行时却失败? - Why would an assert on a unit test with HostType(“Moles”) pass when run individually, but fail when run with a group of tests? 运行所有测试时单元测试失败,但在调试时通过 - Unit Tests failing when I Run All Tests but pass when I Debug 我正在尝试在Visual Studio中运行所有测试,但是当我运行测试时,第一个测试将通过,但所有其他测试将失败 - I am trying to run all tests in visual studio, but when I run the the tests the first one will pass, but all the others will fail 为什么我的测试一起运行时失败,但单独通过? - Why do my tests fail when run together, but pass individually? 如果一次运行一个单元测试则传递正常,如果运行“解决方案中的所有测试”,则为FileLoadException - Unit Tests pass fine if run one at a time, FileLoadException if run “All Tests in Solution” 一起运行时单元测试超时,单独运行时成功吗? - Unit Tests timeout when run together, succeed when run individually? 隐式运行单元测试 - Implicitly run Unit Tests
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM