网站测试自动化系统—在测试代码中硬编码测试数据

本文涉及的产品
应用实时监控服务-应用监控,每月50GB免费额度
简介:

在前面的文章数据驱动测试里,讲到了将测试数据以及表现测试步骤的代码分开的技术。从测试的角度来看,固然希望能够覆盖的测试场景越多越好,但是在设计和编写自动化测试代码的时候,却又可以事先设计好一些固定的测试数据简化自动化测试代码的编写工作。

之所以要这样做(按照编程的术语讲是硬编码),是因为按照等价类划分,固定的测试数据一般都已经被其他测试用例覆盖了。请考虑下面这个例子,假设你要测试一个博客网站(例如博客园)的文章评论功能,例如测试禁用一篇文章的评论功能,或者是测试文章作者删除评论的功能。按照正常的流程,肯定是需要先编码发布一篇文章,然后再编码指定的评论功能测试用例。这样的流程有以下几个缺点:

1.         需要冗余的编码,因为每个评论测试用例的代码都要包含发布文章的步骤,在编程里面,我们都是极力推荐,什么只要代码在不同的地方重复两次,就要考虑是否将它封装成一个函数之类的理念。这种包含冗余编码的方式是我们在测试过程中极力要避免地,否则,程序员可能哪天心情很好,重构一下代码,破坏了一些网页的HTML结构但是从用户的角度来看又没有任何区别;这种代码重构,作为测试人员只能跟着程序员的代码重构,修改测试代码,那个时候,你当然会希望改的地方越少越好啦。

 

对于这个缺点,可能有人要说,在前面的文章“网站测试自动化系统—基于Selenium和VSTT”,创建博客的测试步骤不是已经被有效地封装成一个函数了吗,为什么还会说有冗余?这是因为在自动化测试过程中, 测试人员会定期(一些高规格的软件开发团队要求每天)将所以编写完毕的测试代码批量执行一遍,这就涉及到对于任何测试用例编码都非常重要的两个原则:

 

1)        一个测试用例可以独自执行成功,就是说如果是单独执行这一个测试用例的话,这个测试用例是可以执行成功的否则就是产品编码的失误(Bug)。举个例子,你正要编码测试一个管理博客文章的功能,这个功能通常来说都是登录用户才可以使用的。然而,也许你刚刚编码完毕一个登录方面的测试用例,而且用例执行完毕的时候,没有执行注销操作。这个时候你不能想当然地以为下一个测试用例一定就是你现在正在编码的文章管理的测试用例。

 

因为测试人员既保留有将多个测试用例任意排列执行的权力,也可以选择单独执行这一个测试用例比如程序员刚刚重构了文章管理功能的代码,为了节省测试时间,测试人员可能会选择只执行文章管理方面的测试用例。所以不要将自己的命运寄托在别人手里。即除了整个团队都公认的前提以外,不要相信任何前提。

 

2)        测试用例可以在任意排列的用例序列中执行通过,因此测试代码应该尽量保护测试环境。举个例子,你设计了一个管理用户权限的测试用例,一般来说这种功能只有管理员才有权限操作的。然而,也许另一个粗心大意的测试工程师编码了一个测试删除用户的用例,恰好将管理员删除了,而你的用例正好在他的用例之后执行……己所不欲,勿施于人,既然你不希望碰到这种情况,那么在编码自己的测试用例之前也应该避免类似的事情发生。

 

回过头来再举评论管理测试用例的设计,于是你的几个测试代码可能看起来像下面这样:

[TestMethod]

public void BlogCommentIsDisabled()

{

    TestLibrary.UserHelper.LogOnAsAdmin();

    var blog = TestLibrary.BlogHelper.CreateBlog("博客文章标题""文章内容");

    // 去管理文章的网页

    TestLibrary.BlogHelper.ManageArticles();

    // 在文章管理的网页的文章列表里依次查找标题为

    // "博客文章标题"的文章连接,

    var blogListItem = TestLibrary.BlogHelper.FindBlog(blog.Title);

    // 并且在网页上点击"浏览这个链接,打开阅读文章的网页

    blogListItem.View();

    // 评论这篇文章

    TestLibrary.BlogHelper.Comment(blog);

    // 然后执行一些验证判断评论功能的确被禁用掉了

    // ...

}

 

