• 从 GitHub 下载示例

介绍

Atata Framework ——基于Selenium WebDriver的C#/.NET Web测试自动化全功能框架。使用流畅的页面对象模式;拥有独特的日志系统;包含触发器功能;有一套随时可用的组件。支持.NET Framework 4.0+和.NET Core/Standard 2.0+。

该框架主要由以下概念组成:

  • 组件(控件和页面对象)
  • 控件搜索的属性
  • 设置属性
  • 触发器
  • 验证属性和方法

特征

  • 网络驱动程序。基于Selenium WebDriver并保留其所有功能。
  • 页面对象模型。提供独特的流畅页面对象模式,易于实现和维护。
  • 组件。包含一组丰富的即用型组件,用于输入、表格、列表等。
  • 整合。适用于任何.NET测试引擎(例如NUnit、xUnit、SpecFlow)以及CI系统,如 Jenkins、Azure DevOps或TeamCity。
  • 触发器。一堆触发器与不同的事件绑定以扩展组件行为。
  • 验证。一套用于组件和数据验证的流畅断言方法和触发器。
  • 可配置。定义默认组件搜索策略以及其他设置。Atata.Configuration.Json提供灵活的JSON配置。
  • 报告/日记。内置可定制的日志记录和屏幕截图捕获功能。
  • 可扩展。Atata.Bootstrap和Atata.KendoUI包有一组随时可用的组件。框架支持任何类型的扩展。

背景

Atata框架的一个想法是使用Selenium WebDriver和C#/.NET为任何类型的网站创建复杂、可扩展和可定制的Web测试自动化框架。

参考

与框架相关的链接列表:

  • 文档
  • GitHub - Atata
  • GitHub - Atata 示例应用程序测试
  • Atata 模板Visual Studio 扩展
  • NuGet - Atata

要从NuGet包管理器控制台安装它,请运行Install-Package Atata

用法

我想使用演示网站展示框架的用法。这是一个简单的网站,包含以下内容:登录页面、用户页面、用户详细信息页面和用户编辑窗口。

测试项目将使用NuGet包:Atata、Atata.Bootstrap、Atata.WebDriverSetup、Selenium.WebDriver、NUnit和NUnit3TestAdapter。

我使用NUnit但它不是必需的,您可以使用任何.NET测试框架,如MSTest或xUnit。但对我来说,NUnit最合适。

让我们尝试为以下测试用例实现自动测试:

  1. 在https://demo.atata.io/signin页面上登录。
  2. 单击用户列表页面上的“新建”按钮。
  3. 创建一个新用户。
  4. 验证新用户是否出现在用户列表页面上。
  5. 导航到用户的详细信息。
  6. 验证用户的详细信息。

任何页面都可以用页面对象表示。我将尝试逐步解释Atata的东西。首先,我们需要为登录页面实现页面对象类。

登录页面

using Atata;namespace SampleApp.UITests
{using _ = SignInPage;[Url("signin")][VerifyTitle][VerifyH1]public class SignInPage : Page<_>{public TextInput<_> Email { get; private set; }public PasswordInput<_> Password { get; private set; }public Button<UsersPage, _> SignIn { get; private set; }}
}

SignInPage.cs

在Atata中,您使用控件而不是IWebElement进行操作。页面对象由控件组成。任何像TextInput包装的控件IWebElement都具有自己的一组方法和属性,用于与其交互。在文档中了解有关组件的更多信息。

请注意上面代码的第5行:

using _ = SignInPage;

它是为了简化控件声明的类类型的使用,因为每个控件都必须知道其所有者页面对象(指定单个或最后一个通用参数)。这只是一个语法糖,当然,您可以通过以下方式声明控件:

public TextInput<SignInPage> Email { get; private set; }

如您所见,SignIn按钮定义了2个通用参数:第一个是单击按钮后要导航到的页面对象的类型;另一种是所有者类型。对于不执行任何导航的按钮和链接,只需传递单个通用参数,即所有者页面对象。

可以用属性标记属性以指定查找方法(例如FindById,FindByName)。在当前情况下,不需要它,因为默认搜索输入是FindByLabel和按钮是FindByContentOrValue,它适合我们的需求。在文档中了解有关控件搜索的更多信息。

还有一个[Url]属性指定此页面的相对(可​​以是绝对)URL。当您导航到此页面对象时可以使用它。

