[原创]ASP.NET MVC 使用模板引擎AderTemplateEngine进行视图解析

mikel阅读(1140)

ASP.NET MVC 使用AderTemplateEngine模板引擎进行视图显示

1. 创建ArtTemplateViewLocator视图文件定位类,代码如下:

namespace ArtLib

{

class ArtTemplateViewLocator : ViewLocator

{

public ArtTemplateViewLocator()

{

base.ViewLocationFormats = new string[] { “~/Views/{1}/{0}.htm”,

“~/Views/{1}/{0}.html”,

“~/Views/Shared/{0}.htm”,

“~/Views/Shared/{0}.html”

};

base.MasterLocationFormats = new string[] { “” };

}

}

}

2. 创建VariableResolver类用于ArtTemplate模板视图解析,并注册自定义组件TagList,代码如下:

namespace ArtLib

{

public static class VariableResolver

{

public static object ArtTemplateResolve(string filePath,IDictionary viewData)

{

//初始化模板引擎

TemplateManager template1 = TemplateManager.FromFile(filePath);

//设置模板数据

template1.SetValue(“ViewData”, viewData);

//注册自定义的标签

template1.RegisterCustomTag(“list”, new TagList());

return template1.Process();

}

}

}

3. 创建ArtTemplateViewEngine类用于处理视图请求,并赋予ViewData给模板引擎,代码如下:

IViewLocator _viewLocator = null;

public IViewLocator ViewLocator

{

get

{

if (this._viewLocator == null)

{

this._viewLocator = new ArtTemplateViewLocator();

}

return this._viewLocator;

}

set

{

this._viewLocator = value;

}

}

public string TemplatePath { get; set; }

#region IViewEngine 成员

public void RenderView(ViewContext viewContext)

{

string viewLocation = ViewLocator.GetViewLocation(viewContext, viewContext.ViewName);

if (string.IsNullOrEmpty(viewLocation))

{

throw new InvalidOperationException(string.Format(“View {0} could not be found.”, viewContext.ViewName));

}

string viewPath = HttpContext.Current.Request.MapPath(viewLocation);

//以下为模板解析

string viewTemplate = VariableResolver.ArtTemplateResolve(viewPath, viewContext.ViewData) as string;

HttpContext.Current.Response.Write(viewTemplate);

}

#endregion

4. 创建ArtTemplateControllerFactory类,用于Golbal.asax进行视图绑定,代码如下:

namespace ArtLib

{

public class ArtTemplateControllerFactory : DefaultControllerFactory

{

protected override IController CreateController(RequestContext requestContext, string controllerName)

{

Controller controller = (Controller)base.CreateController(requestContext, controllerName);

controller.ViewEngine = new ArtTemplateViewEngine();//修改默认的视图引擎为我们刚才创建的视图引擎

return controller;

}

}

}

5. 创建自定义的模板标签类:TagList.cs用于显示数据列表,代码如下:

namespace ArtLib

