1 全过程的软件测试图解
传统的软件测试,开发人员完成任务之后,最后交付给测试人员,这种模式下,测试人员不能及早发现需求阶段的缺陷,同时测试工作的开展也滞后了,产品质量得不到有效的过程控制和分析,总体进度可能会由于返工问题造成拖延。
在整个SDLC中,三条角色主线和四个阶段。
三条角色主线:开发、QA、测试,文中主要讲解测试。
四个阶段:需求、开发、发布、日常运营。
简单来说可以归纳为下图所示:
测试人员贯穿这四个阶段,开展测试活动,试实践活动简单描述如下图所示:
每个阶段也有开发人员对应的活动,以及QA人员对应的活动。
对于产品而言,每次版本迭代,都会经历:需求、开发、发布,最后推向日常运营,发布阶段虚线指向的需求阶段和日常运营阶段,并不是一个终止阶段,而是不断迭代的过程。
那测试人员是如何开展全程软件测试活动的呢?
2 需求阶段测试
在需求阶段,开发人员、测试人员、QA人员主要做的事情,如下表所示:
阶段 | 开发人员 | 测试人员 | QA人员 |
需求阶段 | · 用户故事分析 · 用户故事估时 | · 参与用户故事分析、挖掘故事含混性 · 参考经验库质疑开发的时间估算 | · 保证确认需求活动符合需求管理过程 · 管理用户故事评审 · 管理需求变更 |
作为测试人员的主要实践如下:
参与用户故事分析、挖掘故事含混性
在sprint会议上,对用户故事进行分析,检查功能性需求和非功能性需求是否描述清晰,其中可以将非功能性需求作为验收要点,例如一个用户故事:
“客户希望提高响应时间”
测试人员应当协助开发人员消除故事的含混性:提高什么的响应时间和响应时间为多少?可以建议修改为:
“客户信息普通查询返回结果的响应时间为5s内”
说明在“客户信息”模块,进行“普通查询”操作,返回结果的时间在5s内,这个陈述句已经清晰表达了,也达到了消除含混性的效果。同样,测试人员可以编写提高查询效率的用户故事:
“客户在信息查询模块,进行普通查询,能够在5s内返回结果”
“备注:5s为非功能性需求,也是验收要点”
参考经验库质疑开发的时间估算
在sprint会议上,开发人员根据经验出牌(团队自己定义的规则,用扑克牌)估算时间,当给出最终结果的时候,测试人员应当对其进行质疑。测试人员借鉴历史经验库:开发人员在某方面的技能如何、该模块曾经产生过何种程度的缺陷、修复缺陷的消耗时间是多少等等,综合考虑,提出疑问,让开发估算最终的时间,尽可能考虑这些因素。当然,测试人员能够质疑的其中一个前提是:测试人员具备相关开发经验。
小结:在需求阶段,测试人员要发挥作用,减少含混性需求引入到开发阶段、同时协助开发做好时间估算。
3 开发阶段测试
在开发阶段,开发人员、测试人员、QA人员主要做的事情,如下表所示:
阶段 | 开发人员 | 测试人员 | QA人员 |
需求阶段 | · 用户故事分析 · 用户故事估时 | · 参与用户故事分析、挖掘故事含混性 · 参考经验库质疑开发的时间估算 | · 保证确认需求活动符合需求管理过程 · 管理用户故事评审 · 管理需求变更 |
作为测试人员的主要实践如下:
功能要点确认
Xmind是一个非常好用的脑图工具,通常在开发人员进行编码前,测试人员会针对需求处理的用户故事,与开发人员进行确认,修正理解偏差,确保需求理解一致。
图-5-脑图用例模板
测试用例设计
测试人员主要设计测试故事点,使用DSL(Domain Specific language),对测试用例进行描述,包括三个基本要素:
Feature、Scenario、Example,补充要素:xmind、Requirement。
Feature:把测试分类到某个模块,并对这个特性本身的业务目的进行相关描述,带进业 务目标,传递业务知识。
Scenario:标明这个Feature的测试场景,可以使用文字描述步骤,或者使用xmind脑图
描述,场景中的数据使用Examples中列出的。
Example:引出具体的数据表格把用到的数据都展示出来,避免相同步骤因为测试数据 的变化而重复若干遍造成冗余。
Xmind:脑图文件,展示测试故事点
Requirement:关联需求管理系统的需求id。
随着敏捷越来越广为人知,敏捷测试也更多受到了大家的关注。在这里,我想谈一下我在敏捷项目中遇到的一个自动化测试相关问题以及我们如何借助DSL领域专用语言来解决它。
对敏捷软件开发方法有一定了解的人都知道,敏捷软件开发过程是一个迭代式交付的过程。每个迭代相当于比较小型的交付周期。那么,为了配合频繁的软件交付,敏捷测试相对于传统测试必须要做相应的调整。这也导致了敏捷项目中的测试面临几个特有的挑战:
- 频繁的回归测试以确保每个迭代的成果都是可交付的
- 让整个开发团队参与到测试活动中以缩短质量信息的反馈周期
- 让客户参与到测试活动中来帮助提高测试的有效性
自动化测试在应对频繁的回归测试这个挑战上起着非常关键的作用。自动化测试做不好,团队最终会被每个迭代都会增加的回归测试工作量压垮。
我经历过的一个团队,在这个团队中,大家很早就意识到了自动化测试的重要性,在自动化测试上的投入不遗余力。我们相信自动化功能测试增加到足够多的时候,它就能指导手动回归测试,保证整个交付过程顺利进行。
的确,自动化测试刚开始进行的时候,我们收益颇多。每增加一个自动化测试,我们就能减少一些手动测试。自动化测试让我们我们有比较充裕的时间来手动测试那些还没有来得及自动化的、难以被自动化的功能点上,而且还能有时间和精力做探索性测试。这个结果让团队感到生活很美好,也让我们对自动化测试坚信不疑
然而好景不长,随着自动化测试的不断增加,我们会面临这样一些问题:
- 自动化测试是围绕着实现细节展开的。随着数量的增多,业务的轮廓很容易迷失在细节中。
- 在功能级别丧失了对测试的追踪。由于测试人员无法具体知晓那些测试案例被自动化测试覆盖。每次回归的时候,团队都需要回归整个测试组。
于是,我们的手动测试越来越难得到自动化测试的帮助。它开始成了项目的鸡肋。测试代码阅读困难、维护困难以及测试结果的看起来也很费劲。这直接导致了我们不仅要投入相当的时间来增加自动化测试,也要投入不少时间来阅读并利用测试结果。
于是我们开始重新审视自动化测试的做法,继续摸索更好的方式。
很快,我们发现“能够跑起来”并不是好的自动化测试仅需的特性。让我们通过一段测试代码来看一下具体怎么回事。
selenium.open(“/”) selenium.type(“id=username”, “myname”) selenium.type(“id=password”, “mypassword”) selenium.click(“id=btnLogin”) selenium.waitForPageToLoad(30000) assertTrue(selenium.isTextPresent(“Welcome to our website!”))
这个测试中,我们首先打开了一个页面,在页面中寻找一个id为username的输入框,输入“myname”,然后再寻找一个id为password的输入框,输入“password”,然后点击一个id为btnLogin的按钮,等待30秒以后,断言页面应该出现的文字。
我们可以看到,这个测试的实现很完整的描述了测试的操作过程,是一个面向步骤而不是目的的描述。当然,稍加分析,我们也可以看出来这个测试的目的是测用户登录成功系统。
但是,想象当我们有很多这样面向步骤来描述的测试时,要从中抽离出被无数细碎的操作步骤所淹没的测试意图,并把测试的结果利用起来,其实并没有那么直观。而且,如果在测试中出现了错误,对于问题的具体功能点的定位也不是那么容易。
与此同时,并不是团队中所有的成员都有能力阅读和编写这样的测试。这无疑降低了团队成员对于自动化测试的参与度。对于客户,自动化测试更是一个黑盒子,做了什么,没做什么,基本上搞不清,更谈不上参与到自动化测试中,帮助提高测试的有效性。
种种状况,究其原因就是测试可读性太差,测试意图不够明显。可运行并且容易读的测试才是好的自动化测试。这样才能够保证任何时候,我们不会丧失对于测试案例的跟踪与管理。测试人员随时都可以通过快速阅读测试,了解那些功能已经被自动化测试覆盖,有效规划手工测试的工作量。
怎么提高测试的可读性呢?
我们的解决办法是DSL领域专用语言。
什么是领域专用语言?在马丁大叔的博客里有比较详细的描述。大致来说,领域专用语言就是针对某个领域的特定目的编程语言。不像Java、C#等通用语言,可以解决任何领域的问题。领域专用语言通过自己独特的语法结构来描述更接近于专业领域语言的业务。
让测试的描述能够接近被测系统的领域语言、使测试意图得到清晰表达就是我们想要得到的效果。DSL正好能够帮我们实现。
让我们再看看之前的那段代码:
selenium.open(“/”) selenium.type(“id=username”, “myname”) selenium.type(“id=password”, “mypassword”) selenium.click(“id=btnLogin”) selenium.waitForPageToLoad(30000) assertTrue(selenium.isTextPresent(“Welcome to our website!”))
由于使用的是通用语言,在我们这个特定的使用场景中显得过于细节化、过程化,不能清晰表达测试意图。
换成DSL,我们的测试就可以直接用验收标准的语言来描述如下:
Given I am on login page When I provide username and password Then I can enter the system
这样测试的内容就直观多了,还包含了一些业务信息,让我们知道这个是在测试一个登录的场景,而不是任意的输入信息,兼顾传递了业务知识的职责。至于这些DSL背后能够运行的代码,也被隐藏起来。如果是不能够阅读原来那样的测试代码的人(不管是需求分析人员还是客户甚至一些对自动化代码关注比较少的测试人员)想要加入到自动化测试活动中进行反馈,就不会被DSL背后的代码带来的“噪音”所影响。
当然,在我们的现实应用场景中,这个需求没有那么简单,我们的验收标准还会考虑不同的数据比如输入不同组合的用户名密码:
Given I am on login page When I provide ‘david’ and ‘davidpassword’ Then I can enter the system Given I am on login page When I provide ‘kate’ and ‘kate_p@ssword’ Then I can enter the system
以及更多的测试数据。
那么这种情况下,仅仅是比较通俗的语言还是不够的,毕竟测试数量在那摆着。如果测试数量不能减少,维护起来仍然很麻烦。打个比方,如果系统的实现变成了每次都要输入用户名、密码和一个随机验证码,我们就需要在我们的自动化测试中修改多处,比较繁琐。因此,我们需要在可读性比较好的自然语言描述的测试上,把它的抽象层次再提高一点。
幸运的是,我们当时选择的DSL工具是cucumber,它除了提供了几个测试的描述层次:Feature,Scenario,Steps,还提供了非常好的一种组织方式—数据表。
这样,我们的这个自动化测试就可以把之前的那个登录的功能根据特性、场景总结和具体的步骤分离开来,清晰的分层,同时利用数据表我们的测试精简成一系列被重复多次但输入数据有所变化的操作过程,如下:
Feature: authentication In order to have personalized information I want to access my account by providing authentication information So that the system can know who I am Scenario Outline: login successfully Given I am on login page When I provide ‘<username>’ and ‘<password>’ Then I can enter the system Examples: |username |password | |david |davidpass | |kate |kate_p@ssword|
测试这下看起来就更清爽了。首先,用Feature关键字,我们把测试分类到login这个大特性下的,并对这个特性本身的业务目的进行相关描述,带进业务目标,传递业务知识;然后用Scenario关键字来提高挈领的标明我们这个测试场景中做的是测试登录成功的情况,并且把步骤都写出来;最后,我们用Examples关键字引出具体的数据表格把用到的数据都展示出来,避免我们的相同步骤因为测试数据的变化而重复若干遍造成冗余。万一碰上了需求的变化,要求同时提供用户名、密码和验证码,那我们的测试也只需要改动较少的地方就足够了。
更棒的是,用了这种数据表的方式,整个团队的协作效率提高了。对于写代码没有那么顺畅的测试人员来说,增加自动化测试也就是增加更多测试数据,填充到数据表里就可以了。
就这样,我们用DSL实现了可执行的可读性高的文档。帮助了回归测试,降低了文档维护难度,也促进团队成员利用测试来传递知识的积极性,让更多人能够参与到测试中。
用例评审
主要是坚持同行评审的原则,主要在测试组内进行,负责该任务的开发人员也会参与,简单来说就是对测试用例进行查漏补缺的工作。
测试探索
进行了“功能要点确认”和“用例评审”后,为了保证测试场景的覆盖率,需要再进行测试探索。在开发人员完成雏形之后,使用探索式测试的策略,对功能基本流程进行有目的的快速走查,挖掘功能不确定的地方和补充测试场景,避免不确定的因素拖延到开发阶段后期,造成返工。
其中:功能测试、Bug Tracking、回归测试、系统测试、验收测试都是日常测试工作所需环节。
燃尽图发布
另外,测试人员还有一项重要工作,每日发布燃尽图,让团队了解当前进度情况,总结问题
所在,寻求耗时超过预期时间任务的解决办法。
图-6-燃尽图
图形特点:
1)剩余工时在计划基准上方,代表进度有所延迟,应抓紧进度;
发现此类问题,需要分析总结,原则是保证交付时间,对相应任务进行调整,拥抱变化,发现任务粒度太大,该拆分的继续拆分;对于重构需要慎重,不要过度深入重构,给测试带来额外工作量,影响整个进度,对于整个版本而言,只有开发、测试在承诺的时间内完成任务,才是真正完成,仅仅开发完成交付算不上成功。
2)剩余工时在计划基准接近,代表进展良好,继续保持;
此时也需要查看在这种进度下,优先级高的任务是否得到时间保证,而不是因为处理完简单任务才使得燃尽图长的好看。往往有些开发人员,喜欢挑着任务来做,把简单易做、优先级的任务先完成了,因为这些总在预期内能够完成,所以前期燃尽图的趋势看起来没有问题。
缺陷经验库
每个团队都存在开发/测试新人和开发/测试老人,当测试人员与开发新人进行需求确认的时候,还需要进行缺陷经验教训的提醒,避免多走弯路。
提升开发自测质量
测试人员可以提供相关checklist(大家可以根据原作者提供的修改为符合团队的)帮助开发人员在编码过程中关注开发自测的要点,从而提升质量。
图-8-web软件测试checklist
持续集成
利用持续集成(Jenkins)平台,做到快速的构建开发代码,自动的单元测试化,来提高开发代码的效率和质量。
负责单元测试的开发人员,会收到失败构建的邮件;
负责集成测试的开发人员,会收到失败构建的邮件;
负责自动化测试(Selenium)的测试负责人员,会收到失败构建的邮件;
这种方式,确保单元测试、集成测试、自动化测试,有相关人员关注和维护。
图-9-持续集成
Sonar反馈
Sonar is an open platform to manage code quality. As such, it covers the 7 axes of code quality。
sonar分析结果
测试人员主要反馈问题如下:
Code coverage:团队要求代码覆盖率在80%以上;
Test success:团队要求测试成功率在100%;
Duplications:团队要求代码重复率在10%以下;
Violations:团队要求Major类别的代码规则缺陷在20以下;
开发团队必须保证每个环境的质量目标,才能够保证整个的质量目标。
小结:
测试人员与开发人员永远不是敌对关系,而是协助关系,确切来说是质量天枰的两边,任何一边的工作没有做好,都会失去平衡。
4 发布阶段测试
在发布阶段,开发人员、测试人员、QA人员主要做的事情,如下表所示:
阶段 |
开发人员 |
测试人员 |
QA人员 |
发布阶段 |
· 上线申请 · 上线部署 · 服务监控 |
· 测试报告 · 线上功能检查 |
· 管理评审活动 · 管理文档产物 |
作为测试人员的主要实践如下:
测试报告
完成验收测试,提供测试报告,给出测试数据度量,例如:
- 测试发现缺陷总数:测试过程中产生的去除状态为“无效”、“不用改”的缺陷数目。
- 测试发现严重缺陷数:测试过程中产生的并去除状态为“无效”、“不用改”的、且严重性为“Major”和“Critical”的缺陷总数目。
- 测试发现缺陷修复数:测试过程中产生的状态为“已关闭”的缺陷数量;
- 未解决缺陷数:去除状态为“无效”、“不用改”、“关闭”的缺陷总数。
- 缺陷修复率:(测试发现缺陷的修复数)÷(测试发现缺陷总数)×100%
- 严重缺陷率:(测试发现严重缺陷数)÷(测试发现缺陷总数)×100%
- 严重缺陷修复率:(已修复的严重缺陷数)÷(测试发现严重缺陷数)×100%
- 测试需求覆盖率:已测试需求个数÷需求总数×100%
缺陷统计分析报告
另外,测试人员还有一项重要工作,对当前版本的缺陷进行统计分析:
按缺陷级别统计:
Critical |
Major |
Medium |
Minor |
总计 |
|
首页 |
0 |
0 |
1 |
0 |
1 |
模块一 |
0 |
0 |
0 |
2 |
2 |
模块二 |
0 |
1 |
2 |
10 |
13 |
模块三 |
0 |
0 |
1 |
4 |
5 |
模块四 |
0 |
0 |
1 |
2 |
3 |
模块五 |
0 |
0 |
3 |
2 |
5 |
模块六 |
0 |
1 |
0 |
1 |
2 |
模块七 |
0 |
2 |
0 |
6 |
8 |
sonar |
0 |
1 |
2 |
0 |
3 |
总计 |
0 |
5 |
10 |
27 |
图-11-缺陷统计
按缺陷来源统计:
开发1 |
开发2 |
开发3 |
开发4 |
开发5 |
遗留 |
|
Critical |
0 |
0 |
0 |
0 |
0 |
0 |
Major |
1 |
2 |
0 |
0 |
0 |
2 |
Medium |
1 |
7 |
0 |
1 |
0 |
1 |
Minor |
1 |
7 |
4 |
6 |
3 |
6 |
总计 |
3 |
16 |
4 |
7 |
3 |
9 |
按缺陷状态统计:
缺陷总数 |
已关闭缺陷数 |
遗留 |
缺陷修复率 |
严重缺陷数 |
严重缺陷率 |
已关闭严重缺陷数 |
严重缺陷修复率 |
42 |
40 |
2 |
95% |
5 |
12% |
5 |
100% |
测试进度和问题分析:
1. 从BUG的严重级别分布来看,Major级别以上的BUG占12%,占的比重不高,说明大部分的主要功能已经实现了;
2. 其中在sonar定义级别的缺陷,主要集中在代码规范和单元测试覆盖率,说明代码质量有待提高;
3. 版本测试的前期时间较充足,后期随着开发提交完成的功能点增多,BUG数量增多,剩余测试时间变得紧张;
4. 在版本测试期间,发现测试环境存在一次代码被覆盖、两次因开发人员操作失误影响测试执行的情况;
小结:
测试人员应当持续反馈、改进、总结每个版本发生的问题(不管是缺陷,还是过程中出现的),并对缺陷进行分析,总结出一些规律,帮助开发人员建立良好的习惯,改进代码的质量。
5 日常运营阶段测试
在日常运营阶段,开发人员、测试人员、QA人员主要做的事情,如下表所示:
阶段 |
开发人员 |
测试人员 |
QA人员 |
日常运营 |
生产故障登记 |
· 版本问题反馈和改进提议 · 生产故障分析 |
管理日常运营活动 |
日常运营阶段,并不是终止阶段,即便需求、开发、发布阶段暂停活动,只要产品提供服务,日常运营都存在着。
作为测试人员的主要实践如下:
版本问题反馈和改进提议
对日常运营发生的问题,总结反馈,提出改进建议,并且跟踪实施。
生产故障分析
协助开发排查生产故障,避免测试场景的遗漏。
6 人力资源
软件测试并不是保证产品质量的最后一道防线,测试人员也不是,测试人员的工作完全可以由更加资深的开发人员来完成,不过现实总是残酷的,目前测试与开发的比例为:1:3,在成熟的团队是这样子,另外一些还在持续改进的团队,由于资源不足,可能去到1:7。开发人员在相当长的一段时间内不可能完全替代测试人员,有个关键要素:思维方式不同,有句古话来形容:江山易改本性难移。当开发人员的思维方式改变的时候,那就成为测试人员了,倒不如把测试人员独立出来更好,并且培养给开发人员一定的测试素养,这个对保证产品质量都是有帮助的。
全程软件测试实践,强调的是贯穿每个阶段的测试活动,不论是开发、还是测试,要理解双方的活动价值,什么时候该做什么事情,什么事情该做到什么程度才算好,保证每个环节的质量,才能够保证产品的全程质量,另外产品质量不是测试出来的,而是构建过程中沉淀下来的,开发人员的素养、测试人员的素养、以及团队对开发测试过程的重视程度,决定了产品质量。产品质量就如同一块蛋糕,应当切分为小块,落实到每个人手里,让每个人尝到甜头,担当起来。
7 TQM(全面质量管理) in Software
这是一个延伸与关联,过程如下:
TQM是以产品质量为核心,建立起一套科学严密高效的质量体系,以提供满足用户需要的产品的全部活动.
在软件业,软件质量得不到提高主要原因在于质量观念的缺乏,而将全面质量管理的思想运用于软件业,是提高软件产品质量、获取竞争优势的有效手段。CMM不但对于指导过程改进是一项很好的工具,而且把全面质量管理概念应用到软件上,实现从需求管理到项目计划、项目控制、软件获取、质量保证、配置管理的软件过程全面质量管理。CMM的思想是一切从顾客需求出发,从全组织层面上实施过程质量管理,正符合了TQM的基本原则。因此,它的意义不仅仅是对软件开发的过程进程控制,最关键的它还是一种高效的管理方法,有助于企业最大程度的降低成本,提高质量和用户满意度。
软件质量管理体现TQM的运行机制 软件质量管理是CMM四级中一个独立的KPA,其目的是使项目的软件质量管理活动是有计划的、软件产品的质量目标是量化的和受到管理的。它遵循了全面质量管理活动的科学程序—PDCA(Plan、Do、Check、Action),即四个阶段:
(1) 计划:即确定质量目标以及实现这个目标需要采取的措施。制定质量计划是整个质量管理活动的基础。国家标准对质量下的定义为: 质量是产品或服务满足明确或隐含需要能力的特征和特性的总和。
对于软件来说,软件质量则体现在质量特性上,ISO/IEC9126中规定了6个质量特性,即功能性、可靠性、易用性、效率、可维护性和可一致性,每个特性包含若干子特性。设定质量目标就是要找到用户的质量需求与这些质量特性的相关性,并将其转化为开发过程中可度量的技术指标或能力指标,作为质量控制的依据。
上述的六大特性属于软件的外部属性,与用户满意度直接相关,可以根据组织的目标和项目的特点建立质量模型,并采用一定的方法,如QFD(Quality Function Deployment)、GQM(Goal Question Metrics)等确定量化的质量目标,但这在实际工作中往往是相当复杂和难以获得的。因此,更常用的做法是以过程能力目标反映产品质量目标,一个典型的能力指标就是缺陷密度(即每单位规模工作产品中存在的缺陷数)和相应的阶段缺陷排错率,可以根据历史数据估计产品的规模和目标缺陷密度,从而对每个阶段发现的缺陷数量进行控制。
(2) 实施 :即按预定计划、目标措施及其分工实际执行。为了在过程中控制软件的质量,需采取相应的手段在预定的阶段点或里程碑上进行软件工作产品质量的测量,常用的方法有 同行评审、原型评价、测试等。这些方法主要从两方面对软件的质量进行度量,一是内部属性,即过程和活动自身可以度量的属性,例如工作产品的缺陷密度 ;二是外部属性,即与用户环境相关的属性,这些属性在过程中往往难以度量,只有通过在项目的早期引入用户测试来予以评价,而让用户参与开发过程,大大有利于产品质量的提高。
(3) 检查 :即把实施的结果和计划的要求对比,检查计划的执行情况和实施的效果,是否达到预期的目标,并找出原因。在对质量度量的结果进行分析时,往往会用到一些统计工具和方法,如检查表、直方图、控制图、Pareto图、散布图、因果图、运行图等。这些工具可以帮助确定问题、评估现状、发现原因甚至形成下一步措施。
(4) 处理 :即总结经验教训,将未解决的问题作为下一阶段制定计划的依据。CMM要求对软件质量测量的结果分析后,应“采取合适的与软件质量计划相一致的措施,以便使得产品的质量测量结果与软件质量目标相符合”。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
希望对您公司IT软件研发与质量管理有帮助。 其它您可能感兴趣的文章:
敏捷软件质量保证的方法与实践
构建高效的研发与自动化运维
IT运维监控解决方案介绍
IT持续集成之质量管理
人才公司环境与企业文化
企业绩效管理系统之平衡记分卡
企业文化、团队文化与知识共享
高效能的团队建设
组织目标与个人目标
餐饮连锁公司IT信息化解决方案一
如有想了解更多软件研发 , 系统 IT集成 , 企业信息化,项目管理,企业管理 等资讯,请关注我的微信订阅号:
作者:Petter Liu
出处:http://www.cnblogs.com/wintersun/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
该文章也同时发布在我的独立博客中-Petter Liu Blog。