[VerifyTitle]和[VerifyH1]是在当前情况下在页面对象初始化时(导航到页面之后)执行的触发器。如果该string值未传递给这些属性,则它们使用不以“Page”结尾的类名作为“登录”。它可以完全配置。在文档中了解有关触发器的更多信息。

用户页面

“用户”页面包含具有CRUD操作的用户表。

using Atata;namespace SampleApp.UITests
{using _ = UsersPage;[VerifyTitle][VerifyH1]public class UsersPage : Page<_>{public Button<UserEditWindow, _> New { get; private set; }public Table<UserTableRow, _> Users { get; private set; }public class UserTableRow : TableRow<_>{public Text<_> FirstName { get; private set; }public Text<_> LastName { get; private set; }public Text<_> Email { get; private set; }public Content<Office, _> Office { get; private set; }public Link<UserDetailsPage, _> View { get; private set; }public Button<UserEditWindow, _> Edit { get; private set; }[CloseConfirmBox]public Button<_> Delete { get; private set; }}}
}

UsersPage.cs

在UsersPage类上,你可以看到Table<TRow, TOwner>和TableRow<TOwner>控件的用法。在UserTableRow类中,类型Text和Content默认情况下的属性由列标题(FindByColumnHeader属性)搜索。也可以配置。例如,FirstName控件将包含第一行的“John”值。该表的用法将在下面的测试方法中显示。

Delete按钮标有CloseConfirmBox触发器,它接受单击按钮后显示的确认窗口。

用户创建/编辑窗口

这是一个非常简单的Bootstrap弹出窗口,带有两个选项卡和常规输入控件。

using Atata;
using Atata.Bootstrap;namespace SampleApp.UITests
{using _ = UserEditWindow;public class UserEditWindow : BSModal<_>{[FindById]public GeneralTabPane General { get; private set; }[FindById]public AdditionalTabPane Additional { get; private set; }[Term("Save", "Create")]public Button<UsersPage, _> Save { get; private set; }public class GeneralTabPane : BSTabPane<_>{public TextInput<_> FirstName { get; private set; }public TextInput<_> LastName { get; private set; }[RandomizeStringSettings("{0}@mail.com")]public TextInput<_> Email { get; private set; }public Select<Office?, _> Office { get; private set; }[FindByName]public RadioButtonList<Gender?, _> Gender { get; private set; }}public class AdditionalTabPane : BSTabPane<_>{public DateInput<_> Birthday { get; private set; }public TextArea<_> Notes { get; private set; }}}
}

UserEditWindow.cs

UserEditWindow从BSModal<TOwner>页面对象类继承。它是Atata.Bootstrap包的一个组件。

Save按钮标有指定控件搜索值的Term("Save", "Create")属性。这意味着按钮应该通过“Save”或“Cancel”内容找到。

Gender和Office控件使用以下enum:

namespace SampleApp.UITests
{public enum Gender{Male,Female}
}

Gender.cs

namespace SampleApp.UITests
{public enum Office{Berlin,London,NewYork,Paris,Rome,Tokio,Washington}
}

Office.cs

用户详情页面

using System;
using Atata;namespace SampleApp.UITests
{using _ = UserDetailsPage;public class UserDetailsPage : Page<_>{[FindFirst]public H1<_> Header { get; private set; }[FindByDescriptionTerm]public Text<_> Email { get; private set; }[FindByDescriptionTerm]public Content<Office, _> Office { get; private set; }[FindByDescriptionTerm]public Content<Gender, _> Gender { get; private set; }[FindByDescriptionTerm]public Content<DateTime?, _> Birthday { get; private set; }[FindByDescriptionTerm]public Text<_> Notes { get; private set; }}
}

UserDetailsPage.cs

Atata设置

配置Atata的最佳位置是在所有测试之前执行一次的全局设置方法。

using Atata;
using NUnit.Framework;namespace SampleApp.UITests
{[SetUpFixture]public class SetUpFixture{[OneTimeSetUp]public void GlobalSetUp(){AtataContext.GlobalConfiguration.UseChrome().WithArguments("start-maximized").UseBaseUrl("https://demo.atata.io/").UseCulture("en-US").UseAllNUnitFeatures().Attributes.Global.Add(new VerifyTitleSettingsAttribute { Format = "{0} - Atata Sample App" });AtataContext.GlobalConfiguration.AutoSetUpDriverToUse();}}
}

SetUpFixture.cs