[TestMethod]

public void DeleteBlogComment()

{

    TestLibrary.UserHelper.LogOnAsAdmin();

    var blog = TestLibrary.BlogHelper.CreateBlog("博客文章标题""文章内容");

    // 去管理文章的网页

    TestLibrary.BlogHelper.ManageArticles();

    // 在文章管理的网页的文章列表里依次查找标题为

    // "博客文章标题"的文章连接,

    var blogListItem = TestLibrary.BlogHelper.FindBlog(blog.Title);

    // 并且在网页上点击"浏览这个链接,打开阅读文章的网页

    blogListItem.View();

    // 评论这篇文章

    var comment = TestLibrary.BlogHelper.Comment(blog);

    // 找到刚才的评论、删除评论,然后执行验证确定

    // 评论被删除掉

}

 

每个测试用例单独执行的时候,都不会有任何问题,但是两个放在一起执行的时候,问题就来了,两个用例创建了同名的文章,这样就直接导致测试结果的不稳定。为了解决这个问题,也许有人会创建一个随机生成文章标题的帮助类(Helper Class),这种编码的难度很大,因为需要确保文章的标题永远是唯一的(或许可以考虑Guid?)。

 

2.         节省测试的时间,在用例中执行过多的步骤也会增加测试时间。虽然测试团队都会在晚上批量执行自动化测试用例,但是在产品开发的过程当中,测试用例通过率不能达到100%是很正常的。对于每一个失败的测试用例,测试人员都要分析失败的原因判断是产品的缺陷导致的,还是由于测试代码本身的问题引起的。额外的测试步骤也会相应地增加测试人员分析失败的时间(一般测试人员都会重新执行一遍测试代码来找出问题原因)。

 

3.         增加不必要的测试用例失败,测试可以分好几块,一种是功能测试,也就是验证产品的功能是否可以正常工作;一种是压力测试,即测试产品在极端情况下的执行情况;还有其他的例如性能测试,国际化测试等等。一般来说,不同的测试都会有自己的自动化测试用例集合。如果在功能测试当中,用例代码在系统里面添加了很多冗余数据,执行的测试用例多了,必然导致网站的性能和反应速度会有所下降。而在测试代码中,一般都会在执行一步操作以后,等待一段时间等网页的内容刷新。网站反应速度的下降,直接导致测试失败。例如本来在编写测试代码的时候,3秒钟肯定会刷新的网页,在测试执行的环境中,因为过多的冗余数据,30秒可能都打不开一个网页。当然啦,网站反应速度的下降肯定是产品代码的缺陷,但是不应该将压力测试和功能测试混合起来做。

因此,我个人建议,在测试过程中,例如前面举的评论功能的测试中,完全可以事先在网站的数据库中先创建好一篇或多篇专门用来做评论测试的文章。而每天晚上,在大规模执行自动化测试用例之前,编写一个小的脚本,将网站的数据库替换成这个基准数据库。

又比如,为了测试用户权限管理的功能,完全可以事先在网站的数据库当中先准备好一个管理员帐号,这个管理员帐号和密码可以当作一个常量,然后测试代码里都使用这个帐号来执行权限管理的测试。例如下面的代码:

public class Consts

{

    public const string TimeToWaitForPageToLoad = "30000";

 

    public const string AdminUserName = "administrator";         

 

    public const string AdminPassword = "0123456";

}         

 

public class UserHelper : UIHelperBase

{

    public UserHelper(TestLibrary settings)

        : base(settings)

    {

    }

 

    public void LogOnAsAdmin()

