不要追求绝对的公平,红尘之中没有公平而言,人活一世,难得糊涂。                                           it is no use doing what you like, you have got to like what you do.

单元测试和事先测试开发

上一篇 / 下一篇  2007-02-25 10:56:02 / 个人分类:单元测试

单元测试和事先测试开发
H`$Lh5rS n G`051Testing软件测试网j7cU5g%zC$R h%u
  要减少软件中的错误数目,方法之一就是拥有一个专业的测试组,其工作就是尽一切可能使软件崩溃。不幸的是,如果拥有测试组,那么即使是经验丰富的开发人员,也会倾向于花费较少的时间来保证代码的可靠性。
*]@f%iraBE)y051Testing软件测试网,[(q%I0KHg:j
  软件界有一句俗语:“开发人员不应该测试他们自己的代码”。这是因为开发人员对自己的代码了如指掌,他们很清楚如何采用适当的方法对代码进行测试。尽 管这句俗语很有道理,但却忽略了非常重要的一点 - 如果开发人员不对自己的代码进行测试,又如何知道代码能否按照预期的方式运行?
|Sg[w[1k0
9TdF c.N0  简单说来,他们根本无从得知。开发人员编写那种运行不正常或只在某些情况下运行正常的代码是一个严重的问题。他们通常只测试代码能否在很少的情况下正常运行,而不是验证代码能够在所有情况下均正常运行。
,cl8H(d^/O$c1ZL0
Q4|u f9jKs0发现软件错误
~,S5l[a%[e0  发现软件错误的情况有很多:51Testing软件测试网 @'J w8M$s-Y
  1. 由首次编写代码的开发人员发现。
  2. 由尝试运行代码的开发人员发现。
  3. 由组中的其他开发人员或测试人员发现。
  4. 作为产品大规模测试的一部分。
  5. 由最终用户发现。
   如果在第一种情况下发现软件错误,则修复错误比较容易,成本也很低。情况越靠后,修复软件错误的成本就越高;修复一个由最终用户发现的软件错误可能要耗 费 100 或 1000 倍的成本。更不用说用户通常因为软件错误导致工作无法继续,而一直等到下一个版本才能解决问题。
q o L1A'PuX0  如果开发人员能够在编写代码期间发现所有的软件错误,那就再好不过了。为此,您必须编写能在编写代码时运行的测试。有一种很不错的方法,它恰好可以做到这一点。
?WVfTX bF!Yv0
Z)u~1B2T-p0事先测试开发51Testing软件测试网t&~Z `G
  所谓的事先测试开发是指在编写代码前编写测试。如果所有测试均正常运行,便可以断定代码运行正常;添加新功能时,这些测试会继续验证您是否破坏了代码的任何部分。51Testing软件测试网9T*Q:|(SF+};K{)N

?A8tA [)DH0  此概念于 20 世纪 90 年代初诞生于 Smalltalk 世界,Kent Beck 在当时编写了 SmalltalkUnit。在过去的几年中,大部分环境都具备了单元测试工具,其中有一个很出色的适用于 .NET Framework 领域的工具,即nUnit(英文)。51Testing软件测试网4O r(vDR vxt f
示例下面我将编写一个IntegerList类来介绍事先测试开发的工作原理。IntegerListArrayList类的变体,用于在本地存储整数,因此不存在装箱和取消装箱的开销。
:Qx LL:x,C051Testing软件测试网"H]]w2H6U/aA Dq
  第一步是创建一个控制台项目,并向其中添加一个IntegerList.cs源文件。要连接 nUnit 框架,需要添加对 nUnit 框架的引用。在我的系统中,它们位于d:\program files\nUnit v2.0\bin
S5Dr~G f0j051Testing软件测试网 N A'zi U{2?[(H
  第二步是花些时间考虑如何对该类进行测试。这与确定类应该具备哪些功能的过程类似,但重点放在功能的特定用途(将值 1 添加到列表并检查是否成功),而不是功能本身(将一个项目添加到列表)。要生成此类,我们首先要提供一个要使用的测试列表:51Testing软件测试网YI#Up{E V2P
  1. 测试该类可以构造
  2. 将两个整数添加到列表,并确保数目和项目都正确。
  3. 执行同一操作,但针对更多的项目。
  4. 将此列表转换为一个字符串。
  5. 使用foreach枚举此列表。
此示例从某种程度上代表了我开始时的想法,即希望这个类执行的操作。多数类一次只会创建一小部分,测试应随着类的增长而添加。
]lo:@8\N051Testing软件测试网WF,U1g(v(kg
开始51Testing软件测试网,P;ENF Z(g
  现在我可以开始了。我创建一个名为 IntegerListTest.cs 的新 C# 类文件,用于存放所有测试。下面是包含第一个测试的文件:
?3o\/{'e+N0
-Kb9@+z$g AyrYr0using System;
R m`%L X fwAN#n0using System.Collections;51Testing软件测试网9@/N9_,? G"f
using NUnit.Framework;
Q;}qc+A051Testing软件测试网8h a:{I5N~zqb:C[
namespace IntegerList
I!C%xY8|F?C2VGw0{51Testing软件测试网|![ m(TXSU
    /// <summary>51Testing软件测试网f'j8_%x9~o9H(T'd
    /// IntegerClassTest 的摘要说明。51Testing软件测试网O;|([/u;k Em&Krg
    /// </summary>51Testing软件测试网m y&\q.MUo
    [TestFixture]51Testing软件测试网+P"u)B ok
    public class IntegerClassTest51Testing软件测试网LE Y\%M7D-i5s
    {
y-E,grL-B6iB0        [Test]51Testing软件测试网:N7b5k:q;OaMTt
        public void ListCreation()51Testing软件测试网4z:Hu!vf| _x]r
        {
:MO-m_'\oAo]0            IntegerList list = new IntegerList();
7[/F2K k Q0            Assertion.AssertNotNull(list);
7ue3x5Ki6U%A^F8i`0        }
CQL3M tW5g0    }51Testing软件测试网 @ @I$mLY.g7P
}
%J7[/T&Uo051Testing软件测试网;QLRjvti c8bO:J
  [TestFixture] 属性将此类标记为测试类,[Test] 属性将 ListCreation() 方法标记为测试方法。在此方法中,我创建了一个列表,然后使用 Assertion 类测试对象 gets 已经创建。
VLdR]3ct6@.z051Testing软件测试网$ZV7WD ^7}8L
  我启动 nUnit GUI 测试程序,打开可执行文件,并执行这些测试。51Testing软件测试网b4nB5t2V.Cx;uJ
51Testing软件测试网1F)M/U On$L
51Testing软件测试网&? eK/[ p

.[A.y|!{'H8g0  所有测试都已通过。现在我想添加一些真实功能。第一个操作就是向列表中添加一个整数。此测试如下所示:
7Z%W8T'AR[:~t1T a0
1w%TI&u2]L(PE%n0        [Test]
e#Y Zh`5SV8q0        public void TestSimpleAdd()51Testing软件测试网f`Wa?r
        {
~2Jh:wrP0            IntegerList list = new IntegerList();51Testing软件测试网 hL;y!D5[o7PL
            list.Add(5);
f-{3@&E w]0            list.Add(10);51Testing软件测试网 o,_-NGgb
            Assertion.AssertEquals(2, list.Count);51Testing软件测试网7`"u wLPk
            Assertion.AssertEquals(5, list[0]);51Testing软件测试网V h`3?Z z
            Assertion.AssertEquals(10, list[1]);51Testing软件测试网 ^2o5qE We;hR#b)[
        }51Testing软件测试网$Hq U8n`&I,A@a]j

!{Vk A)\9dN0  在此测试中,我选择同时测试两个操作:
1^Y2mzmc0
$h S Y+N"Sr0

    "AX7hW"l@#A h&j0
  • 列表正确维护 Count 属性。
  • 列表可以包含两个项。

Kvm$s9O5C/L b~0  某些测试驱动开发的倡议者提倡测试应尽可能只测试数目,但是如果只测试数目而不测试项目,这对于我而言有些不可思议,因此我所选择的是两者一起测试。
/|f?@a`3Q$i0
#T"M1Nlv-~E0  编译这段代码时,由于 IntegerList 类中没有方法,因此编译失败,为此我加上以下代码进行编译:51Testing软件测试网9bt@ R.sJO_ x

,RF:J7L;o9B0        public int Count
-pa%gptZ;d0        {51Testing软件测试网&n(K6G3z:L4vs
            get51Testing软件测试网I)f*b F$Xx
            {
,j#p w`+y/Wp0                return -1;51Testing软件测试网k ?,tK%v/r*hc
            }51Testing软件测试网f1~$|RR8N
        }
f)tcTZ0
5F+Xku1G"\W~%Is0        public void Add(int value)
&Hf4un$p.] Egl"b,jwZ0        {51Testing软件测试网:\9[hd rI ~$Y
        }51Testing软件测试网HfQ _k
51Testing软件测试网)Nf*}Xz$bq
        public int this[int index]
7P { q*Ln'?.O)m#{,j+^0        {51Testing软件测试网.] [ t0h:DY tK
            get
-v%VIV t?+q0            {
u jw|r{4f-Xt0                return -1;51Testing软件测试网l1\T!m:}*[EZ
            }
Hf {C,tfg;X0        }51Testing软件测试网7kb Q)Ek!U

]RM8m-x A0  然后我返回并运行测试,这时它们显示为红色,表示测试失败。这很好,因为它意味着测试实际上已测试出程序错误。现在我可以执行此实现。我可以做些简单的工作,尽管这样做效率不是很高:51Testing软件测试网:NTCYU@M

!O*R$G4i?C7k0        public int Count51Testing软件测试网[Px?cy:L G
        {51Testing软件测试网^EW-bY
            get51Testing软件测试网~5Ynb;M,Lo9l^
            {51Testing软件测试网)@"h;IX X
                return elements.Length;51Testing软件测试网0? nF P \)n%Sb$_h
            }
d:cqSv9jZG0        }
;?3\HJ ]5x051Testing软件测试网W+i!XG4LgW
        public void Add(int value)51Testing软件测试网SQoY7H'm2\
        {51Testing软件测试网r,{Q1W ^@
            int newIndex;
/N5lM3{o?"y3{+s_!\6VB0            if (elements != null)51Testing软件测试网 ?(n?'u!]4_
            {51Testing软件测试网)vfX2p.RQyZ W M&Z
                int[] newElements = new int[elements.Length + 1];
$?!ky^&IZ0                for (int index = 0; index < elements.Length;51Testing软件测试网-]D'},Fj1s%hgo E$@B^
                     index++)   
i+I aT&cV0]uq-{0                {51Testing软件测试网{Af ?F(q
                    newElements[index] = elements[index];
,[fCp V/NQ0                }
?6m cmi0iI"ASt0                newIndex = elements.Length;51Testing软件测试网)C c2pvjH i
                elements = newElements;51Testing软件测试网-EN?i'VcC{
            }
_CA'KsA&@)Nmt"Y0            else51Testing软件测试网r%I d,S^*|&EX
            {
emP)X&\$X WvA_0                elements = new int[1];51Testing软件测试网)k'MjX&~7B} | }\
                newIndex = 0;
OQEg-w6R8G5lng,N0            }
h&dW(Vg-lG]\0            elements[newIndex] = value;51Testing软件测试网-z.I d:E~,_t}*H9H
        }