在这里,我们使用以下内容全局配置Atata:

  1. 告诉使用Chrome浏览器。
  2. 设置基本站点URL。
  3. 设置文化,它由控件使用,如DateInput.
  4. 告诉使用所有Atata功能与NUnit集成,例如登录到NUnit TestContext,在测试失败时截取屏幕截图等。
  5. 设置页面标题的格式,因为测试网站上的所有页面都有一个页面标题,如“登录——Atata 示例应用程序”。
  6. AutoSetUpDriverToUse为我们要使用的浏览器设置驱动程序,在这种情况下是chromedriver.exe。Atata.WebDriverSetup包负责这些内容。

有关更多配置选项,请查看文档中的入门/设置页面。

基本UITestFixture

现在让我们配置NUnit以在测试设置事件上构建AtataContext(启动浏览器并进行额外配置)并在测试拆卸事件上清理Atata(关闭浏览器等)。我们可以创建基础测试工具类来做到这一点。我们也可以在那里放置可重用的Login方法。

using Atata;
using NUnit.Framework;namespace SampleApp.UITests
{[TestFixture]public class UITestFixture{[SetUp]public void SetUp(){AtataContext.Configure().Build();}[TearDown]public void TearDown(){AtataContext.Current?.CleanUp();}protected UsersPage Login(){return Go.To<SignInPage>().Email.Set("admin@mail.com").Password.Set("abc123").SignIn.ClickAndGo();}}
}

UITestFixture.cs

在这里您可以看到AtataContext Build和CleanUp方法的原始用法。

正如您在Login方法中看到的,导航从Go静态类开始。为了让示例保持简单,我在这里使用了硬编码凭据,例如,可以轻松地将其移动到App.configAtata.json

用户测试

最后,将使用上面创建的所有类和枚举的测试。

