为什么我的测试一起运行时失败,但单独运行时通过?

58

在Visual Studio中编写测试时,我会通过保存、构建然后在Nunit中运行测试(右键单击测试然后运行)来检查它是否正常工作。

测试成功了……接下来我继续……

现在我又编写了另一个测试并且像上面一样保存和测试也是可以成功的。但是,当将它们一起运行时,它们就不能正常工作了。

这是我的两个测试,当它们分别运行时可以工作,但是当它们一起运行时会失败:

using System;
using NUnit.Framework;
using OpenQA.Selenium.Support.UI;
using OpenQA.Selenium;

namespace Fixtures.Users.Page1
{
    [TestFixture]
    public class AdminNavigateToPage1 : SeleniumTestBase
    {
        [Test]
        public void AdminNavigateToPage1()
        {
            NavigateTo<LogonPage>().LogonAsCustomerAdministrator();
            NavigateTo<Page1>();
            var headerelement = Driver.FindElement(By.ClassName("header"));

            Assert.That(headerelement.Text, Is.EqualTo("Page Title"));
            Assert.That(Driver.Url, Is.EqualTo("http://localhost/Page Title"));
        }

        [Test]
        public void AdminNavigateToPage1ViaMenu()
        {
            NavigateTo<LogonPage>().LogonAsCustomerAdministrator();
            Driver.FindElement(By.Id("menuitem1")).Click();
            Driver.FindElement(By.Id("submenuitem4")).Click();
            var headerelement = Driver.FindElement(By.ClassName("header"));

            Assert.That(headerelement.Text, Is.EqualTo("Page Title"));
            Assert.That(Driver.Url, Is.EqualTo("http://localhost/Page Title"));
        }
    }
}

当第二个测试失败时,因为它们被一起运行时,

Nunit会呈现如下信息:

Sse.Bec.Web.Tests.Fixtures.ManageSitesAndUsers.ChangeOfPremises.AdminNavigateToChangeOfPremises.AdminNavigateToPageChangeOfPremisesViaMenu: OpenQA.Selenium.NoSuchElementException : 找不到元素

并且这一行被突出显示:

var headerelement = Driver.FindElement(By.ClassName("header"));

有人知道为什么我的代码一起运行时会失败,但是单独运行时却可以通过吗?

非常感谢任何回答!


4
你确定这些测试没有共享状态吗?测试之间会关闭并重新打开浏览器吗?当它们一起运行时,哪个测试会失败?两个测试都失败还是其中一个通过,另一个失败? - Daniel Mann
浏览器关闭,命令窗口也关闭……它们重新打开以处理接下来的测试…… 第一个测试通过了,然后所有后续测试通常都失败,并且总是因为上述相同的原因而失败。 - Graeme Secondwave
虽然不能解决你具体的问题,但是可以看看http://www.ncrunch.net/,它可以在后台运行你的测试。摆脱整个“保存、构建和运行测试”的麻烦。 - Brad Boyce
这种情况经常发生在我身上(虽然错误不同),尽管我的测试涉及数据库并且每个测试都在事务中运行。我会收到非线程安全的异常,因此当通过Visual Studio运行时,下一个测试必须在上一个测试完成之前开始(通过命令行运行似乎没有相同的问题)。 - PJUK
我在这里详细介绍了如何修复Python中缓存模块状态的问题:https://dev59.com/vVMI5IYBdhLWcg3w5vwN#71428106 - jlhasson
9个回答

18

当单元测试以某种方式使用共享资源/数据时,通常会出现这种情况。

  1. 如果您的系统正在被测试且具有静态字段/属性来计算您进行断言的输出,则也可能会发生此情况。
  2. 如果所测试的系统在使用(静态)依赖项,则可能会发生此情况。

2
我在 .net core 中使用 InMemory 数据库时遇到了这种情况 -- 只是不确定该如何解决它。 - IEnjoyEatingVegetables
1
为了避免这个问题,并确保测试从可读性的角度是自包含的,我通常不依赖于单元测试之间共享对象。我在每个单元测试中创建单独的对象。您可以为创建单独的对象编写单独的方法(以避免反复重写相同的代码)。 - user3613932
这对我来说就是这样了,我在测试类上有一个表示一些基本数据起始点的共享公共属性。我必须在每个测试中初始化这个对象,以消除错误。 - J King
我在pytest单元测试中激活了翻译,但没有意识到它在测试后没有被重置。稍后的测试失败了,因为它依赖于基础语言。在Django中,解决方案是使用with translation.override(...)上下文管理器。 - Stefan_EOX

3

你可以尝试以下两件事

  1. put the break point between the following two lines. And see which page are you in when the second line is hit

  2. Introduce a slight delay between these two lines via Thread.Sleep

    Driver.FindElement(By.Id("submenuitem4")).Click();
    var headerelement = Driver.FindElement(By.ClassName("header"));
    