{

public class TagList : ITagHandler

{

#region ITagHandler 成员

public void TagBeginProcess(TemplateManager manager, Ader.TemplateEngine.Parser.AST.Tag tag, ref bool processInnerElements, ref bool captureInnerContent)

{

processInnerElements = true;

captureInnerContent = true;

}

public void TagEndProcess(TemplateManager manager, Ader.TemplateEngine.Parser.AST.Tag tag, string innerContent)

{

Expression exp;

exp = tag.AttributeValue(“pageSize”);

if (exp == null)

throw new Exception(“Missing attribute: pageSize”);

string pageSize = manager.EvalExpression(exp).ToString();

exp = tag.AttributeValue(“currentPage”);

if (exp == null)

throw new Exception(“Missing attribute: currentPage”);

string currentPage = manager.EvalExpression(exp).ToString();

Business business = new BusinessLogic();

List<InformationSimple> list = new List<InformationSimple>();

if (string.IsNullOrEmpty(pageSize))

{

list = business.Select<InformationSimple>();

}

else

{

Page page = new Page();

page.PageSize = Convert.ToInt32(pageSize);

page.CurrentPage = Convert.ToInt32(currentPage);

list = business.SelectByPage<InformationSimple>(ref page);

}

String html = “”;

foreach (InformationSimple info in list)

{

html = html + “<tr><td>” + info.InfoTitle + “</td></tr>”;

}

html = “<table>” + html + “</table>”;

manager.WriteValue(html);

}

#endregion

}

6. 修改ASP.NET MVC的测试项目的Global.asax文件的Application_Start()方法,加入自定义控制器工厂类:ArtTemplateControllerFactory,代码如下:

protected void Application_Start()

{

ControllerBuilder.Current.SetControllerFactory(typeof(ArtTemplateControllerFactory));

RegisterRoutes(RouteTable.Routes);

}

7. 创建模板文件Views/Home/Index.htm,代码如下:

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>

<html xmlns=”http://www.w3.org/1999/xhtml” >

<head>

<title></title>

</head>

<body>

<ad:list pageSize=”10″ currentPage=”1″></ad:list>

#ViewData[“Title”]#

</body>

</html>

8.

[转载].NET模板引擎 EFPlatform.CodeGenerator (代码生成器 静态页生成 生成HTML 模板)

mikel阅读(869)

[转载].NET模板引擎 EFPlatform.CodeGenerator (代码生成器 静态页生成 生成HTML 模板) – Eric’s Bloggy – 博客园.

这个东东是去年我看着ASP:标记突发奇想花4天 时间设计编写的类库, 原名叫 HtmlGenerator, 最近发现PHP和JAVA有很多类似的项目, 但是都设计的很渣(不同意的表打我@_@), 于是把 HtmlGenerator 重构了一下, 改叫 CodeGenerator. 配合我的数据库迁移工具和数据库实体类生成品….. 好像跑题了 -____-

CodeGenerator 的特点:
1. 标记简结实用, 所有网页美工都能在一分钟内掌握. 而且不与HTML标准冲突, 模板页可用任何WYSIWYG工具编辑, 和编辑普通HTML网完全相同.
2. 标记只与表示层相关, 不包括任何业务逻辑, 丝毫不影响你应用多层结构.
3. 标记到后台被解析成了生成器对象, 完全面向对象, 不像绝大多数生成器要死嗑字符串.
4. 生成器对象使用DataSource属性取得数据, DataSource可以为  简单值类型(如 int, DateTIme), 也可以为简单数组(如 decimal[], string[]), 还可以为ADO.NET数据集(如DataTable), 甚至单个对象实体或对象集合或列表(如 SomeClassCollection, List<SomeClass>), 所有数据源类型通吃! 哈哈, 比ASP.NET带的数据控件支持的类型还多.
5. 标记的Name直接与数据源的列名ColumnName或属性名PropertyName, 好处不言而喻了吧.
6. 说到这里好了, 留一手先. 呵呵

演示程序下载地址: http://files.cnblogs.com/ericfine/Demo.rar
EFPlatform.CodeGenerator 源代码下载地址: http://files.cnblogs.com/ericfine/EFPlatform.CodeGenerator.rar (过时了)
EFPlatform.TemplateEngine 源代码下载地址: http://files.cnblogs.com/ericfine/EFPlatform.TemplateEngine.rar (其实就是CodeGenerator的优化版:))

应用项目:
http://portray.mz99.com/
http://music.mz99.com/
http://joke.mz99.com/
http://www.mcuol.com/

应用流程:

Default.aspx.cs:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.IO;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using EFPlatform.TemplateEngine;

public partial class _Default : Page
{
private string outputPath;
private string categoryFileName;
private string productFileName;
private static DbProviderFactory dbFactory;
private DbConnection connection;

protected void Page_Load(object sender, EventArgs e)
{
outputPath
= Server.MapPath(./);
categoryFileName
= string.Format(@”{0}\Template\Category.html, outputPath);
productFileName
= string.Format(@”{0}\Template\Product.html, outputPath);
string currentConnection = ConfigurationManager.AppSettings[Connection];
ConnectionStringSettings css
= ConfigurationManager.ConnectionStrings[currentConnection];
this.GetConnection(css);
}


private void GenerateCategory()
{
string template = Helper.ReadTextFile(categoryFileName);
Generator gen
= new Generator(template);
gen.ParseTemplate();
Region rgnTitle
= gen.GetRegion(Title);
Region rgnCategory
= gen.GetRegion(Category);
Region rgnProducts
= gen.GetRegion(Products);
Region rgnNavigator
= gen.GetRegion(Navigator);

if(rgnTitle == null || rgnCategory == null || rgnProducts == null || rgnNavigator == null)
{
Response.Write(
Missing region.);
return;
}


int categoryId;
string outputFileName;
DataView dvCategory
= this.GetCategoryTable().DefaultView;
Pager pgrCategory
= new Pager(1, dvCategory.Count);

for(int i = 0; i < pgrCategory.PageCount; i++)
{
rgnTitle.DataSource
= (string)dvCategory[i][CategoryName]; //Use a string as data source
rgnCategory.DataSource = dvCategory[i]; //Use a DataRowView object as data source
pgrCategory.CurrentPage = i + 1;
rgnNavigator.DataSource
= pgrCategory; //Use a Pager object as data source
categoryId = (int)dvCategory[i][CategoryID];
rgnProducts.DataSource
= this.GetProductTable(categoryId); //Use a DataTable object as data souce
outputFileName = string.Format(@”{0}\Html\Category{1}.html, outputPath, categoryId);
Helper.WriteTextFile(outputFileName, gen.Generate());
}

}


private void GenerateProduct()
{
string template = Helper.ReadTextFile(productFileName);
Generator gen
= new Generator(template);
gen.ParseTemplate();
Region rgnTitle
= gen.GetRegion(Title);
Region rgnProduct
= gen.GetRegion(Product);
Region rgnNavigator
= gen.GetRegion(Navigator);

if(rgnTitle == null || rgnProduct == null || rgnNavigator == null)
{
Response.Write(
Missing region.);
return;
}


string outputFileName;
List
<Product> productList = this.GetProductList();
Pager pgrProduct
= new Pager(1, productList.Count);

for(int i = 0; i < pgrProduct.PageCount; i++)
{
rgnTitle.DataSource
= productList[i].CategoryName; //Use a string as data source
rgnProduct.DataSource = productList[i]; //Use a Product object as data source
pgrProduct.CurrentPage = i + 1;
rgnNavigator.DataSource
= pgrProduct; //Use a Pager object as data source
outputFileName = string.Format(@”{0}\Html\Product{1}.html, outputPath, productList[i].ProductID);
Helper.WriteTextFile(outputFileName, gen.Generate());
}

}


DataSourcePreparing

protected void Button1_Click(object sender, EventArgs e)
{
this.GenerateCategory();
}


protected void Button2_Click(object sender, EventArgs e)
{
this.GenerateProduct();
}

}

Web.config:

<?xml version=”1.0″?>
<configuration>
<system.web>
<compilation Debug=”true” />
<authentication mode=”Windows” />
<customErrors mode=”Off” defaultRedirect=”GenericErrorPage.htm”>
<error statusCode=”403″ redirect=”NoAccess.htm” />
<error statusCode=”404″ redirect=”FileNotFound.htm” />
</customErrors>
</system.web>
<connectionStrings>
<add name=”Access” providerName=”System.Data.OleDb” connectionString=”Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|Northwind.mdb”/>
<add name=”SQLExpress” providerName=”System.Data.SQLClient” connectionString=”Data Source=.\SQLExpress;Integrated Security=True;User Instance=True;Database=Northwind;AttachDBFilename=|DataDirectory|Northwind.mdf”/>
</connectionStrings>
<appSettings>
<add key=”Connection” value=”Access”/>
</appSettings>
</configuration>

[转载]模版引擎AderTemplate源代码分析笔记

mikel阅读(1123)

[转载]模版引擎AderTemplate源代码分析笔记 – kwklover – 博客园.

概述

AderTemplate是一个小型的模板引擎。无论是拿来直接使用还是用来研究模板引擎实现方式,都是一个不错的选择。本文尝试对其源代码做一些分析。

数据流程

AderTemplate的数据处理流程可以总结为:
模版文件 -> 模版分析 -> Template对象 -> 分析处理TemplateElement集合 -> 输出目标文本

模版语法

简化描述如下

1,变量替换 :如#variable#

2,循环语句 <ad:foreach var=”x” collection=”#values#” index=”i”></ad:foreach>

3,判断语句 <ad:if test=”#value#”> </ad:if>

详细请参看AderTemplate的相关说明.

模版分析

模版分析的过程可以分成两步:
1
,把模版文件分析成Token

2,对Token流进行分析,形成Element集合

所以,首先要了解TokenElement的结构与异同。

Token的类结构:

AderTemplateToken.JPG

TokenKind(Token的类型)的详细说明:

TokenKind

说明

EOF

结束符

Comment

注释,但在AderTemplate没看到具体实现

ID

这个比较难描述,下面会通过输出Token流来直官了解下ID

TextData

文本数据,不包含模版语法的独立文本区

TagStart

Tag的开始标记,<ad: 注:Tag是一个Element,后面详述

TagEnd

Tag的结束符, > ,注意与TagClose的区别

TagEndClose

Tag的自闭结束符 ,即 />

TagClose

Tag的结束标记 </ad: <ad:对应,所以与TagEnd很容易区别

TagEquals

Tag=符号。与Tag无关的=属于TextData,TagEquals

ExpStart

AderTemplate中,语法#v#被定义为Expression.#ExpStart

ExpEnd

AderTemplate中,语法#v#被定义为Expression.#ExpEnd

LParent

左括号 (

RParent

右括号 )

Dot

即一个点 .

Comma

逗号 ,

Integer

数字

StringStart

文本的开始符,即

StringEnd

文本的结束符,即

StringText

How to description ?

为了获得更直观的认识。下面尝试把一个简单的模版文件分析的Token流输出。
模版文件,我们暂且命名为templateFile

<ad:foreach collection=”#GetBookList(3)#” var=”book” index=”i”>

<tr>

<td>#i#</td>

<td>#book.BookName#</td>

<td>#book.BookCount#</td>

</tr>

</ad:foreach>

分析的代码:

static void Main(string[] args)

{

string data = string.Empty;

using (System.IO.StreamReader reader = new System.IO.StreamReader(templateFile))

{

data = reader.ReadToEnd();

}

//模版分析,形成Token

TemplateLexer lexer = new TemplateLexer(data);

Console.WriteLine(“=======开始输出TemplateLexer分析出Token=======”);

do

{

Token token = lexer.Next();

Console.WriteLine(“\tToken类型:{0}\t行列:({1}, {2})\t数据:{3}”, token.TokenKind.ToString(), token.Line, token.Col

, token.Data.Replace(“\r\n”, “”));

if (token.TokenKind == TokenKind.EOF)

break;

} while (true);

Console.WriteLine(“=======结束输出TemplateLexer分析出Token=======\r\n”);

}

输出结果:

输出结果

对照表格的说明和输出结果。可以把Token理解为特定类型的文本区,即把模版文件按照TokenKind定义的类型分解成多个文本区(Token),所以分析模版文件形成的Token(集合)是一种相对线性的结构。是对模版文件的初步分析与处理。

下面继续说说Element的类体系:

AderTemplateElement.JPG

上述类关系图中可以看到Element主要有四个继承体系。

但每个类表达的意义是什么呢?

下面用一个表格做一个简单的说明。

类名

指代及其含义

Element

各种Element的基类

Text

文本类的Element , TokenKindTextDataToken相似的概念

TagClose

Tag结束符号

Tag

Tag是一个比较广的概念。上述的模版可以表示一个foreach tag

Expression

AderTemplate把语法类似#values#定义为一个Expression

FCall

Expression子类之一,表示该Expression是一个外部函数(Function)

FieldAccess

Expression子类之一,可以近似的理解成字段访问”,#book.BookName#

Name

Expression子类之一,可以近似的理解成外部变量”,#i#

StringLiteral

Expression子类之一,可以近似的理解成非数字型变量

IntLiteral

Expression子类之一,可以近似的理解成数字型变量

StringExpression

Expression子类之一,但其表示的是Expression的集合封装类

为了获得更直观的了解。我们可以通过分析上述的模版文件输出的Element来理解。因为此部分代码比较多。所以不列出了。后面会提供包含本文提到的所有代码的example.

这里需要说明的是Tag ElementTag是一个复合结构.Tag在不同的分析阶段,会些许不同。

如下面代码的(1)(2)分析出的Element是不同的。可通过输出结果来跟踪异同。

TemplateLexer lexer = new TemplateLexer(templateData);

TemplateParser parser = new TemplateParser(lexer);

List<Element> elems = parser.Parse(); –(1)

TagParser tagParser = new TagParser(elems);

elems = tagParser.CreateHierarchy(); –(2)

TokenElement异同分析:

在上面已经提到,Token是对模版文件的一种初步分析和分解。AderTemplate对模版文件的分析处理目标是把模版文件表达为一个Template对象。而Template对象的本质其实就是Element集合的封装类,也就是AderTemplate的对模版文件的分析其实是希望把模版文件在程序结构层面表达为一个Element的集合,从上面的ElementToken类关系图可以看出。Element远比Token复杂的多。如果直接从模版文件直接分析成Element集合结构的话,可以预见分析过程会变的相当复杂。所以AderTemplate引入了Token数据结构作为一种辅助分析手段。使得AderTemplate的对模版文件的分析过程变得更流畅和清晰了。程序的架构也变得相对优雅。所以我认为阅读AderTemplate的源代码,关键是理解TokenElement的结构和作用。特别是Element结构。因为它是AderTemplate的基础结构。即解决了在程序层面用什么结构来表达模版文件的问题。

模版分析涉及到的处理类:

TemplateLexer : 从模版文件中分析成Token流。

TemplateParser: 基于Token流分析成初步的Element集合结构。

TagParser : 把TemplateParser分析出的Element集合做最后的优化处理。

Template(模版)对象:

AderTemplate中。我们可以简单地认为一个Template就是经过模版分析后形成的最终的Element集合的封装体。

Template(模版)处理:

TemplateElement集合进行分析。生成目标文本。

此过程涉及到的技术其实不复杂,就是反射和委托,所以本文不打算详细分析此过程。否则篇幅过大。而且没什么意义。在后面我会提供一个模拟AderTemplateElement分析处理到生成目标文本过程的例字。

相关例子

为了辅助理解AderTemplate的整个处理过程。我写了几个例子。
1
,输出Token的结构信息。对比模版文件辅助理解Token的结构

2,输出从Token流中分析出的Element集合的结构信息。以辅助理解Element的结构。

3,模拟从Element集合中生成目标文本的过程。辅助理解从Element集合到生成目标文本的整个处理流程和过程。这个过程是相对简单的。从这个角度来看,AderTemplateElement集合来表达模版文件是一个比较优秀的设计。

至于如何从模版文件分析成Element集合(Template对象)则是一个相对复杂的过程了。最好的解释还是源代码本身了。所以就不在此不做更多的分析了。

总结

AderTemplate是一个小型的模版引擎。功能并不算强大。但基本功能已经具备。从应用的角度来说,可能未必是一个最好的选择。但想学习模版引擎的开发。确是一个很不错的选择。我个人认为其架构还是比较优秀的。而且数据处理流程也很清晰。

当写作本文的时候,我阅读AderTemplate的源代码已有好些天了。依然感觉似懂非懂。到是写本文让我把思路给理清了。所以有时候。我觉得。写笔记也是一种学习。:)