using Atata;
using NUnit.Framework;namespace SampleApp.UITests
{public class UserTests : UITestFixture{[Test]public void Create(){Office office = Office.NewYork;Gender gender = Gender.Male;Login() // Returns UsersPage..New.ClickAndGo() // Returns UserEditWindow..ModalTitle.Should.Equal("New User").General.FirstName.SetRandom(out string firstName).General.LastName.SetRandom(out string lastName).General.Email.SetRandom(out string email).General.Office.Set(office).General.Gender.Set(gender).Save.ClickAndGo() // Returns UsersPage..Users.Rows[x => x.Email == email].View.ClickAndGo() // Returns UserDetailsPage..AggregateAssert(page => page.Header.Should.Equal($"{firstName} {lastName}").Email.Should.Equal(email).Office.Should.Equal(office).Gender.Should.Equal(gender).Birthday.Should.Not.Exist().Notes.Should.Not.Exist());}}
}

UserTests.cs

我更喜欢在Atata测试中使用流畅的页面对象模式。如果您不喜欢这种方法,请使用无流畅模式。

您可以根据需要在测试中使用随机值或预定义值。

控制验证从Should属性开始。有一组的扩展方法用于不同的控制,如:Equal,Exist,StartWith,BeGreater,BeEnabled,HaveChecked,等等。

就这样。构建项目,运行测试并验证它是如何工作的。查看文档以了解有关Atata的更多信息。

日志记录

Atata可以生成到不同来源的日志。当我们使用UseAllNUnitFeatures配置AtataContext时,Atata会将日志写入NUnit上下文。您还可以使用NLog或log4net的目标将日志写入文件。

这是测试日志的一部分:

2021-03-02 12:50:42.4649  INFO Starting test: Create
2021-03-02 12:50:42.4917 TRACE > Set up AtataContext
2021-03-02 12:50:42.4937 TRACE - Set: BaseUrl=https://demo.atata.io/
2021-03-02 12:50:42.4977 TRACE - Set: ElementFindTimeout=5s; ElementFindRetryInterval=0.5s
2021-03-02 12:50:42.4980 TRACE - Set: WaitingTimeout=5s; WaitingRetryInterval=0.5s
2021-03-02 12:50:42.4982 TRACE - Set: VerificationTimeout=5s; VerificationRetryInterval=0.5s
2021-03-02 12:50:42.4986 TRACE - Set: Culture=en-US
2021-03-02 12:50:42.5067 TRACE - Set: DriverService=ChromeDriverService on port 64593
2021-03-02 12:50:43.4007 TRACE - Set: Driver=ChromeDriver (alias=chrome)
2021-03-02 12:50:43.4029 TRACE < Set up AtataContext (0.910s)
2021-03-02 12:50:43.4917  INFO Go to "Sign In" page
2021-03-02 12:50:43.5439  INFO Go to URL "https://demo.atata.io/signin"
2021-03-02 12:50:44.9231 TRACE > Execute trigger VerifyTitleAttribute { Case=Title, Match=Equals, Format="{0} - Atata Sample App" } on Init against "Sign In" page
2021-03-02 12:50:44.9370  INFO - > Assert: title should equal "Sign In - Atata Sample App"
2021-03-02 12:50:45.9745  INFO - < Assert: title should equal "Sign In - Atata Sample App" (1.037s)
2021-03-02 12:50:45.9752 TRACE < Execute trigger VerifyTitleAttribute { Case=Title, Match=Equals, Format="{0} - Atata Sample App" } on Init against "Sign In" page (1.052s)
2021-03-02 12:50:45.9773 TRACE > Execute trigger VerifyH1Attribute { Index=-1, Case=Title, Match=Equals } on Init against "Sign In" page
2021-03-02 12:50:45.9880  INFO - > Assert: "Sign In" <h1> heading should exist
2021-03-02 12:50:46.0225 TRACE - - > Find visible element by XPath ".//h1[normalize-space(.) = 'Sign In']" in ChromeDriver
2021-03-02 12:50:46.0754 TRACE - - < Find visible element by XPath ".//h1[normalize-space(.) = 'Sign In']" in ChromeDriver (0.051s) >> Element { Id=a694ecd2-0874-4ba3-b61f-e4e3eb821f0a }
2021-03-02 12:50:46.0756  INFO - < Assert: "Sign In" <h1> heading should exist (0.087s)
2021-03-02 12:50:46.0758 TRACE < Execute trigger VerifyH1Attribute { Index=-1, Case=Title, Match=Equals } on Init against "Sign In" page (0.098s)
2021-03-02 12:50:46.0842  INFO > Set "admin@mail.com" to "Email" text input
2021-03-02 12:50:46.0889 TRACE - > Execute behavior ValueSetUsingClearAndSendKeysAttribute against "Email" text input
2021-03-02 12:50:46.0968 TRACE - - > Find visible element by XPath ".//label[normalize-space(.) = 'Email']" in ChromeDriver
2021-03-02 12:50:46.1321 TRACE - - < Find visible element by XPath ".//label[normalize-space(.) = 'Email']" in ChromeDriver (0.035s) >> Element { Id=bc2450f6-27bb-497b-80aa-ff428b95d440 }
2021-03-02 12:50:46.1501 TRACE - - > Find visible element by XPath ".//*[normalize-space(@id) = 'email']/descendant-or-self::input[@type='text' or not(@type)]" in ChromeDriver
2021-03-02 12:50:46.1803 TRACE - - < Find visible element by XPath ".//*[normalize-space(@id) = 'email']/descendant-or-self::input[@type='text' or not(@type)]" in ChromeDriver (0.030s) >> Element { Id=3baa8d49-2ac4-4f69-900e-e6be31daaa14 }
2021-03-02 12:50:46.1815 TRACE - - > Clear element { Id=3baa8d49-2ac4-4f69-900e-e6be31daaa14 }
2021-03-02 12:50:46.2280 TRACE - - < Clear element { Id=3baa8d49-2ac4-4f69-900e-e6be31daaa14 } (0.046s)
2021-03-02 12:50:46.2291 TRACE - - > Send keys "admin@mail.com" to element { Id=3baa8d49-2ac4-4f69-900e-e6be31daaa14 }
2021-03-02 12:50:46.3052 TRACE - - < Send keys "admin@mail.com" to element { Id=3baa8d49-2ac4-4f69-900e-e6be31daaa14 } (0.076s)
2021-03-02 12:50:46.3055 TRACE - < Execute behavior ValueSetUsingClearAndSendKeysAttribute against "Email" text input (0.216s)
2021-03-02 12:50:46.3057  INFO < Set "admin@mail.com" to "Email" text input (0.221s)
2021-03-02 12:50:46.3059  INFO > Set "abc123" to "Password" password input
2021-03-02 12:50:46.3061 TRACE - > Execute behavior ValueSetUsingClearAndSendKeysAttribute against "Password" password input
2021-03-02 12:50:46.3066 TRACE - - > Find visible element by XPath ".//label[normalize-space(.) = 'Password']" in ChromeDriver
2021-03-02 12:50:46.3378 TRACE - - < Find visible element by XPath ".//label[normalize-space(.) = 'Password']" in ChromeDriver (0.031s) >> Element { Id=461e982a-c6c4-414f-ac9f-c7c7bd16baeb }
2021-03-02 12:50:46.3476 TRACE - - > Find visible element by XPath ".//*[normalize-space(@id) = 'password']/descendant-or-self::input[@type='password']" in ChromeDriver
2021-03-02 12:50:46.3756 TRACE - - < Find visible element by XPath ".//*[normalize-space(@id) = 'password']/descendant-or-self::input[@type='password']" in ChromeDriver (0.027s) >> Element { Id=a92d523a-a4c9-4ab6-9455-477cef964b0d }
2021-03-02 12:50:46.3759 TRACE - - > Clear element { Id=a92d523a-a4c9-4ab6-9455-477cef964b0d }
2021-03-02 12:50:46.4203 TRACE - - < Clear element { Id=a92d523a-a4c9-4ab6-9455-477cef964b0d } (0.044s)
2021-03-02 12:50:46.4205 TRACE - - > Send keys "abc123" to element { Id=a92d523a-a4c9-4ab6-9455-477cef964b0d }
2021-03-02 12:50:46.4810 TRACE - - < Send keys "abc123" to element { Id=a92d523a-a4c9-4ab6-9455-477cef964b0d } (0.060s)
2021-03-02 12:50:46.4813 TRACE - < Execute behavior ValueSetUsingClearAndSendKeysAttribute against "Password" password input (0.175s)
2021-03-02 12:50:46.4815  INFO < Set "abc123" to "Password" password input (0.175s)
2021-03-02 12:50:46.4837  INFO > Click "Sign In" button
2021-03-02 12:50:46.4862 TRACE - > Execute behavior ClickUsingClickMethodAttribute against "Sign In" button
2021-03-02 12:50:46.4892 TRACE - - > Find visible element by XPath ".//*[self::input[@type='button' or @type='submit' or @type='reset'] or self::button][normalize-space(.) = 'Sign In' or normalize-space(@value) = 'Sign In']" in ChromeDriver
2021-03-02 12:50:46.5177 TRACE - - < Find visible element by XPath ".//*[self::input[@type='button' or @type='submit' or @type='reset'] or self::button][normalize-space(.) = 'Sign In' or normalize-space(@value) = 'Sign In']" in ChromeDriver (0.028s) >> Element { Id=0994387f-fd82-49f6-ab43-8b90c3aee738 }
2021-03-02 12:50:46.5186 TRACE - - > Click element { Id=0994387f-fd82-49f6-ab43-8b90c3aee738 }
2021-03-02 12:50:46.6419 TRACE - - < Click element { Id=0994387f-fd82-49f6-ab43-8b90c3aee738 } (0.123s)
2021-03-02 12:50:46.6421 TRACE - < Execute behavior ClickUsingClickMethodAttribute against "Sign In" button (0.155s)
2021-03-02 12:50:46.6423  INFO < Click "Sign In" button (0.158s)
2021-03-02 12:50:46.6544  INFO Go to "Users" page
...

下载

在Atata GitHub 页面上查看Atata框架的来源。

在GitHub上获取演示测试项目的来源:Atata Sample App Tests。演示项目包含:

  • 20多种不同的自动测试
  • 验证验证功能
  • 使用NLog记录功能
  • 屏幕截图
  • 从 GitHub 下载示例

联系

您可以使用atata标签提出有关 Stack Overflow 的问题或选择其他联系方式。欢迎任何反馈、问题和功能请求。

Atata教程

  • 页面验证
  • 验证消息的验证
  • 处理确认弹出窗口
  • 通过.runsettings文件进行多浏览器配置
  • 报告范围报告

https://www.codeproject.com/Articles/1158365/Atata-New-Test-Automation-Framework

Atata——C# Web测试自动化框架相关推荐

  1. 11个开源测试自动化框架,如何选?

    以下为作者观点: 如果你正在考虑建立你自己的测试自动化框架,请再想一想.在大多数情况下,你最好可以考虑一个或多个可用的开源选项. 这是因为,一般来说,框架是一套可以跨团队使用的最佳实践.假设.通用工具 ...

  2. Web UI自动化框架搭建

    本篇博文只从项目架构角度,提供一些建议供参考.不涉及具体代码编写.目前市场上主流的免费开源工具就是Selenium.大家可以根据自己项目技术栈,选择合适的语言+外加Unit Test框架,来构建自己的 ...

  3. 测试自动化框架的重要性– iSAFE的优势

    测试自动化并不像某些人想象的那样容易或经济. 仅应在必要和适用的情况下使用.而且,最重要的是,它不能替代手动测试.相反,它补充了手动测试. 自动化框架和正确的测试自动化工具对于增强测试自动化过程至关重 ...

  4. Web UI自动化框架大比拼

    引子 对于测试从业者来说,手工测试是一个绕不过去的坎.当年我校招毕业以测试工程师岗位进了一家互联网公司.入职第一天就被师父"拉去干活",至今印象深刻,是一个投顾管理平台(投资顾问管 ...

  5. 手把手教你从0到1搭建web ui自动化框架(python3+selenium3+pytest)

    -前期准备 -环境 -实战: 从0开始 前期准备 为更好的学习自动化框架搭建,你需要提前了解以下知识: python基础知识 pytest单元测试框架 PO模式 selenium使用 环境 本次我们自 ...

  6. 从0到1搭建测试自动化框架

    我们在做自动化测试过程中,需要对我们的自动化用例,以及代码进行管理,使其更加结构化,模块化.从而更方便扩展以及维护.好的测试框架,让自动化更加省时省力. 当我们的自动化用例和代码写得多的时候,需要用一 ...

  7. 嵌入式测试自动化框架搭建

    又在写自动化测试框架,还是总结一下吧,希望下次能用的上. 芯片,硬件开发和智能设备公司对嵌入式自动化测试框架框架要求越来越高.有些公司有目标和方向,会结合自己的特点搭建出适合自己的测试平台:有些公司只 ...

  8. WEB UI自动化框架设计

    1.框架简介 1.1 框架设计思想 本框架是由selenium+testng+java+maven编写的数据驱动框架,对于用例的开放设计性比较强,分离了页面元素.页面元素操作.用例和检查点,也就是说, ...

  9. 2020年你不可不知的自动化框架,可替代Selenuim的测试框架Top10

    Selenium是一种开源自动测试工具.它可以跨不同的浏览器和平台在Web应用程序上执行功能,回归,负载测试.Slenium是最好的工具之一,但确实有一些缺点. 业界有一些强大的工具可以替代Selen ...

最新文章

  1. 组合计数 ---- 2020 EC final B. Rectangle Flip 2(枚举+组合计数)
  2. Linux下 curl 代理设置注意事项--curl proxy
  3. MySql 数据库 - 重置数据库、重置初始密码方法,数据库初始化方法,长时间不用忘记密码暴力解决方法
  4. 物理光学 计算倏逝波/渐逝波在界面上存在的范围
  5. mysql最高安全级别双一_MySQL核心之双一原则
  6. day10_cookiesession学习笔记
  7. 借助桶排序思想完成的一道题
  8. Web服务器网管交流一下
  9. 微软小冰学会画画了,堪称复活近代画家,还能命题作画
  10. 谷歌开源集成学习工具AdaNet:2017年提出的算法终于实现了
  11. day10 多进程、多线程(一)
  12. informix安装教程以及创建一个实例(详细)(系列1,informix安装篇)
  13. 计算机软考高级论文怎么写,【干货】软考高级论文怎么写易得高分?
  14. hud android,HUD | F-Droid - Free and Open Source Android App Repository
  15. 谷歌SRE运维模式解读
  16. ONLYOFFICE 如何连接集成到 Wordpress 上
  17. Diablo3狗熊榜
  18. OpenCL中kernel的循环调用
  19. API文档打开显示'已取消到该网页的导航'的解决方法
  20. 金融初学者的感受:兴趣的魔力

热门文章

  1. python 输入密码不显示_Python开发实例:隐藏输入密码时屏幕回显
  2. php+go+to,让phpstrom支持codeigniter框架实现 (GO TO )转到定义的功能
  3. mvc 调用其他控制器方法session丢失_Java从入门到放弃;MVC 模式
  4. ajax后台返回数据中文乱码_ajax返回的数据是乱码????
  5. 滤波器设计软件_滤波器设计——电路仿真软件的滤波器参数提取(下)
  6. matlab的7.3版本是什么_MX Linux 19.3 发布,Debian和antiX Linux的混合版本
  7. mysql覆盖索引二次查找_mysql中关于覆盖索引的知识点总结
  8. idea切换视图快捷键_IDEA操作技巧:一些常用且实用的快捷键
  9. cuda linux 算力_华为AI再进化,CANN 3.0释放算力狂魔
  10. 设计师灵感交流社区|给你的作品一个舞台