    {

        LogOn(TestLibrary.Consts.AdminUserName, TestLibrary.Consts.AdminPassword);

    }

 

    public void LogOn(string username, string password)

    {

        if (String.IsNullOrEmpty(username))

            throw new CaseErrorException(new ArgumentNullException("username"));

        if (String.IsNullOrEmpty(password))

            throw new CaseErrorException(new ArgumentNullException("password"));

 

        selenium.Open("/");

        Thread.Sleep(2000);

        if (selenium.IsElementPresent("link=Log On"))

        {

            selenium.Click("link=Log On");

        }

        if (selenium.IsElementPresent("link=Login"))

        {

            selenium.Click("link=Login");

        }

        selenium.WaitForPageToLoad(TestLibrary.Consts.TimeToWaitForPageToLoad);

        selenium.Type("username", username);

        selenium.Type("password", password);

        selenium.Click("//input[@value='Log On']");

        selenium.WaitForPageToLoad(TestLibrary.Consts.TimeToWaitForPageToLoad);

    }

}

 

在上面的代码中,我也把等待网页刷新的时间设置成常量。对于在测试代码中使用事先在基准数据库中准备的测试数据,需要一点编程技巧。请先看下面的代码,下面的代码是一段记录通过网页操作创建文章的代码:

public class Blog : UIHelperBase

{

    // 博客的标题

    public string Title { getprivate set; }

 

    // 博客的超链接

    public string Permalink { getprivate set; }

 

    // 博客的超链接文本

    public string MenuText { getprivate set; }

 

    public string Owner { getprivate set; }

 

    public Blog(TestLibrary settings, string title,

        string permalink, string menutext, string owner)

        : base(settings)

    {

        Title = title;

        Permalink = permalink;

        MenuText = menutext;

        Owner = owner;

    }

 

    // 通过网页界面的操作创建一篇新文章

    //

    // PostSetting是一个结构,包含了一篇新文章的所有元素,

    // 例如文章标题,内容等等.

    public Post CreatePost(PostSettings settings)

    {

        if (settings == null)

            throw new CaseErrorException(new ArgumentNullException("settings"));

        if (!String.IsNullOrEmpty(settings.Body))

            throw new CaseErrorException("Set post body is not implemented yet!");

        if (settings.PublishDateTime.HasValue)

            throw new CaseErrorException("PublishDateTime is not implemented yet!");

 

        // selenium这个变量,你可以想象成是一个正在浏览网页的网友的封装

        selenium.Open("/");

        selenium.Click("link=Admin");

        selenium.WaitForPageToLoad(TestLibrary.Consts.TimeToWaitForPageToLoad);

        selenium.Click("link=Manage Blogs");

        selenium.WaitForPageToLoad("60000");

        selenium.Click(String.Format("link={0}", Title));

        selenium.WaitForPageToLoad(TestLibrary.Consts.TimeToWaitForPageToLoad);

        selenium.Click("link=New Post");

        selenium.WaitForPageToLoad(TestLibrary.Consts.TimeToWaitForPageToLoad);

        selenium.Type("Routable_Title", settings.Title);

        selenium.Type("Tags", settings.Tags);

 

        if (settings.Permalink != null)

            selenium.Type("Routable_Slug", settings.Permalink);

        if (settings.DisableNewComments)

            selenium.Click("CommentsActive");

 

        if (settings.PublishSetting == PostSettings.PublishSettings.PublishNow)

            selenium.Click("Command_PublishNow");

        else if ( settings.PublishSetting == PostSettings.PublishSettings.PublishLater )

            throw new CaseErrorException("PublishLater is not implemented yet!");

 

        selenium.Click("submit.Save");

        selenium.WaitForPageToLoad(TestLibrary.Consts.TimeToWaitForPageToLoad);

 

        return new Post(TestSettings, settings, this);

    }

}

 

public class PostSettings

{

    public enum PublishSettings

    {

        SaveDraft,

        PublishNow,

        PublishLater

    }

 