AderTemplate与相关例子代码下载

[转载]推荐一个模板引擎templateengine

mikel阅读(1063)

[转载]推荐一个模板引擎 – templateengine – 流浪的风(专注于.NET2.0技术) – 博客园.

一直都在使用StringTemplate模板引擎,虽然使用方便,但是功能实在太弱太弱,准备放弃使用StringTemplate。刚好碰巧发现了另外一个开源的模板引擎,就是今天要介绍的TemplateEngine 2,功能非常强大。

先看看他的语法吧

Thank You for your order #order.billFirstName# #order.billLastName#.
<br>
Your Order Total is: #format(order.total, “C”)#
<br>
<ad:if test=”#order.shipcountry isnot “US”#”>
Your order will arrive in 2-3 weeks
<ad:else>
Your order will arrive in 5-7 days
</ad:if>

TemplateEngine2.0 主要功能

Template Engine主要是两个类在使用:Template 和TemplateManager,Template 是一个基本模板, TemplateManager是扩展的模板

用Template或TemplateManager 是非常容易创建模板的

Template template = Template.FromString(string name, string data)
Template template
= Template.FromFile(string name, string filename)

用TemplateManager

TemplateManager mngr = new TemplateManager(Template template);

或者

TemplateManager mngr = TemplateManager.FromFile(string filename);
TemplateManager mngr
= TemplateManager.FromString(string template);