Vt:l O-U_4H0
CR-nt+M;\7HT0        public int this[int index]51Testing软件测试网 NA5WS2{/[c(KVS
        {51Testing软件测试网oJu[+V lx ldE;V
            get
R;J3j L3V(_bL0            {
ta1y n/G F1^%{0                return elements[index];51Testing软件测试网HA.^W3a+H8[
            }
;f+Q@|+d?wc Lu0        }
&Q-V,?3@L ^FW0
(Ci"},f4z'n;c/d0现在已经完成类的一小部分,并已经编写了可确保其正常工作的测试,但我仅仅测试了项目中很少的一部分。接下来,我要编写一个用于检查 1000 个项的测试:
5tfon0~7m0H051Testing软件测试网4O f$YG;[S [R)h JL
        [Test]51Testing软件测试网~WbF9Eg-_U
        public void TestOneThousandItems()
*~$NO\!D-s0        {
)P6Q#jx-@~:T0            list = new IntegerList();51Testing软件测试网-QR [7} h+^{}
51Testing软件测试网1^fn@u9Ags,\
            for (int i = 0; i < 1000; i++)51Testing软件测试网6K:Z+n.M%Gi[
            {51Testing软件测试网g*j5M\N!kl"S
                list.Add(i);
4]c6k5g#|HC4?-ar0            }
"L;N]'i0{.|051Testing软件测试网c _$m p Cu `V;J T#p
            Assertion.AssertEquals(1000, list.Count);51Testing软件测试网b'P9O/M7E6Y3r:j
            for (int i = 0; i < 1000; i++)
0SN2@zr_,C0            {51Testing软件测试网;Oi:h4_o?ha&]2k
                Assertion.AssertEquals(i, list[ i ]);51Testing软件测试网 D3JC/j c._A} ~\j
            }
o"r@Ok.WD0        }
^{s'e1e)W051Testing软件测试网w"iKYDZ6c{
此测试运行正常,因此无须进行任何更改。
w7Q'^ n/[051Testing软件测试网HD K6a*I7I.R!G7J j:y
添加 ToString() 方法51Testing软件测试网HMV7H U:Y.C {!O
接下来,我将添加测试代码,以测试 ToString() 能否正常运行:
QW-h@Il;lFR0
P"P+m'KC)O"En4o0        [Test]51Testing软件测试网$K5P c Ll1a\
        public void TestToString()
f\:~2~sB6tr0        {
cp'CBu2O0q:L/Ie5W0            IntegerList list = new IntegerList();
HR t3hd%q(d0            list.Add(5);
)fC3ESL'[ AW6t4]0            list.Add(10);
L3t4V }m1a8AeZA}0            string t = list.ToString();51Testing软件测试网[?,H ?v4M'N
            Assertion.AssertEquals("5, 10", t.ToString());51Testing软件测试网5W@ }6R Wfl"P/C0T
        }51Testing软件测试网 l F_ p]PR,mM
51Testing软件测试网VM3mxti
  失败了,没关系。以下代码可以使其通过:
)h3U_g*i8Nc"A051Testing软件测试网LW?#V!v6\(m
        public override string ToString()
"csgq7e;r0        {51Testing软件测试网%vu*g3dwmj
            string[] items = new string[elements.Length];
~ yr'H%fkK'f0            for (int index = 0; index < elements.Length; index++)
6\p/t'f9}|7C0            {51Testing软件测试网xhqe^$T
                items[index] = elements[index].ToString();51Testing软件测试网/r bk` L|`u
            }51Testing软件测试网)LXG4\v
            return String.Join(", ", items);
q/Ogf[p0        }51Testing软件测试网V#{Lm!Z
51Testing软件测试网;rBko gox&@
启用 Foreach
8t@2p6G%XHO/U0  许多用户希望能够使用 foreach 遍历我的列表。为此,我需要在类中实现 Ienumerable,并定义一个单独的用于实现 Ienumerable 的类。第一步,测试:51Testing软件测试网$ThJ o:e y
51Testing软件测试网{U#jG2@&M
        [Test]
"z|BIh2q'aw l R0        public void TestForeach()
j5Fm'sk`-^,n0        {
$cX1Y~N(Su0            IntegerList list = new IntegerList();51Testing软件测试网 cNME+Cx)\,B
            list.Add(5);51Testing软件测试网:u:T*I3mf-l"w(Na
            list.Add(10);
4DCF2t.S/O?z~Bh(c0            list.Add(15);51Testing软件测试网|5F*g/c?:D&B t
            list.Add(20);
fs6l4m J*S9GZ0
&g]e0o2q@X[0            ArrayList items = new ArrayList();51Testing软件测试网(X fF[w mG
51Testing软件测试网0PD!qY1b9q
            foreach (int value in list)51Testing软件测试网;@4_*|;D6k$J,a\0J
            {
[*Q1? uPgy._m6X0                items.Add(value);51Testing软件测试网&HK/|Ou cal$ZT
            }51Testing软件测试网wPZ1K*Eh\{

1jK2\;~Vh9{ s0            Assertion.AssertEquals("Count", 4, items.Count);
T*f3^d'h&f j0            Assertion.AssertEquals("index 0", 5, items[0]);51Testing软件测试网fXg xo qcr
            Assertion.AssertEquals("index 1", 10, items[1]);
6M|,j^uh*r*C bc0            Assertion.AssertEquals("index 2", 15, items[2]);51Testing软件测试网?Cuu vXt-K
            Assertion.AssertEquals("index 3", 20, items[3]);
1MK ?+d(_%f7EE"S Th8G0        }51Testing软件测试网%zJ7msV

%qkr$l*YhUbQ0  我还通过 IntegerList 实现 IEnumerable:51Testing软件测试网X:c3O+?qP$fT P

!oADn:eFEhrT0        public IEnumerator GetEnumerator()
oZE'zG$LkU`9}}6t0        {
`+`@ z)U}6Rv0            return null;
x[-j cx.yr0        }51Testing软件测试网}/B^-[&]*p a
51Testing软件测试网yNz#IiWKl n a
  运行测试时,此代码生成异常。为了正确地实现此功能,我将使用一个嵌套类作为枚举器。
kV3GX^'s0
7O4E2O%p6`9U)kf0    class IntegerListEnumerator: IEnumerator
4Xfj[%s"g.A-[y0    {
@f;B#BlV o jC ]0        IntegerList    list;51Testing软件测试网*Xi]4WY
        int index = -1;51Testing软件测试网}p$B/f5Tj|%@C*a6^f
51Testing软件测试网 B*u4}p9s
        public IntegerListEnumerator(IntegerList list)
6y2RW+j| y`^KU0        {
$Y,L O:F&]Q7\a2K&B0            this.list = list;
$l Z4^O4X6z0        }
D^PF@2u0        public bool MoveNext()
S$bD"O!m5r0        {
S|%~#\6GOjkl0            index++;51Testing软件测试网;r{ CB6D%m|(V
            if (index == list.Count)
I,?5U[`0                return(false);51Testing软件测试网4_N0Uz7r K
            else51Testing软件测试网6`#O!` F{t
                return(true);
*rL6}z6O%\6T0        }51Testing软件测试网5n!sh]8^ z"X ^
        public object Current51Testing软件测试网s*@spw} S
        {51Testing软件测试网Q zDtH,G*VR%EL&Px
            get51Testing软件测试网??%t ?F{XO+|-O
            {51Testing软件测试网,z'w#@ Hq F-k
                return(list[index]);51Testing软件测试网v-x!e7KkY
            }
"k%~)J|s/_0        }51Testing软件测试网3N X({.k3r]$C
        public void Reset()
vG p `#bkF4aG0        {51Testing软件测试网 w ZV*SGq-M
            index = -1;
#T1h'w@ @5~Mb0        }51Testing软件测试网4A#~l sEW{:Q3Y:m
    }51Testing软件测试网 NGwc8Pc E

]uYz B*t$h0  此类将一个指针传递给 IntegerList 对象,然后只返回此对象中的元素。51Testing软件测试网#D;v P.K~Z
51Testing软件测试网)w7v1k'v(@1O~
  这样,便可以对列表执行 foreach 操作,但遗憾的是 Current 属性属于对象类型,这意味着每个值将被装箱才能将其返回。此问题可采用一种基于模式的方法加以解决,此方法酷似当前方法,但它通过 GetEnumerator() 返回一个真正的类(而非 IEnumerator),且此类中的 Current 属性为 int 类型。51Testing软件测试网}-Wz IXNHGl
51Testing软件测试网)\.GeSw
  然而执行此操作后,我要确保在不支持该模式的语言中仍然可以使用这种基于接口的方法。我将复制编写的上一个测试并修改 foreach 以转换为接口:51Testing软件测试网.b#|RP"I8c^:`-u
51Testing软件测试网_{2|x z'MU"W B Sh
            foreach (int value in (IEnumerable) list)
^/`2l"Q-FNrD-A051Testing软件测试网 m7g8G/jV
  只需少许改动,列表即可在两种情况下正常运行。
6| A6I#X1{f#m1q0

TAG: 单元测试

 

评分:0

我来说两句

Open Toolbar