    public string Title { getset; }

 

    public string Permalink { getset; }

 

    public string Body { getset; }

 

    public string Tags { getset; }

 

    public bool DisableNewComments { getset; }

 

    public PublishSettings PublishSetting { getset; }

 

    public DateTime? PublishDateTime { getset; }

}

 

public class Post : UIHelperBase

{

// 当初创建文章的原始详细信息

    public PostSettings Settings { getprivate set; }

 

    // 文章的标题 – 从网页上获取

    public string Title { get { return selenium.Read(...); } }

 

// 下面省略文章相关的操作若干

// ...

 

    public Post(TestLibrary settings, PostSettings postSettings, Blog blog)

        : base(settings)

    {

        Settings = postSettings;

        ContainerBlog = blog;

}

 

// 下面省略文章相关的操作若干

// ...

 

}

 

从上面的代码中,你可以观察到,Post的属性,除了Settings属性以外,其他的属性都是从网页上直接读取的当然是假设当前网页正在显示对应的文章。因此,要将基准数据库集成到自动化测试代码中来,只要实例化一个PostSettings变量就好了。TestLibrary 负责连接到Selenium-RC,并保存对应连接的类。下面的代码演示了这个思想:

public class TestLibrary

{

    public UserHelper UserHelper { getprivate set; }

 

    public BlogHelper BlogHelper { getprivate set; }

 

    public CommentHelper CommentHelper { getprivate set; }

 

    public Blog DefaultBlog { getprivate set; }

 

    public Post DefaultPost { getprivate set; }

 

    public ISelenium Selenium { getprivate set; }

 

    public string SiteUrl { getprivate set; }

 

    public class Consts

    {

        public const string TimeToWaitForPageToLoad = "30000";

 

        public const string AdminUserName = "administrator";      

 

        public const string AdminPassword = "0123456";

    }

 

    public TestLibrary(ISelenium selenium)

    {

        this.UserHelper = new UserHelper(this);

        this.BlogHelper = new BlogHelper(this);

        this.CommentHelper = new CommentHelper(this);

        Selenium = selenium;

 

        InitialDefaultSiteDate();

    }

 

    private void InitialDefaultSiteDate()

    {

        DefaultBlog = new Blog(this"Default Test Blog""default-test-blog""Default Test Blog"Consts.AdminUserName);

        DefaultPost = new Post(thisnew PostSettings()

        {

            Title = "Default Test Post",

            Permalink = "default-test-post",

            Body = "This is for web site testing purpose.",

            Tags = "Test",

            PublishSetting = PostSettings.PublishSettings.PublishNow

        },

        DefaultBlog);

    }

}

下面是TestLibrary的完整源代码:

public class TestLibrary

{

    public UserHelper UserHelper { getprivate set; }

 

    public BlogHelper BlogHelper { getprivate set; }

 

    public CommentHelper CommentHelper { getprivate set; }

 

    public Blog DefaultBlog { getprivate set; }

 

    public Post DefaultPost { getprivate set; }

 

    public ISelenium Selenium { getprivate set; }

 

    public string SiteUrl { getprivate set; }

 

    public class Consts

    {

        public const string TimeToWaitForPageToLoad = "30000";

 

        public const string AdminUserName = "administrator";         

 

        public const string ContributorUser = "Contributor1";

 

        public const string AuthorUser = "Author1";

 

        public const string ModeratorUser = "Moderator1";

 

        public const string EditorUser = "Editor1";

 

        public const string CommonPassword = "0123456";

 

        public const string AdminPassword = "0123456";

 

        public const string DefaultSeleniumHost = "localhost";

 

        public const int DefaultSeleniumPort = 4444;

 

        public const string DefaultBrowser = "*firefox";

 

        public const string DefaultSite = "http://localhost:30320";

    }

 

    public TestLibrary(ISelenium selenium)