当调用FromString 方法时,string是模板的代码。这个方法能动态的加载文本到template中。FormFile是指调用的模板文件。

TemplateManager有个属性SetValue可以用来设置模板的变量和变量的内容

mngr.SetValue(xx, xxxxxxxxxxxxxx);

输出模板

Response.Write(mngr.Process());

这一切都非常的简单。

函数列表

equals(obj1.obj2) 比较两个变量是否一样,返回bool型
notequals(obj1,obj2)比较两个变量是否不一样,返回 bool型 也可以 not(equals(obj1,obj2))
iseven(num) 测试是否是偶数
isodd(num)测试是否奇数
isempty(string) 测试字符串是否为0
isnotempty(string) 测试字符串是否大于0
isnumber(num) 测试是否为整型
toupper(string) 转化成大写
tolower(string) 转化成小写
isdefined(varname) 测试变量是否有定义
ifdefined(varname, value) 返回 value 如果varname 是有定义的,
特别有用:#ifdefined(“name”, name)# 将输出value 如果varname没有定义,其它情况不输出
len(string)返回字符串长度
tolist(collection, property, delim) 简单的输出列表

例1:

ArrayList list = new ArrayList();
list.Add(
one);
list.Add(
two);
list.Add(
three);
template.SetValue(
mylist, list);

你的模板内容:
#toList(mylist, ” & “)#
输出:one & two & three

例2:

list.Add(new Customer(Tom, Whatever));
list.Add(
new Customer(Henry, III));
list.Add(
new Customer(Tom, Jackson));
template.SetValue(
mylist, list);

模板内容:
#toList(mylist, “firstName”, “,”)#
输出:Tom,Henry,Tom

函数列表

snull(obj)
测试是否为空
not(boolvalue) 返回not(!)的bool型
iif(booleanExpression, iftruevalue, iffalsevalue) 如果booleanExpression为真,输出iftruevalue,否则输出iffalsevalue
format(object, formatstring)格式化字符串,相当于ToString(formatstring)

例子:

#format(total, “C”)#
will output: $1,208.45

函数列表

trim(string)
去除前后空格
filter(collection, booleanproperty) 返回一个新的组所有符合booleanproperty要求的
gt(obj1,obj2) 如果 obj1 > obj2 返回为真,否则为false
lt(obj1,obj2) 如果 obj1 < obj2 返回为真,否则为false
compare(obj1, obj2) 如果 obj1<obj2 返回-1 obj1=obj2 返回0 如果obj1>obj2 返回1
or(bool1,bool2) 有一个为真返回就是true
and(bool1, bool2) 两个都为真时返回true
comparenocase(string1, string2) 相等时返回为true 否则为false
stripnewlines(string) 返回 所有\r\n 用space 代替的字符串
typeof(object) 返回object 类型,如typeof(“abcd234”) 返回 “string” typeof(3) 返回 int
cint(value) 返回整数 等同于Convert.toInt32()
cdouble(value) 返回双精度
cdate(value) 返回一个datetime 型
createtypereference(type) 引用一个静态的类型,但格式必须是<ad:set tag

例:
#createtypereference(“System.Math”).Round(3.39789)#
#createtypereference(“System.Math”).PI#
or
<ad:set name=”MyMath” value=”#createtypereference(“System.Math”)#” />
#MyMath.Round(3.3)#
#MyMath.PI#

还支持C#内置的一些方法,如还可以这样使用
#xx.Length#

还有很多你意想不到的效果,赶紧来试用吧。