尝试了你建议的thread.sleep函数。 在我添加它之前,当我运行测试时,总是第二个测试失败,但是使用sleep命令后,第二个测试总是通过,而第一个测试失败?奇怪! - Graeme Secondwave
这可能意味着你需要将Thread.sleep放在"header"元素查找器上面,以便在你的程序寻找该元素之前给浏览器足够的时间来加载页面。同样,在第一次测试中也要尝试这样做。 - chandmk
好的,我已经在两个测试中都加入了休眠函数,并且它们都通过了!奇怪,我之前尝试过,但从来没有将它们添加到两个测试中...不确定为什么现在它们通过了,但我仍然很高兴!感谢您的帮助。 - Graeme Secondwave
thread.sleep可能不是回答“为什么会发生这种情况”的答案。我使用selenium驱动程序进行基于CSS的菜单点击时的经验并不那么可预测。Thread.Sleep似乎是我能够可靠地处理这些点击的唯一方法。 - chandmk

3

如果以上答案都不适用于您,我通过在失败的测试中的断言之前添加 Thread.Sleep(1) 来解决了这个问题...

看起来测试同步在某处遗漏了... 请注意,我的测试不依赖于顺序,我没有任何静态成员或外部依赖。


+1 因为你让我意识到我的静态对象是错误和问题的原因。谢谢! - workabyte

2

不知道Selenium是如何工作的,我猜测是Driver造成的。由于Driver是一个静态类,所以这两个测试共享状态。一个共享状态的例子是Driver.Url。因为测试是并行运行的,所以有一个竞争条件来设置此对象的状态。

话虽如此,我没有为您提供解决方案 :)


2

你建议我在 TestFixtureTearDown 中清理哪些内容? - Graeme Secondwave
@GraemeSecondwave 很抱歉我不熟悉Selenium。但是我知道当我一起运行测试时它们会失败,当我将常见指令添加到设置中时它们就可以正常工作。例如,如果您在 TestFixtureSetup 中添加 NavigateTo<LogonPage>().LogonAsCustomerAdministrator(); 会发生什么呢? - default
如果 var headerelement... 这一行被突出显示,那么很可能是它之前的那一行抛出了异常。确保 submenuitem4 存在。你尝试过调试吗?(右键单击测试,选择“使用调试器进行测试”) - default

0

我认为你需要确保能够登录第二个测试,因为你已经登录可能会导致失败?

-> 将登录放在设置方法中或者(因为似乎你在两个测试中使用了同一个用户)甚至可以放到固定设置中 -> 如果需要,登出可以放在拆卸方法中

     [SetUp]
     public void LaunchTest()
     {
        NavigateTo<LogonPage>().LogonAsCustomerAdministrator();
     }

     [TearDown]
     public void StopTest()
     {
        // logoff
     }
     [Test]
     public void Test1()
     {...}
     [Test]
     public void Test2()
     {...}

如果DOM存在延迟,建议使用webdriver.wait与条件组合,而不是使用thread.sleep。睡眠可能在80%的情况下有效,在其他情况下则无效。等待轮询直到达到超时时间,这更可靠且易于阅读。以下是我通常处理此问题的示例:
    var webDriverWait = new WebDriverWait(webDriver, ..);
    webDriverWait.Until(d => d.FindElement(By.CssSelector(".."))
        .Displayed))

0

您确定在运行其中一个测试后该方法

NavigateTo<LogonPage>().LogonAsCustomerAdministrator();

是否将您带回了应该去的地方?似乎失败是由于不当的导航处理程序引起的(假设标题元素存在且在两个测试中都找到)。


观察驱动程序在我的测试中导航,它加载所有页面并导航到所需的页面。我正在寻找的页面标题显示出来了。当单独运行时,它不喜欢“Header元素”作为测试,因为测试通过了吗? - Graeme Secondwave

0

我知道这是一个非常老的问题,但今天我刚遇到它,而且没有答案解决我的特殊情况。

使用Selenium和NUnit进行前端自动化测试。

对于我的情况,在我的启动中,我使用了[OneTimeSetUp][OneTimeTearDown],试图更高效地运行。

然而,这会导致使用共享资源的问题,在我的情况下是驱动程序本身和我用来验证/获取元素的帮助程序。

也许是一个奇怪的边缘情况 - 但花了我几个小时才找出来。


0
我在C++中遇到了类似的问题。 结果发现我使用了未初始化的变量值,导致了未定义的行为,取决于未保留内存的内容。
我怀疑是某种内存问题,决定用valgrind运行单元测试。
Valgrind给出了以下形式的错误:
==1795== Conditional jump or move depends on uninitialised value(s)
事实上,我的代码确实使用了未初始化的值,这当然会导致不确定的行为,取决于运行代码时该内存位置中的垃圾内容。
我遇到的错误类型是这样的:
    class MyClass
    {
        public:
        MyClass();
        void InitStuff(const bool flagIn)
        
        private:
        bool flag;
    };
    
    void MyClass::InitStuff(const bool flagIn)
    {
        if (flag) // Bug! I'm using member variable flag, not flagIn
        {
            DoStuff();
        }
        flag = flagIn;
    }

所以,我建议检查你的代码是否使用了未初始化的值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接