    {

        this.UserHelper = new UserHelper(this);

        this.BlogHelper = new BlogHelper(this);

        this.CommentHelper = new CommentHelper(this);

        Selenium = selenium;

 

        InitialDefaultSiteDate();

    }

 

    private void InitialDefaultSiteDate()

    {

        DefaultBlog = new Blog(this"Default Test Blog""default-test-blog""Default Test Blog"Consts.AdminUserName);

        DefaultPost = new Post(thisnew PostSettings()

        {

            Title = "Default Test Post",

            Permalink = "default-test-post",

            Body = "This is for web site testing purpose.",

            Tags = "Test",

            PublishSetting = PostSettings.PublishSettings.PublishNow

        },

        DefaultBlog);

    }

 

    public static TestLibrary SetupTest(TestContext testContext)

    {

        if (testContext != null && testContext.DataRow != null && testContext.DataRow.Table.Columns.Contains("seleniumHost"))

        {

            return SetupTest(testContext.DataRow["seleniumHost"].ToString(),

                Int32.Parse(testContext.DataRow["seleniumPort"].ToString()),

                testContext.DataRow["browser"].ToString(),

                testContext.DataRow["site"].ToString());

        }

        else

        {

            return SetupTest(Consts.DefaultSeleniumHost, Consts.DefaultSeleniumPort,

                Consts.DefaultBrowser, Consts. DefaultSite);

        }

    }

 

    public static TestLibrary SetupTest(string seleniumHost, int seleniumPort,

        string browser, string site)

    {

        var selenium = new DefaultSelenium(

            seleniumHost, seleniumPort, browser, site);

        selenium.Start();

 

        return new TestLibrary(selenium) { SiteUrl = site };

    }

 

    public void Shutdown()

    {

        try

        {

            Selenium.Stop();

        }

        catch (Exception)

        {

            // Ignore errors if unable to close the browser

        }

    }

}

 

未完待续……

本文转自 donjuan 博客园博客,原文链接:http://www.cnblogs.com/killmyday/archive/2010/03/19/1690247.html   ,如需转载请自行联系原作者