官方地址:http://www.adersoftware.com/index.cfm?page=templateEngine2

[转载]使用vbs借助mspaint.exe实现图片无损压缩

mikel阅读(1041)

[转载]使用vbs借助mspaint.exe实现图片无损压缩_勇敢的心_百度空间.

有的时候四处寻觅的好东西可能就在眼前!由于想要换一个网站的Banner,为了能把图片压缩到极致,于是四处寻找好用的图片压缩工具,试用了一些软件后 才发现,原来最好的图片压缩工具其实就是微软winXP系统下自带的MSpaint.exe(画图程序)。

美中不足的是Mspaint无批量处理功能,只能逐个压缩,这给我们需要处理大量图片的朋友带来了不小困难。笔者就此问题,在网上也找了若干的解决方案,发现借助vbs可以很好地解决此问题;参照其他网友的实现,笔者又做了少许加工,完成了如下vbs脚本:

‘**********************************************
‘*使用说明,选择源文件夹和目标文件夹
‘*不支持中文路径和文件名
‘**********************************************

Dim FileName,fs,srcFolder,disFolder
Const PICTURE_TYPE = “.jpg.gif.jpeg”
Const MY_COMPUTER = &H11&
Const WINDOW_HANDLE = 0
Const OPTIONS = 0
Set objShell = CreateObject(“Shell.Application”)
Set objFolder = objShell.Namespace(My_Computer)
Set objFolderItem = objFolder.Self
srcFolder = objFolderItem.Path

‘**********************************************
‘*选择源文件夹
‘**********************************************
Set objFolder = objShell.BrowseForFolder(WINDOW_HANDLE, “选择源文件夹:”, OPTIONS, “”)
If objFolder Is Nothing Then
msgbox “您没有选择任何有效目录!”
wscript.quit
End If

Set objFolderItem = objFolder.Self
srcFolder = objFolderItem.Path

If HasChinese(srcFolder) Then
msgbox “不支持中文路径,请重新选择!”
wscript.quit
End If

‘**********************************************
‘*选择输出文件夹
‘**********************************************
Set objFolder = objShell.BrowseForFolder(WINDOW_HANDLE, “选择输出文件夹:”, OPTIONS, “”)
If objFolder Is Nothing Then
msgbox “您没有选择任何有效目录!”
wscript.quit
End If

Set objFolderItem = objFolder.Self
disFolder = objFolderItem.Path

If HasChinese(disFolder) Then
msgbox “不支持中文路径,请重新选择!”
wscript.quit
End If
Call main

Sub main
On Error Resume Next
set WshShell = WScript.CreateObject(“WScript.Shell”)
WshShell.Run “C:\WINDOWS\system32\mspaint.exe”
WScript.Sleep 1000
WshShell.AppActivate “paint”
WScript.Sleep 1000

Dim objfso,objfolder1
Set objfso = CreateObject(“scripting.filesystemobject”)
Set objfolder1 = objfso.getfolder(srcFolder)

For Each objfile In objfolder1.files
If AllowExtension(LCase(objfso.GetExtensionName(objfile))) =true Then

WshShell.Sendkeys “^o”
WScript.Sleep 100
path=TrimLast(srcFolder,”\”) +”\”+ objfile.name
WshShell.SendKeys path
WScript.Sleep 100
WshShell.SendKeys “~”
WScript.Sleep 200
‘另存为
WshShell.Sendkeys “%f”
WshShell.Sendkeys “a”
WScript.Sleep 100
WshShell.Sendkeys “{BS}”
path=TrimLast(disFolder,”\”) +”\”+ objfile.name
WshShell.SendKeys path
WScript.Sleep 100
WshShell.Sendkeys “~”
WScript.Sleep 200

End If
Next

End Sub
‘**********************************************
‘*检查是否含有中文
‘**********************************************
Function HasChinese(sFileName)
Set regEx = New RegExp
regEx.Pattern = “^[\x00-\xff]*$”
regEx.IgnoreCase = True
HasChinese = Not regEx.test(sFileName)
End Function

‘**********************************************
‘*检查是否允许的扩展名
‘**********************************************
Function AllowExtension(sFileName)
If IsNull(sFileName) Or sFileName = “” Then
AllowExtension = False
else
AllowExtension = InStr(PICTURE_TYPE,sFileName)>0
End If
End Function

‘****
‘* Remove “chr” (if it exists) from end of “str”.
‘****
Function TrimLast(str,chr)
TrimLast = str
If Right(str,1) = chr Then
TrimLast = Left(str,Len(str)-1)
End If
End Function

直接下载:批量压缩图片.vbs

[转载].net c# 一个简单但是功能强大动态模板引擎

mikel阅读(989)

[转载].net c# 一个简单但是功能强大动态模板引擎 – DotNet Software Project – 博客园.

net C# 一个简单但是功能强大动态模板引擎(一) 收藏
注意:欢迎转载,但是请注明出处.

流行的模板引擎有很多,譬如Velocity.但是他的模板语言比较简,复杂的功能比较难实现,而且编辑模板比较麻烦容易出错.

但是利用UserControl就可以实现功能强大的一个动态模板引擎,编辑的模板的时候跟编辑一个用户控件没有区别,并且支持任何.net语言譬如C#.

下面就是代码:

view plaincopy to clipboardprint?
using System;
using System.Collections.Generic;
using System.Text;

namespace Template
{
public class TemplateBody : System.Web.UI.UserControl
{
private IDictionary<string, object> _context = new Dictionary<string, object>();
protected void Page_Load(object sender, EventArgs e)
{

}

public IDictionary<string, object> ViewData
{
get { return _context; }
}
}
}
using System;
using System.Collections.Generic;
using System.Text;

namespace Template
{
public class TemplateBody : System.Web.UI.UserControl
{
private IDictionary<string, object> _context = new Dictionary<string, object>();
protected void Page_Load(object sender, EventArgs e)
{

}

public IDictionary<string, object> ViewData
{
get { return _context; }
}
}
}

TemplateBody类基本没什么代码只是声明了一个ViewData属性,该属性用于向模板添加数据由模板来获取并展示.

view plaincopy to clipboardprint?
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Web.UI;

