你学会测试了吗(3):测试语法之断言介绍
前言
这个简短的系列一一讲解一下.Net下测试的相关知识,希望对初学者有所帮助。
在这个系列第一篇中从测试工具入手推荐TestDriven.NET。官方下载TestDriven.NET-2.14.2190 Beta版(直接下载)和TestDriven.NET-2.13.2184正式版(直接下载)。第二篇中我选择了最为经典的NUnit单元测试框架来介绍TestDriven.NET所支持的一些重要的属性。这一篇继续使用这个框架,介绍单元测试的核心——断言Assert。
概述
在测试框架中,断言是单元测试的核心,我们在测试中要对其程序断言,如果某个断言失败,方法的调用不会返回值,并且会报告一个错误。如果一个测试包含多个断言,那些紧跟失败断言的那些断言都不会执行,因此每个测试方法最好只有一个断言。
下面看看NUnit框架吧,来2张图:
断言
现在,我们使用经典的NUnit框架的最新版本,可以用三种方法来写我们的断言:
- 标准模式:过去比较经典的写法。这些方法在NUnit.Framework命名空间下的Assert类中以静态方法提供,对其不同的类型(字符串、集合、文件)NUnit.Framework框架还提供了字符串断言、集合断言、文件断言。
- 约束模式:全新的写法,使用Assert.That()方法来约束扩展所有的断言,使用新增NUnit.Framework.SyntaxHelpers命名空间下提供的方法调用NUnit.Framework.Constraints命名空间下的各种约束。
- 继承模式:只要把测试的类继承NUnit.Framework.AssertionHelper类,可以使用Expect()方法来替换Assert.That()方法。
在这里,我把Assert方法分为:同等断言、一致性断言、比较断言、类型断言、条件测试、工具方法这6类,另外还有字符串断言、集合断言、文件断言。
当然按照约束方式,也可以大致分为Equal Constraint、Same As Constraint、Condition Constraints、Comparison Constraints、Type Constraints、String Constraints、Collection Constraints、Property Constraint、Compound Constraints、Custom Constraints、List Mapper等。
下面我依次介绍一下断言,使用三种方式来写自己的断言。
- Equality Asserts(同等断言)
- Identity Asserts(一致性断言)
- Comparison Asserts(比较断言)
- Type Asserts(类型断言)
- Condition tests(条件测试)
- Utility methods(工具方法)
- StringAssert(字符串断言)
- CollectionAssert(集合断言)
- FileAssert(文件断言)
- 其他约束
1.Equality Asserts、Equal Constraint
NUnit.Framework提供了Assert.AreEqual()、Assert.AreNotEqual()方法测试两个对象是否相等。方法支持相同类型,不同类型,多维数组,嵌套数组,集合类相互比较。
NUnit.Framework.AssertionHelper命名空间下提供了Is.EqualTo(object)方法使用同等约束条件来测试两个对象是否相等。当然了,我们继承NUnit.Framework.AssertionHelper类,可以使用Expect()方法来替换Assert.That()方法。下面给出这个例子:
注意:需要引用NUnit.Framework,NUnit.Framework.AssertionHelper,NUnit.Framework.Constraints命名空间,并把测试类继承AssertionHelper。
[Test]public voidEqualTest() {//定义一些变量vari3 =new int[] { 1, 2, 3 };vard3 =new double[] { 1.0, 2.0, 3.0 };variunequal =new int[] { 1, 3, 2 };vararray2x2 =new int[,] { { 1, 2 }, { 3, 4 } };vararray4 =new int[] { 1, 2, 3, 4 };varactual =new string[] {"HELLO","world"};varexpected =new string[] {"Hello","World"};//经典语法Assert.AreEqual(4, 2 + 2);Assert.AreEqual(i3, d3);Assert.AreNotEqual(5, 2 + 2);Assert.AreNotEqual(i3, iunequal);//约束语法Assert.That(2 + 2,Is.EqualTo(4));Assert.That(2 + 2 == 4);Assert.That(2 + 2,Is.Not.EqualTo(5));Assert.That(2 + 2 != 5);Assert.That(5.0,Is.EqualTo(5));Assert.That(2.1 + 1.2,Is.EqualTo(3.3).Within(.0005));Assert.That(double.PositiveInfinity,Is.EqualTo(double.PositiveInfinity));Assert.That(double.NaN,Is.EqualTo(double.NaN));Assert.That(i3,Is.EqualTo(d3));Assert.That(i3,Is.Not.EqualTo(iunequal));Assert.That(array2x2,Is.EqualTo(array4).AsCollection);//成功Assert.That("Hello!",Is.EqualTo("HELLO!").IgnoreCase);Assert.That(actual,Is.EqualTo(expected).IgnoreCase);//使用继承语法Expect(2 + 2, EqualTo(4)); Expect(2 + 2 == 4); Expect(i3, EqualTo(d3)); Expect(2 + 2, Not.EqualTo(5)); Expect(i3, Not.EqualTo(iunequal)); }
2.Identity Asserts、Same As Constraint
Assert.AreSame()和Assert.AreNotSame()方法测试两个对象是否是同一个对象。Assert.Contains方法用来测试在一个数组或列表里是否包含该对象。
NUnit.Framework.AssertionHelper命名空间下提供了Is.SameAs(object)方法使用Same As约束条件来测试两个对象是否是相同对象。使用继承也是如此。
[Test]public voidSameAsTest() {//定义变量varex1 =newException();varex2 = ex1;varex3 =newException();//约束语法Assert.That(ex2,Is.SameAs(ex1));Assert.That(ex3,Is.Not.SameAs(ex1));//使用继承语法Expect(ex2, SameAs(ex1)); Expect(ex3, Not.SameAs(ex1)); }
3.Comparison Asserts、Comparison Constraints
NUnit.Framework框架为我们提供了下面四个方法:
- Assert.Greater(x, y)方法用于测试一个对象是否大于另外一个对象。
- Assert.GreaterOrEqual(x, y)方法用于测试一个对象是否大于另外一个对象。
- Assert.Less( x, y )方法用于测试一个对象是否大于另外一个对象。
- Assert.LessOrEqual( x, y )方法用于测试一个对象是否大于另外一个对象。
NUnit.Framework.AssertionHelper命名空间下提供了Is.GreaterThan(IComparable)、Is.GreaterThanOrEqualTo(IComparable)、Is.AtLeast(IComparable)、 Is.LessThan(IComparable)、Is.LessThanOrEqualTo(IComparable)、Is.AtMost(IComparable)方法使用比较约束条件来测试比较两个对象。使用继承也是如此。
[Test]public voidComparisonTest() {//经典语法Assert.Greater(7, 3);Assert.GreaterOrEqual(7, 3);Assert.GreaterOrEqual(7, 7);Assert.Less(3, 7);Assert.LessOrEqual(3, 7);Assert.LessOrEqual(3, 3);//约束语法Assert.That(7,Is.GreaterThan(3));Assert.That(7,Is.GreaterThanOrEqualTo(3));Assert.That(7,Is.AtLeast(3));Assert.That(7,Is.GreaterThanOrEqualTo(7));Assert.That(7,Is.AtLeast(7));Assert.That(3,Is.LessThan(7));Assert.That(3,Is.LessThanOrEqualTo(7));Assert.That(3,Is.AtMost(7));Assert.That(3,Is.LessThanOrEqualTo(3));Assert.That(3,Is.AtMost(3));//使用继承语法Expect(7, GreaterThan(3)); Expect(7, GreaterThanOrEqualTo(3)); Expect(7, AtLeast(3)); Expect(7, GreaterThanOrEqualTo(7)); Expect(7, AtLeast(7)); Expect(3, LessThan(7)); Expect(3, LessThanOrEqualTo(7)); Expect(3, AtMost(7)); Expect(3, LessThanOrEqualTo(3)); Expect(3, AtMost(3)); }
4.Type Asserts、Type Constraints
Assert.IsAssignableFrom(),Assert.IsNotAssignableFrom(),Assert.IsInstanceOfType(),Assert.IsNotInstanceOfType()方法让我们可以构造一些关于对象类型的断言。
同理,NUnit.Framework.AssertionHelper命名空间下提供了Is.TypeOf(Type)、Is.InstanceOfType(Type)、Is.AssignableFrom(Type)方法使用类型约束条件来测试对象类型。使用继承也是如此。
[Test]public voidTypeTest() {//经典语法Assert.AreEqual(typeof(string),"Hello".GetType());Assert.AreEqual("System.String","Hello".GetType().FullName);Assert.AreNotEqual(typeof(int),"Hello".GetType());Assert.AreNotEqual("System.Int32","Hello".GetType().FullName);Assert.IsInstanceOfType(typeof(string),"Hello");Assert.IsNotInstanceOfType(typeof(string), 5);Assert.IsAssignableFrom(typeof(string),"Hello");Assert.IsNotAssignableFrom(typeof(string), 5);//约束语法Assert.That("Hello",Is.TypeOf(typeof(string)));Assert.That("Hello",Is.Not.TypeOf(typeof(int)));Assert.That("Hello",Is.InstanceOfType(typeof(string)));Assert.That(5,Is.Not.InstanceOfType(typeof(string)));Assert.That("Hello",Is.AssignableFrom(typeof(string)));Assert.That(5,Is.Not.AssignableFrom(typeof(string)));//使用继承语法Expect("Hello", TypeOf(typeof(string))); Expect("Hello", Not.TypeOf(typeof(int))); Expect("Hello", InstanceOfType(typeof(string))); Expect(5, Not.InstanceOfType(typeof(string))); Expect("Hello", AssignableFrom(typeof(string))); Expect(5, Not.AssignableFrom(typeof(string))); }
5.Condition Tests、Condition Constraints
测试框架提供了Assert.IsTrue,Assert.IsFalse,Assert.IsNaN,Assert.IsEmpty、Assert.IsNotEmpty,Assert.IsNull、Assert.IsNotNull方法分别用于测试两个对象是否正确,错误,非数字,(字符串或集合)空、非空,引用为空、引用不为空。
而NUnit.Framework.AssertionHelper命名空间也提供相类似的方法使用条件约束测试对象。直接看例子:
[Test]public voidConditionTest() {//定义变量doubled =double.NaN;//经典语法Assert.IsNull(null);Assert.IsNotNull(42);Assert.IsTrue(2 + 2 == 4);Assert.IsFalse(2 + 2 == 5);Assert.IsNaN(d);Assert.IsEmpty("");Assert.IsNotEmpty("Hello!");Assert.IsEmpty(new bool[0]);Assert.IsNotEmpty(new int[] { 1, 2, 3 });//约束语法Assert.That(null,Is.Null);Assert.That(42,Is.Not.Null);Assert.That(2 + 2 == 4,Is.True);Assert.That(2 + 2 == 4);Assert.That(2 + 2 == 5,Is.False);Assert.That(d,Is.NaN);Assert.That("",Is.Empty);Assert.That("Hello!",Is.Not.Empty);Assert.That(new bool[0],Is.Empty);Assert.That(new int[] { 1, 2, 3 },Is.Not.Empty);//使用继承语法Expect(null, Null); Expect(42, Not.Null); Expect(2 + 2 == 4, True); Expect(2 + 2 == 4); Expect(2 + 2 == 5, False); Expect(d, NaN); Expect("", Empty); Expect("Hello!", Not.Empty); Expect(new bool[0], Empty); Expect(new int[] { 1, 2, 3 }, Not.Empty); }
6.Utility methods
我们想对测试有自定义控制,测试框架提供了两个实用方法:Assert.Fail()和Assert.Ignore()方法。这对于开发你自己的特定项目的断言,例如用于判断中它非常有用。
Assert.Fail()方法表示这个测试方法是一个失败方法,这个失败是基于其他方法没有封装的测试。
Assert.Ignore()方法表示这个测试方法是一个忽略的方法,在测试过程中,将忽略这个测试。
7.StringAssert、String Constraints
StringAssert类提供许多AreEqualIgnoringCase、Contains、StartsWith、EndsWith、IsMatch、Equals、ReferenceEquals方法,这些方法在检查字符串值时是有用的。
而NUnit.Framework.AssertionHelper命名空间也提供相类似的Text.Contains(string)、Text.DoesNotContain(string)、Text.StartsWith(string)、Text.DoesNotStartWith(string)、Text.EndsWith(string)、Text.DoesNotEndWith(string)、Text.Matches(string)、Text.DoesNotMatch(string) 方法使用字符串约束检查字符串。直接看例子:
[Test]public voidStringTest() {//定义变量varphrase ="Hello World!";vararray =new string[] {"abc","bad","dba"};vargreetings =new string[] {"Hello!","Hi!","Hola!"};varpassage ="Now is the time for all good men to come to the aid of their country.";varquotes =new string[] {"Never say never","It's never too late","Nevermore!"};//经典语法StringAssert.Contains("World", phrase);StringAssert.StartsWith("Hello", phrase);StringAssert.EndsWith("!", phrase);StringAssert.AreEqualIgnoringCase("hello world!", phrase);StringAssert.IsMatch("all good men", passage);StringAssert.IsMatch("Now.*come", passage);//约束语法 //测试是否包含"World"Assert.That(phrase,Text.Contains("World"));Assert.That(phrase,Text.DoesNotContain("goodbye"));Assert.That(phrase,Text.Contains("WORLD").IgnoreCase);Assert.That(phrase,Text.DoesNotContain("BYE").IgnoreCase);Assert.That(array,Text.All.Contains("b"));//测试字符串是否以"Hello"开始Assert.That(phrase,Text.StartsWith("Hello"));Assert.That(phrase,Text.DoesNotStartWith("Hi!"));Assert.That(phrase,Text.StartsWith("HeLLo").IgnoreCase);Assert.That(phrase,Text.DoesNotStartWith("HI").IgnoreCase);Assert.That(greetings,Text.All.StartsWith("h").IgnoreCase);//测试字符串是否以"!"结束Assert.That(phrase,Text.EndsWith("!"));Assert.That(phrase,Text.DoesNotEndWith("?"));Assert.That(phrase,Text.EndsWith("WORLD!").IgnoreCase);Assert.That(greetings,Text.All.EndsWith("!"));Assert.That(phrase,Is.EqualTo("hello world!").IgnoreCase);Assert.That(phrase,Is.Not.EqualTo("goodbye world!").IgnoreCase);Assert.That(new string[] {"Hello","World"},Is.EqualTo(new object[] {"HELLO","WORLD"}).IgnoreCase);Assert.That(new string[] {"HELLO","Hello","hello"},Is.All.EqualTo("hello").IgnoreCase);//测试字符串是否同"all good men"相配Assert.That(passage,Text.Matches("all good men"));Assert.That(passage,Text.Matches("Now.*come"));Assert.That(passage,Text.DoesNotMatch("all.*men.*good"));Assert.That(passage,Text.Matches("ALL").IgnoreCase);Assert.That(quotes,Text.All.Matches("never").IgnoreCase);//使用继承语法 //测试是否包含"World"Expect(phrase, Contains("World")); Expect(phrase, Not.Contains("goodbye")); Expect(phrase, Contains("WORLD").IgnoreCase); Expect(phrase, Not.Contains("BYE").IgnoreCase); Expect(array, All.Contains("b"));//测试字符串是否以"Hello"开始Expect(phrase, StartsWith("Hello")); Expect(phrase, Not.StartsWith("Hi!")); Expect(phrase, StartsWith("HeLLo").IgnoreCase); Expect(phrase, Not.StartsWith("HI").IgnoreCase); Expect(greetings, All.StartsWith("h").IgnoreCase);//测试字符串是否以"!"结束Expect(phrase, EndsWith("!")); Expect(phrase, Not.EndsWith("?")); Expect(phrase, EndsWith("WORLD!").IgnoreCase); Expect(greetings, All.EndsWith("!")); Expect(phrase, EqualTo("hello world!").IgnoreCase); Expect(phrase, Not.EqualTo("goodbye world!").IgnoreCase); Expect(new string[] {"Hello","World"}, EqualTo(new object[] {"HELLO","WORLD"}).IgnoreCase); Expect(new string[] {"HELLO","Hello","hello"}, All.EqualTo("hello").IgnoreCase);//测试字符串是否同"all good men"相配Expect(passage, Matches("all good men")); Expect(passage, Matches("Now.*come")); Expect(passage, Not.Matches("all.*men.*good")); Expect(passage, Matches("ALL").IgnoreCase); Expect(quotes, All.Matches("never").IgnoreCase); }
8.CollectionAssert、Collection Constraints
CollectionAssert类提供许多方法,像AllItemsAreInstancesOfType、AllItemsAreNotNull、AllItemsAreUnique、AreEqual(相同对象和次序)、AreEquivalent(相同对象次序不同)、AreNotEqual、AreNotEquivalent、Contains、DoesNotContain、IsEmpty、IsNotEmpty、IsNotSubsetOf、IsSubsetOf、ReferenceEquals。这些方法在检查集合值和比较两个集合时是有用的。集合参数必须实现IEnumerable接口。
而NUnit.Framework.AssertionHelper命名空间也提供相类似的方法使用集合约束检查集合。下面用例子说明,一看就明白。
[Test]public voidAllItemsTests() {//定义3个集合object[] ints =new object[] { 1, 2, 3, 4 };object[] doubles =new object[] { 0.99, 2.1, 3.0, 4.05 };object[] strings =new object[] {"abc","bad","cab","bad","dad"};//经典语法CollectionAssert.AllItemsAreNotNull(ints);//ints集合所有项不为空CollectionAssert.AllItemsAreInstancesOfType(ints,typeof(int));//ints集合所有项类型为intCollectionAssert.AllItemsAreInstancesOfType(strings,typeof(string));CollectionAssert.AllItemsAreUnique(ints);//ints集合所有项都是唯一的 //Helper语法Assert.That(ints,Is.All.Not.Null);Assert.That(ints,Has.None.Null);Assert.That(ints,Is.All.InstanceOfType(typeof(int)));Assert.That(ints,Has.All.InstanceOfType(typeof(int)));Assert.That(strings,Is.All.InstanceOfType(typeof(string)));Assert.That(strings,Has.All.InstanceOfType(typeof(string)));Assert.That(ints,Is.Unique);Assert.That(strings,Is.Not.Unique);Assert.That(ints,Is.All.GreaterThan(0));Assert.That(ints,Has.All.GreaterThan(0));Assert.That(ints,Has.None.LessThanOrEqualTo(0));Assert.That(strings,Text.All.Contains("a"));Assert.That(strings,Has.All.Contains("a"));Assert.That(strings,Has.Some.StartsWith("ba"));Assert.That(strings,Has.Some.Property("Length", 3));Assert.That(strings,Has.Some.StartsWith("BA").IgnoreCase);Assert.That(doubles,Has.Some.EqualTo(1.0).Within(.05));//使用继承语法Expect(ints, All.Not.Null); Expect(ints, None.Null); Expect(ints, All.InstanceOfType(typeof(int))); Expect(strings, All.InstanceOfType(typeof(string))); Expect(ints, Unique); Expect(strings, Not.Unique); Expect(ints, All.GreaterThan(0)); Expect(ints, None.LessThanOrEqualTo(0)); Expect(strings, All.Contains("a")); Expect(strings, Some.StartsWith("ba")); Expect(strings, Some.StartsWith("BA").IgnoreCase); Expect(doubles, Some.EqualTo(1.0).Within(.05)); }
9.FileAssert
FileAssert类提供AreEqual、AreNotEqual方法来比较两个文件,文件可以作为Stream、FileInfo、指定的文件路径来操作。
10.其他约束
这些约束都是新增的,由于和经典的断言没有一致的分类,我把它们单独列出来了,也在这里说说。
10-1.Property Constraint
属性约束。由主要测试对象的属性。
[Test]public voidPropertyTest() {//定义变量string[] array = {"abc","bca","xyz","qrs"};string[] array2 = {"a","ab","abc"};ArrayListlist =newArrayList(array);//约束语法Assert.That(list,Has.Property("Count"));//是否有Count属性Assert.That(list,Has.No.Property("Length"));//是否没有Length属性Assert.That("Hello",Has.Property("Length", 5));//"Hello"的Length属性是否是5Assert.That("Hello",Has.Length(5));//"Hello"的Length属性是否是5Assert.That("Hello",Has.Property("Length").EqualTo(5));//"Hello"的Length属性是否是5Assert.That("Hello",Has.Property("Length").GreaterThan(3));//"Hello"的Length属性是否大于3Assert.That(array,Has.Property("Length", 4));Assert.That(array,Has.Length(4));Assert.That(array,Has.Property("Length").LessThan(10));Assert.That(array,Has.All.Property("Length", 3));//所有项Length属性是否是3Assert.That(array,Has.All.Length(3));Assert.That(array,Is.All.Length(3));Assert.That(array,Has.All.Property("Length").EqualTo(3));Assert.That(array,Is.All.Property("Length").EqualTo(3));Assert.That(array2,Has.Some.Property("Length", 2));Assert.That(array2,Has.Some.Length(2));Assert.That(array2,Has.Some.Property("Length").GreaterThan(2));Assert.That(array2,Is.Not.Property("Length", 4));Assert.That(array2,Is.Not.Length(4));Assert.That(array2,Has.No.Property("Length").GreaterThan(3));Assert.That(List.Map(array2).Property("Length"),Is.EqualTo(new int[] { 1, 2, 3 }));Assert.That(List.Map(array2).Property("Length"),Is.EquivalentTo(new int[] { 3, 2, 1 }));Assert.That(List.Map(array2).Property("Length"),Is.SubsetOf(new int[] { 1, 2, 3, 4, 5 }));Assert.That(List.Map(array2).Property("Length"),Is.Unique);Assert.That(list,Has.Count(4));//继承语法Expect(list, Property("Count")); Expect(list, Not.Property("Nada")); Expect("Hello", Property("Length", 5)); Expect("Hello", Length(5)); Expect("Hello", Property("Length").EqualTo(5)); Expect("Hello", Property("Length").GreaterThan(0)); Expect(array, Property("Length", 4)); Expect(array, Length(4)); Expect(array, Property("Length").LessThan(10)); Expect(array, All.Property("Length", 3)); Expect(array, All.Length(3)); Expect(array, All.Property("Length").EqualTo(3)); Expect(array2, Some.Property("Length", 2)); Expect(array2, Some.Length(2)); Expect(array2, Some.Property("Length").GreaterThan(2)); Expect(array2, None.Property("Length", 4)); Expect(array2, None.Length(4)); Expect(array2, None.Property("Length").GreaterThan(3)); Expect(Map(array2).Property("Length"), EqualTo(new int[] { 1, 2, 3 })); Expect(Map(array2).Property("Length"), EquivalentTo(new int[] { 3, 2, 1 })); Expect(Map(array2).Property("Length"), SubsetOf(new int[] { 1, 2, 3, 4, 5 })); Expect(Map(array2).Property("Length"), Unique); Expect(list, Count(4)); }
10-2.Compound Constraints
进行对象间的比较。由几个方法复合作用。
[Test]public voidCompoundTest() {//约束语法Assert.That(2 + 2,Is.Not.EqualTo(5));Assert.That(new int[] { 1, 2, 3 },Is.All.GreaterThan(0));Assert.That(2.3,Is.GreaterThan(2.0) &Is.LessThan(3.0));Assert.That(3,Is.LessThan(5) |Is.GreaterThan(10));//继承语法Expect(2 + 2, Not.EqualTo(5)); Expect(2.3, GreaterThan(2.0) & LessThan(3.0)); }
10-3.List Mapper
集合映射,比如下面的例子,测试strings数组对应项的Length属性是否为lengths对应项的值。
[Test]public voidListMapperTest() {//定义2个数组string[] strings =new string[] {"a","ab","abc"};int[] lengths =new int[] { 1, 2, 3 };//约束语法Assert.That(List.Map(strings).Property("Length"),Is.EqualTo(lengths));Assert.That(newListMapper(strings).Property("Length"),Is.EqualTo(lengths));//继承语法Expect(Map(strings).Property("Length"), EqualTo(lengths)); }
结束语
关于这篇测试语法断言介绍,由于断言很多,很难在一篇文章中把所有的断言学习到。之前,我也想考虑分为经典模式和约束模式来介绍,发现大致相同,也浪费大量时间,所以千思万想,把这些属性整合在一起综合介绍,带着丰富的例子,相信可以掌握这些断言。考虑到本节代码过多,还有一部分还没有贴出来,提供下载。地址为:YJingLee.Test.zip(VS2008项目,如果你是VS2005只需复制其中的测试文件到你的项目中即可)
相关阅读:
- 软件测试的革命二 (habug, 2008-7-09)
- 软件测试的革命三 (habug, 2008-7-09)
- 微软的软件测试方法 (habug, 2008-7-09)
- 软件测试实践之测试环境的规划与管理 (habug, 2008-7-09)
- 剖析层出不穷的BUG (habug, 2008-7-09)
- Ad-hoc 测试介绍 (chenqilong, 2008-8-05)
- IE浏览器无法查看源文件的主要原因分析 (lzhtjbh, 2008-8-16)
- 使用 Xen:使用虚拟 Linux 来测试应用程序 (habug, 2008-8-29)
- 你学会测试了吗(1):推荐工具 (habug, 2008-9-08)
- 你学会测试了吗(2):测试语法之属性介绍 (habug, 2008-9-08)
导入论坛 引用链接 收藏 分享给好友 推荐到圈子 管理 举报
TAG: 软件测试