相关实践学习
通过云拨测对指定服务器进行Ping/DNS监测
本实验将通过云拨测对指定服务器进行Ping/DNS监测,评估网站服务质量和用户体验。
相关文章
|
3月前
|
数据采集 JSON 前端开发
GraphQL接口采集:自动化发现和提取隐藏数据字段
本文围绕GraphQL接口采集展开,详解如何通过`requests`+`Session`自动化提取隐藏数据字段,结合爬虫代理、Cookie与User-Agent设置实现精准抓取。内容涵盖错误示例(传统HTML解析弊端)、正确姿势(GraphQL请求构造)、原因解释(效率优势)、陷阱提示(反爬机制)及模板推荐(可复用代码)。掌握全文技巧,助你高效采集Yelp商家信息,避免常见误区,快速上手中高级爬虫开发。
GraphQL接口采集:自动化发现和提取隐藏数据字段
|
1月前
|
JavaScript 测试技术 Python
UI自动化测试中的元素等待机制解析
在UI自动化测试中,元素定位失败常因页面存在iframe或缺乏合理等待机制。本文解析三种等待策略及其应用场景:显式等待可精确控制单个元素等待条件,支持自定义轮询;隐式等待全局生效,适合简单页面加载;强制等待仅用于临时调试,正式脚本慎用。通过对比三者执行精度、资源消耗及适用场景,帮助选择最优策略,提升测试效率与稳定性。
|
1月前
|
测试技术 Python
Python接口自动化测试中Mock服务的实施。
总结一下,Mock服务在接口自动化测试中的应用,可以让我们拥有更高的灵活度。而Python的 `unittest.mock`库为我们提供强大的支持。只要我们正确使用Mock服务,那么在任何情况下,无论是接口是否可用,都可以进行准确有效的测试。这样,就大大提高了自动化测试的稳定性和可靠性。
59 0
|
3月前
|
jenkins 测试技术 Shell
利用Apipost轻松实现用户充值系统的API自动化测试
API在现代软件开发中扮演着连接不同系统与模块的关键角色,其测试的重要性日益凸显。传统API测试面临效率低、覆盖率不足及难以融入自动化工作流等问题。Apipost提供了一站式API自动化测试解决方案,支持零代码拖拽编排、全场景覆盖,并可无缝集成CI/CD流程。通过可视化界面,研发与测试人员可基于同一数据源协作,大幅提升效率。同时,Apipost支持动态数据提取、性能压测等功能,满足复杂测试需求。文档还以用户充值系统为例,详细介绍了从创建测试用例到生成报告的全流程,帮助用户快速上手并提升测试质量。
|
3月前
|
存储 jenkins 测试技术
Apipost自动化测试:零代码!3步搞定!
传统手动测试耗时低效且易遗漏,全球Top 10科技公司中90%已转向自动化测试。Apipost无需代码,三步实现全流程自动化测试,支持小白快速上手。功能涵盖接口测试、性能压测与数据驱动,并提供动态数据提取、CICD集成等优势,助力高效测试全场景覆盖。通过拖拽编排、一键CLI生成,无缝对接Jenkins、GitHub Actions,提升测试效率与准确性。
129 11
|
3月前
|
人工智能 自然语言处理 测试技术
自然语言生成代码一键搞定!Codex CLI:OpenAI开源终端AI编程助手,代码重构+测试全自动
Codex CLI是OpenAI推出的轻量级AI编程智能体,基于自然语言指令帮助开发者高效生成代码、执行文件操作和进行版本控制,支持代码生成、重构、测试及数据库迁移等功能。
339 0
自然语言生成代码一键搞定!Codex CLI:OpenAI开源终端AI编程助手,代码重构+测试全自动
|
4月前
|
存储 人工智能 API
OWL:告别繁琐任务!开源多智能体系统实现自动化协作,效率提升10倍
OWL 是基于 CAMEL-AI 框架开发的多智能体协作系统,通过智能体之间的动态交互实现高效的任务自动化,支持角色分配、任务分解和记忆功能,适用于代码生成、文档撰写、数据分析等多种场景。
1138 13
OWL:告别繁琐任务!开源多智能体系统实现自动化协作,效率提升10倍
|
5月前
|
人工智能 自然语言处理 测试技术
Potpie.ai:比Copilot更狠!这个AI直接接管项目代码,自动Debug+测试+开发全搞定
Potpie.ai 是一个基于 AI 技术的开源平台,能够为代码库创建定制化的工程代理,自动化代码分析、测试和开发任务。
437 19
Potpie.ai:比Copilot更狠!这个AI直接接管项目代码,自动Debug+测试+开发全搞定
|
4月前
|
缓存 监控 API
微店商品详情API接口实战指南:从零实现商品数据自动化获取
本文介绍了微店商品详情API接口的应用,涵盖申请与鉴权、签名加密、数据解析等内容。通过Python实战演示了5步获取商品数据的流程,并提供了多平台同步、价格监控等典型应用场景。开发者可利用此接口实现自动化操作,提升电商运营效率,降低人工成本。文中还总结了频率限制、数据缓存等避坑指南,助力开发者高效使用API。
|
4月前
|
数据采集 消息中间件 API
微店API开发全攻略:解锁电商数据与业务自动化的核心能力
微店开放平台提供覆盖商品、订单、用户、营销、物流五大核心模块的API接口,支持企业快速构建电商中台系统。其API体系具备模块化设计、双重认证机制、高并发支持和数据隔离等特性。文档详细解析了商品管理、订单处理、营销工具等核心接口功能,并提供实战代码示例。同时,介绍了企业级整合方案设计,如订单全链路自动化和商品数据中台架构,以及性能优化与稳定性保障措施。最后,针对高频问题提供了排查指南,帮助开发者高效利用API实现电商数智化转型。适合中高级开发者阅读。

热门文章

最新文章