namespace Template
{
public class TemplateEngine:IDisposable
{
private UserControl _uc;
private TemplateBody _tpl;

public TemplateEngine()
{
_uc = new UserControl();
}
/// <summary>
/// 加载一个模板
/// </summary>
/// <param name=”path”>这个路径为相对路径</param>
public void Load(string path)
{
_tpl = _uc.LoadControl(path) as TemplateBody;
if (_tpl == null)
{
throw (new ArgumentException(path));
}
}
/// <summary>
/// 控制展示
/// </summary>
/// <returns>返回生成的字符串</returns>
public string Render()
{
TextWriter tw = new StringWriter();
Render(tw);
return tw.ToString();
}
/// <summary>
/// 展示模板
/// </summary>
/// <param name=”writer”>TextWriter对象,可以传Response.Output</param>
public void Render(TextWriter writer)
{
HtmlTextWriter htw = new HtmlTextWriter(writer);
_tpl.RenderControl(htw);
}
/// <summary>
/// 增加一个显示数据的上下文
/// </summary>
/// <param name=”key”></param>
/// <param name=”obj”></param>
public void AddContext(string key, object obj)
{
_tpl.ViewData.Add(key, obj);
}

public object this[string key]
{
get{
object ret;
_tpl.ViewData.TryGetValue(key, out ret);
return ret;
}
set { AddContext(key, value); }
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (disposing)
{
_uc.Dispose();
}

}
~TemplateEngine()
{
Dispose(false);
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Web.UI;

namespace Template
{
public class TemplateEngine:IDisposable
{
private UserControl _uc;
private TemplateBody _tpl;

public TemplateEngine()
{
_uc = new UserControl();
}
/// <summary>
/// 加载一个模板
/// </summary>
/// <param name=”path”>这个路径为相对路径</param>
public void Load(string path)
{
_tpl = _uc.LoadControl(path) as TemplateBody;
if (_tpl == null)
{
throw (new ArgumentException(path));
}
}
/// <summary>
/// 控制展示
/// </summary>
/// <returns>返回生成的字符串</returns>
public string Render()
{
TextWriter tw = new StringWriter();
Render(tw);
return tw.ToString();
}
/// <summary>
/// 展示模板
/// </summary>
/// <param name=”writer”>TextWriter对象,可以传Response.Output</param>
public void Render(TextWriter writer)
{
HtmlTextWriter htw = new HtmlTextWriter(writer);
_tpl.RenderControl(htw);
}
/// <summary>
/// 增加一个显示数据的上下文
/// </summary>
/// <param name=”key”></param>
/// <param name=”obj”></param>
public void AddContext(string key, object obj)
{
_tpl.ViewData.Add(key, obj);
}

public object this[string key]
{
get{
object ret;
_tpl.ViewData.TryGetValue(key, out ret);
return ret;
}
set { AddContext(key, value); }
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (disposing)
{
_uc.Dispose();
}

}
~TemplateEngine()
{
Dispose(false);
}
}
}

TemplateEngine 是负责显示的类,核心代码也就是调用了RenderControl函数.

下面是具体使用:

1.创建一个web工程,注意其他工程可能不支持.

2.添加默认页面Default.aspx

3.根目录添加一个TemplateTest.ascx的模板文件 扩展名默认是ascx,如果需要更改别的扩展名的话需要在web.config里在compilation节点增加下列代码:

view plaincopy to clipboardprint?
<buildProviders>
<add extension=”.view” type=”System.Web.Compilation.UserControlBuildProvider”/>
</buildProviders>
<buildProviders>
<add extension=”.view” type=”System.Web.Compilation.UserControlBuildProvider”/>
</buildProviders>

4.直接运行就可以.

Default.aspx代码:

view plaincopy to clipboardprint?
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;

using Template;

namespace TemplateDemo
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
TemplateEngine te = new TemplateEngine();
te.Load(“TemplateTest.ascx”);
te.AddContext(“Text”, “Super Man”);
te.Render(Response.Output);
}
}
}
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;

using Template;

namespace TemplateDemo
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
TemplateEngine te = new TemplateEngine();
te.Load(“TemplateTest.ascx”);
te.AddContext(“Text”, “Super Man”);
te.Render(Response.Output);
}
}
}
TemplateTest.ascx代码:

view plaincopy to clipboardprint?
<%@ Control Language=”C#” AutoEventWireup=”true” CodeBehind=”TemplateBody.cs” Inherits=”Template.TemplateBody” %>

<% for (int i = 0; i < 10; i++){
%>
<%=ViewData[“Text”]%>
<% }%>
<%@ Control Language=”C#” AutoEventWireup=”true” CodeBehind=”TemplateBody.cs” Inherits=”Template.TemplateBody” %>
<% for (int i = 0; i < 10; i++){
%>
<%=ViewData[“Text”]%>
<% }%>

记住,模板必须要加这个头:

<%@ Control Language=”C#” AutoEventWireup=”true” CodeBehind=”TemplateBody.cs” Inherits=”Template.TemplateBody” %>

下面是显示结果:

Super Man Super Man Super Man Super Man Super Man Super Man Super Man Super Man Super Man Super Man

需要改进的地方:

目前模板只支持相对路径,因为.net他对文件进行缓存处理,这样运行一次模板后即编译模板并进行缓存,如果文件被更改将重新编译,提高效率.

如果您需要从数据库或者从一个Stream里加载模板的话,需要重写VirtualPathProvide,并且重写判断模板被更改的函数 CacheDependency,还有获取虚拟文件的函数GetFile, 这样很容易实现从任何地方获取模板.如果您有兴趣可以进行改进,完善.

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/baoaya/archive/2009/07/27/4384178.aspx

[转载]jtemplates jquery模板引擎使用

mikel阅读(1030)

[转载]jtemplates jquery模板引擎使用 – 蛰穴灵异 技术博客 – 博客园.

工作中需要在前端将json数据源绑定到页面html代码中,之前一直用组合html代码的形式编写,不太容易维护。Ms ajax4.0据说有一套模板引擎,只有预览版,而且看来比较复杂,不太能马上用。在网上找到一个jtemplates的js模板引擎,感觉还可以,适合 用来在页面上动态绑定数据。

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<html xmlns=”http://www.w3.org/1999/xhtml”>
<head runat=”server”>
<title>Index</title>
<script src=”/Scripts/JQuery-1.3.2.js” type=”text/JavaScript></script>
<script src=”/Scripts/plugins/JQuery-jtemplates.js” type=”text/JavaScript></script>

<script type=”text/JavaScript>
$(document).ready(
function() {
var data = [{ name1: 测试信息,id:12 },{ name1: 测试信息,id:12 }];
$(
#result).setTemplateElement(template1);
$(
#result).processTemplate(data);
});
</script>
</head>
<body>
<div>
<textarea id=”template1″ style=”display: none;”>
<table>
<thead>
<tr>
<td colspan=”2″>
fdaasfd
</td>
</tr>
</thead>
<tbody>
{#foreach $T as r}
<tr>
<td>
name:{$T.r.name1}
</td>
<td>
id:{$T.r.id}
</td>
</tr>
{#/for}
</tbody>
<tfoot>
</tfoot>
</table>
</textarea>
</div>

<div id=”result”></div>
</body>
</html>

[转载]从模板引擎到模板引擎-使用aspx页面作为模板引擎的一种实现

mikel阅读(939)

[转载]从模板引擎到模板引擎-使用aspx页面作为模板引擎的一种实现 – 懒人居 – Coding for fun – 博客园.

此文完全有感而发,在网上看到很多介绍各类模板引擎的文章,但是我却越来越感觉到很多时候我们所做的事
情是在舍近求远。
什么是模板引擎?说白了就是能够自动替换占位符的替换引擎。原理上也就是两个步骤,找到-》替换。但是替换不是简单的替换,包括:

  • 简单变量替换();
  • 复杂变量替换();
  • 对象变量替换();
  • 键值类型替换();
  • 自定义集合替换();
  • 同时显示多个变量();
  • 模板调用();
  • 给调用模板传参数();
  • 值模板();
  • 简单循环();
  • 交差循环显示();
  • 通过模板交差循环();
  • 条件判断();
  • 从文件中创建模板();

常用的模板引擎,StringTemplate和Velocity差不多都是按照这种模式来设计的。但是这样子真的就是我们需要的吗?我们拿一段典型的StringTemplate的模板代码来看看:

首先是在模板文件中:

姓名:$User.Name$ 年龄:$User.Value$

然后是在代码里:

User us = new User();
us.Name
= xxsssx;
us.Value
=80;
StringTemplate st
= new StringTemplate($User.Name$,$User.Value$);
st.SetAttribute(
User, us);
Console.WriteLine(st.ToString());

那么我们来和ASP.NET本身的代码做一个比较
aspx页面
<asp:Label id=”Name” runat=”Server” /><asp:Label id=”Mail” runat=”Server” />
aspx.cs文件

User us = new User();
us.Name
= xxsssx;
us.Value
=80;
Name.Text
=us.Name;
Mail.Text
=us.Value

是不是有点感觉用了模板引擎跟脱了裤子放屁没有什么区别?
那么我们为什么要用模板引擎?是真的需要还是赶时髦?
就我个人来看,模板引擎是需要的。视图和控制的分离是必要的,但是我们要分清楚什么是视图,什么是控制。是不是视图就不能包含代码和逻辑?肯定答案是否定 的,因为就算用了StringTemplate,你还是需要给它填充数据,而给它填充数据的代码仍然是属于视图而不是控制或者有人干脆就当作了模型。那么 既然这样,为什么我们就不能用aspx页面本身来作为模板呢?它可以判断,可以循环,可以分支,可以使用现成的控件。比如

<%
if(Helper.IsLogin()){
User u=Helper.GetLoginedUser();
%>
你的姓名是:<%=u.Name%>
<%}%>

我们把这种方法和StringTemplate的方法来比较一下。

aspx模板    StringTemplate
ASP.NET控件    支持           不支持
复杂显示逻辑    支持          支持(未经测试所有可能性)
编辑器支持       支持          不支持
编译检测          支持           不支持

现在可能有个疑问了,aspx的页面怎么作为模板来被调用呢?以访问就直接看到了没办法生成静态页面啊?
其实解决的方法很简单,只需要Server.Execute()就可以将执行的aspx页面运行的结果以一个TextWriter返回。这样子要怎么处理还不是看我们怎么高兴啦。哈哈

记得之前有人提过类似方法,不过有人可能会质疑:那不是还有美工也会ASP.NET?其实回过头一想,如果你告诉美工<%=u.Name%> 就是代表用户的姓名,那么也不会需要美工学习全套的asp.net,而其实就美工来说,如果学习简单实用asp.net都有困难,那么学习 StringTemplate的全套模板语法和灵活掌握使用方法可能更加让人郁闷。如果这些工作都给程序员来完成的话,那么已经是程序员熟练掌握的C#语 法是不是更加的平易近人呢?况且还可以智能感知菜单和编译检测支持。所以我觉得在大多数情况下这种方法是比较爽的一种方式。

[转载]用Visual Studio实践敏捷测试(三)下

mikel阅读(1172)

[转载]用Visual Studio实践敏捷测试(三)下 – 服务世界 开发未来 – 博客园.

自动化测试的实现

编 写自动化测试也许对很多测试人员来说比较陌生。所幸的是Visual Studio中为实现自动化测试提供了一系列的工具,单元测试(Unit Test)、编码UI测试(Coded UI Test)、压力测试(Stress Test)、网页性能测试(Web Performance Test)、数据库单元测试(Database Unit Test)等等,让实现自动化测试变得轻松。这里我想着重介绍2种最基本的,也是在我们的产品开发中最常用的测试:单元测试和编码UI测试。

1. 单元测试

单元测试是Visual Studio中最基本、应用最广泛的一种测试。通常开发人员可以选择为一个方法或是一个部件创建单元测试,来保证其逻辑正确。

要 在Visual Studio中创建单元测试,可以在源代码的上下文菜单中选择“创建单元测试”,并在弹出的窗口中选择需要为其创建单元测试的方法(如图一、图二所示)。 这样Visual Studio就会自动创建出一系列单元测试的代码框架,以及针对private/internal等无法直接调用的方法的访问器(Accessor),用 户只需修改或添加具体测试逻辑即可。访问器会随着源代码的每一次编译自动更新,为用户节省了不少麻烦。当然,用户也可以使用单元测试向导创建,或是直接添 加一个单元测试(测试->新建测试)文件再自行添加逻辑代码。

clip_image002

图一 创建单元测试

clip_image004

图二 创建单元测试对话框

单 元测试通常以[TestClass]属性来表示一个测试类,在测试类中使用5种不同的属性标示方法: [ClassInitialize]、[TestInitialize]、[TestMethod]、[TestCleanup]、[ClassCleanup]。 一个测试类中可包含多个测试方法(Test Method),但是仅可以有一个类初始化方法(Class Initialize)、一个测试初始化方法(Test Initialize)、一个测试清理方法(Test Method)、一个类清理方法(Class Cleanup)。在测试运行时,类的初始化会被首先调用,然后在运行每一个测试方法之前运行测试初始化,之后运行测试清理,在测试方法运行结束后,类清 理方法将被运行。除测试方法外,其他的辅助方法都不是必须的。大家可以根据实际需要来安排代码逻辑。

成 功编译后,所有测试方法都会在测试视图(Test View)窗口中列出,在该窗口中还可以对测试方法进行过滤、查询和排序,选择一个或多个测试方法后,可以运行或调试测试用例。测试的结果(是否通过)会 显示在测试结果(Test Result)窗口中,双击任意一条测试结果都会打开具体的测试结果日志以获取更详细的信息,如图三所示。单元测试还可以通过直接在测试方法代码中右键选 择“运行测试”,或是在命令行中直接执行mstest命令来运行。

clip_image006

图三 测试视图和测试结果

此外,单元测试工具不仅可以用作单元测试的目的,也可以作为一种载体,来实现验收测试或是功能测试。我们在实践中大量利用了Visual Studio对单元测试的管理、运行、日志等功能,通过在测试代码中实现验收测试、功能测试的具体逻辑来完成各种不同类型的测试。

2. 编码UI测试

虽 然单元测试框架适用于各种不同的测试,不过其本身却没有提供太多对测试代码实现上的支持。对于自动化测试中常常令人无从下手的UI操作的自动 化,Visual Studio 2010中添加了一种新的测试类型——编码UI测试,以帮助用户克服这一难题。编码UI测试是一种能轻松上手,迅速创建出UI测试的框架。

一 种最简单的创建UI测试的方法是直接从手动测试入手。如果此前我们曾在Test Manager中创建了测试用例,并曾在手动执行时录制过其测试步骤,那么我们就可以直接将录制的步骤转化为编码UI测试的代码。在Visual Studio中选择创建一个编码UI测试后,会跳出一个对话框询问用户是使用已有的操作录制还是重新录制,选择第二项“Use an existing action recording(使用现有操作录制)”后即可通过查询测试用例工作项将相应的测试转化为自动化测试代码(见图四)。

clip_image008

图四 创建编码UI测试

如 果之前没有录制过测试步骤,或是想重新创建测试的话,可以在图四对话框中选择第一项“Record actions, edit UI map or add assertions(录制操作、编辑 UI 映射或添加断言)”,这样编码UI测试生成器(Coded UI Test Builder)就会出现。在编码UI测试生成器中,用户可以自由选择为测试录制操作步骤(图五)、手动添加某些UI控件或是断言(图六),然后就可以为 这些内容生成代码。这一过程可以通过在代码的上下文菜单中选择“Generate Code for Coded UI Test(为编码UI测试生成代码) ”反复执行,需要提醒用户的一点是每一次所有的代码都将被重新生成,所以手动修改生成的代码是没有意义的,除非此后不再借助编码UI测试生成器生成代码。

clip_image009

图五 编码UI测试生成器——录制

clip_image011

图六 编码UI测试生成器——添加UI控件和断言

此外,用户还可以不借助Visual Studio提供的这些工具,直接利用编码UI测试提供的API(Microsoft.VisualStudio.QualityTools.CodedUITestFramework等)编写代码,实现UI自动化测试。

编码UI测试的运行方法、运行结果等都与单元测试类似,此处不再赘述。

这 里要强调的是自动生成的自动化UI测试并不能解决UI测试固有的不稳定的问题。尤其是这种编码UI测试是通过UI控件之间的包含关系来寻找控件并对其执行 操作的,就导致了如果运行测试时UI排列与录制时不尽相同时,测试可能无法正确运行。确保运行时UI环境的一致、在各操作步骤之间添加对UI控件状态的判 断、在生成的代码的基础上编写自己的代码是能提高编码UI测试稳定性的一些方法。

3. 其他类型测试

除 了上述两种常用的测试类型之外,Visual Studio针对不同类型的测试以及测试对象,提供了各种其他的测试工具。例如,网页性能测试通过记录用户每一步操作选择的地址和发送的信息来实现网页测 试的自动化;负载测试帮助用户模拟多用户各种不同测试环境下的负载;数据库单元测试提供了直接针对数据库的测试支持。这里我就不再一一详细介绍了,有兴趣 的读者可以自己在MSDN上查询使用方法或者直接试用这些功能。

自动化测试的管理

对 于手动测试,测试用例工作项已经能很好的描述测试的内容以及记录测试的结果。而自动化测试的不同之处在于其需要代码的支持。我们通常将测试代码和产品代码 一起保存在Team Foundation Server的源代码控制中,这样一方面便于代码的统一管理,另一方面让测试用例也能利用到TFS提供的版本控制、搁置集等功能。另外,我们还可以通过设 置TFS的测试用例工作项中包含的“关联的自动化测试”域的值将测试计划中的测试用例和实际的代码联系起来。

小结

在 这一篇中,我们讨论了手动测试和自动化测试各自的优势和局限性,两者互补和平衡能帮助测试人员更好的在敏捷开发的环境中完成测试任务。此外,我们还了解了 如何借助Visual Studio中提供的一些工具来实现并管理自动化测试。在介绍了自动化测试的方法和工具后,我将在下一篇中进一步为大家介绍如何计划和执行自动化的测试用 例。

林俊彦

软件测试开发工程师