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

单元测试和事先测试开发

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

单元测试和事先测试开发51Testing软件测试网*`+B.Ek`0Z-vtf

F b\U P5v%J&p0
  要减少软件中的错误数目,方法之一就是拥有一个专业的测试组,其工作就是尽一切可能使软件崩溃。不幸的是,如果拥有测试组,那么即使是经验丰富的开发人员,也会倾向于花费较少的时间来保证代码的可靠性。
\`6rpa(r2f @051Testing软件测试网n q?RZen
  软件界有一句俗语:“开发人员不应该测试他们自己的代码”。这是因为开发人员对自己的代码了如指掌,他们很清楚如何采用适当的方法对代码进行测试。尽 管这句俗语很有道理,但却忽略了非常重要的一点 - 如果开发人员不对自己的代码进行测试,又如何知道代码能否按照预期的方式运行?
"bK{1Bq[.v7z&iD0
x&X,D6{yX._0  简单说来,他们根本无从得知。开发人员编写那种运行不正常或只在某些情况下运行正常的代码是一个严重的问题。他们通常只测试代码能否在很少的情况下正常运行,而不是验证代码能够在所有情况下均正常运行。51Testing软件测试网M}/b(x/w+Co

+AcK:iU:E%`0发现软件错误51Testing软件测试网4A Bc.x)i0iM c
  发现软件错误的情况有很多:
!|,z%Df Y0
  1. 由首次编写代码的开发人员发现。
  2. 由尝试运行代码的开发人员发现。
  3. 由组中的其他开发人员或测试人员发现。
  4. 作为产品大规模测试的一部分。
  5. 由最终用户发现。
   如果在第一种情况下发现软件错误,则修复错误比较容易,成本也很低。情况越靠后,修复软件错误的成本就越高;修复一个由最终用户发现的软件错误可能要耗 费 100 或 1000 倍的成本。更不用说用户通常因为软件错误导致工作无法继续,而一直等到下一个版本才能解决问题。
l+W)ix:Q+dO sl0  如果开发人员能够在编写代码期间发现所有的软件错误,那就再好不过了。为此,您必须编写能在编写代码时运行的测试。有一种很不错的方法,它恰好可以做到这一点。51Testing软件测试网~ GD:rH
51Testing软件测试网3^.s"_hWtMX.S
事先测试开发51Testing软件测试网5N0^ehKBxj
  所谓的事先测试开发是指在编写代码前编写测试。如果所有测试均正常运行,便可以断定代码运行正常;添加新功能时,这些测试会继续验证您是否破坏了代码的任何部分。51Testing软件测试网 v~Cu(T5i'g

s w*|ykZ*H0  此概念于 20 世纪 90 年代初诞生于 Smalltalk 世界,Kent Beck 在当时编写了 SmalltalkUnit。在过去的几年中,大部分环境都具备了单元测试工具,其中有一个很出色的适用于 .NET Framework 领域的工具,即nUnit(英文)。51Testing软件测试网L&j]qPz)f1Y
示例下面我将编写一个IntegerList类来介绍事先测试开发的工作原理。IntegerListArrayList类的变体,用于在本地存储整数,因此不存在装箱和取消装箱的开销。51Testing软件测试网 brA7?2mTOC RFL
51Testing软件测试网I%F(B7[$|!F4X h} P!{b!A
  第一步是创建一个控制台项目,并向其中添加一个IntegerList.cs源文件。要连接 nUnit 框架,需要添加对 nUnit 框架的引用。在我的系统中,它们位于d:\program files\nUnit v2.0\bin
,L?|(v8^Y+ja051Testing软件测试网 bG7l{kdR {
  第二步是花些时间考虑如何对该类进行测试。这与确定类应该具备哪些功能的过程类似,但重点放在功能的特定用途(将值 1 添加到列表并检查是否成功),而不是功能本身(将一个项目添加到列表)。要生成此类,我们首先要提供一个要使用的测试列表:51Testing软件测试网?/uzH'X
  1. 测试该类可以构造
  2. 将两个整数添加到列表,并确保数目和项目都正确。
  3. 执行同一操作,但针对更多的项目。
  4. 将此列表转换为一个字符串。
  5. 使用foreach枚举此列表。
此示例从某种程度上代表了我开始时的想法,即希望这个类执行的操作。多数类一次只会创建一小部分,测试应随着类的增长而添加。
O N;Zym;Q,z`.RY0
z'U]N.p`0开始
P+I} _$G0  现在我可以开始了。我创建一个名为 IntegerListTest.cs 的新 C# 类文件,用于存放所有测试。下面是包含第一个测试的文件:51Testing软件测试网4}xAd3xA"zU2\Q6Z,J
51Testing软件测试网.T0ya3p8dW_y7O p
using System;51Testing软件测试网 {M&]iD5tI!w
using System.Collections;
]/VE,d+lb3O4v0using NUnit.Framework;51Testing软件测试网X&nI*W6C

:|Qq`W#r(xl0namespace IntegerList51Testing软件测试网X;CBb0`
{51Testing软件测试网5l#H(B-R;|U5`3s,^
    /// <summary>
U6a o+D(E%M9o0    /// IntegerClassTest 的摘要说明。
uDg}:`6M U _H0    /// </summary>51Testing软件测试网O#n+O!Kl9N/H)e-Y
    [TestFixture]
h#tX,Z(R0y*{0    public class IntegerClassTest51Testing软件测试网 pp6bY"I#Wv B
    {
"c8u0Aw:w&Pi!M0        [Test]
/E#K m&@ z{0        public void ListCreation()51Testing软件测试网%d7V|,@r.X Jw
        {
vm ^;Nh@0            IntegerList list = new IntegerList();
[wr KLKE0            Assertion.AssertNotNull(list);
5y-d2Y I8Dn.mT e0        }
uY&_Su0z)H{7j0    }51Testing软件测试网0tD_O4U T
}
T[ iA z051Testing软件测试网[h!Jx S#k3`
  [TestFixture] 属性将此类标记为测试类,[Test] 属性将 ListCreation() 方法标记为测试方法。在此方法中,我创建了一个列表,然后使用 Assertion 类测试对象 gets 已经创建。
*_4q8CksBh.m051Testing软件测试网 {KqkI6@\2vS
  我启动 nUnit GUI 测试程序,打开可执行文件,并执行这些测试。51Testing软件测试网:X W@1vUP,zy
51Testing软件测试网0ei v{_X

-f{:N A^9| S0
kjOpHxw0  所有测试都已通过。现在我想添加一些真实功能。第一个操作就是向列表中添加一个整数。此测试如下所示:51Testing软件测试网I(Az9LJ b
51Testing软件测试网;_,`kG-b q
        [Test]
5d[?)mr%S.I*I0        public void TestSimpleAdd()51Testing软件测试网I+lJ$D(j0}
        {
/Ihq[5t(B"?|&j-v0            IntegerList list = new IntegerList();
,j#v5|Xc4v7O0            list.Add(5);
*_%m#[b;N+HMj2gL0            list.Add(10);
,\df T%Lr%R D'y#KS0            Assertion.AssertEquals(2, list.Count);51Testing软件测试网U3y B5X,w4\
            Assertion.AssertEquals(5, list[0]);51Testing软件测试网7}P `$WG q4t
            Assertion.AssertEquals(10, list[1]);
9g;{1Jx!}0        }51Testing软件测试网6q:PbQ1t;y&R[ ]y

`ngQCm[z&F0  在此测试中,我选择同时测试两个操作:51Testing软件测试网T-?mJ S#T5I*|

"b%T [qsC7\ es te0
    51Testing软件测试网Zz+a&sX:sFJA
  • 列表正确维护 Count 属性。
  • 列表可以包含两个项。

A^:{GCc,j0  某些测试驱动开发的倡议者提倡测试应尽可能只测试数目,但是如果只测试数目而不测试项目,这对于我而言有些不可思议,因此我所选择的是两者一起测试。51Testing软件测试网bCW'`-D5s:@
51Testing软件测试网-A:C!Hl @R:l
  编译这段代码时,由于 IntegerList 类中没有方法,因此编译失败,为此我加上以下代码进行编译:
z%cRt%Jj2H051Testing软件测试网 g/|7E4n/UD_DA
        public int Count51Testing软件测试网-N;G%K9UiR}5v"k/^jw3F
        {51Testing软件测试网C-kO`ACA
            get51Testing软件测试网9oh9a6J.zS
            {
)fa,D4RC(C _-|0                return -1;51Testing软件测试网!eYIt.X
            }51Testing软件测试网wV)~h,D0?l
        }51Testing软件测试网#L{;[*X{ I-q:I
51Testing软件测试网/ZMSgGc\u
        public void Add(int value)51Testing软件测试网"GXO*[ ku
        {
4x]D`q9Q0        }51Testing软件测试网TL*}'CR$t]'M
51Testing软件测试网 K:PR?Gy
        public int this[int index]51Testing软件测试网(z/A3LoP~x z
        {51Testing软件测试网~7Q7}iz
            get
+F jw8P3^T a ~0            {51Testing软件测试网8]irXl6R7MNC
                return -1;51Testing软件测试网3h0h6}(K b0n+S6f
            }51Testing软件测试网 Q8_0Ls^.DQ
        }
2t*}:s9gZv051Testing软件测试网O/o k W's$K7t8e$FzY
  然后我返回并运行测试,这时它们显示为红色,表示测试失败。这很好,因为它意味着测试实际上已测试出程序错误。现在我可以执行此实现。我可以做些简单的工作,尽管这样做效率不是很高:
a/q"[5B%H;x051Testing软件测试网9z;io&~*c Kb
        public int Count
h1L*P B;b$_SN6i0        {
-\#FJ_p'MG0            get51Testing软件测试网5S!J#zm!^ e$q{cy#_
            {51Testing软件测试网tTaH:inuB
                return elements.Length;51Testing软件测试网U2U]"gsLnP
            }
&`$yvWW0        }51Testing软件测试网fl'yI*Q$^k

i"xPV5o-E Sp0        public void Add(int value)51Testing软件测试网;l`kW!w,nOx6e5g~0I
        {51Testing软件测试网G_mO'C&S!L["i}
            int newIndex;
Qct[ qn(A0            if (elements != null)51Testing软件测试网H+{` r-Z.]I
            {
X*}e nW4w*C0                int[] newElements = new int[elements.Length + 1];
*G/N _'N4y"mAy7a,gD0                for (int index = 0; index < elements.Length;51Testing软件测试网5@ G@z ? j/L?
                     index++)   
UX ak bz;y r4`O\0                {
K-g s.@n/P:^0                    newElements[index] = elements[index];
I-W*A4tk b/A0                }51Testing软件测试网ow'Y2m\$m/i"lr
                newIndex = elements.Length;51Testing软件测试网;HD7\']y}+|6gw
                elements = newElements;51Testing软件测试网)u*e |e6TZzi
            }
:{'p%TL`dZ8f0            else
SKR5nl^]-FS$j0            {51Testing软件测试网uhq'N1D
                elements = new int[1];
P%rT9a%i$mES0                newIndex = 0;
'Y|@t5szh3E+_0            }51Testing软件测试网r x%R3L A$}*h8]"h
            elements[newIndex] = value;51Testing软件测试网%n+`;o)K M'Y
        }
9Z7@;J wmL`JY0
#g7?PvQK0        public int this[int index]
W"IA;w(iR|x0        {51Testing软件测试网]2o$q,i7~W6a
            get
5Q.zpx(jq.gJ.M(O0            {
om;fz]3xF0e:W0                return elements[index];
a,J} [z0            }
\7hh0~az K{0        }
$t&n9f4yG!c4|.t0
0^'Q RT*@i@wG0现在已经完成类的一小部分,并已经编写了可确保其正常工作的测试,但我仅仅测试了项目中很少的一部分。接下来,我要编写一个用于检查 1000 个项的测试:
YI_ y'B-zg3G q0
kUy7p`a6tLN6k0        [Test]51Testing软件测试网;u#i:@%r Gf mE
        public void TestOneThousandItems()51Testing软件测试网"s/w YK b:|+u
        {
M|a_9IH5E"BJ(|4k0            list = new IntegerList();51Testing软件测试网I9Q)X6z-x8l
51Testing软件测试网'gSW M+]-m|J
            for (int i = 0; i < 1000; i++)51Testing软件测试网3R6ra&i u3SGM3g
            {
NjCRG![ i"KU0                list.Add(i);
bnR9d9\)^0            }51Testing软件测试网T&D ~U8FrGV@
51Testing软件测试网$I:[.c4T} v o/^
            Assertion.AssertEquals(1000, list.Count);
:B7j9A u5ac;~Q0            for (int i = 0; i < 1000; i++)51Testing软件测试网g3M$S+Y;Ub
            {
~sib R_3{)E0                Assertion.AssertEquals(i, list[ i ]);51Testing软件测试网 ?+z(ee'H^
            }51Testing软件测试网zZN!F/cI9g~e
        }
x#Q3r_,DO-|0
#^u7c7bS~%t-V |0此测试运行正常,因此无须进行任何更改。51Testing软件测试网 L5t!N+F1PL
51Testing软件测试网K9|Q$rMUL*t
添加 ToString() 方法51Testing软件测试网1FvpD)K jd2QA2E
接下来,我将添加测试代码,以测试 ToString() 能否正常运行:51Testing软件测试网HWRSX/yf
51Testing软件测试网} HJ1c&G p(cfX)Ct
        [Test]
}k7L:fO^y Y%T]0        public void TestToString()51Testing软件测试网b|^v]4U*l9bG
        {51Testing软件测试网*O9Mf#G ^rr
            IntegerList list = new IntegerList();51Testing软件测试网%L"M7[$o5`h} LX+C1@
            list.Add(5);
;Bw3|8wPV+X#p0            list.Add(10);
Y4Nc1TU4~t0            string t = list.ToString();51Testing软件测试网is8_k9xC y+b
            Assertion.AssertEquals("5, 10", t.ToString());51Testing软件测试网.g%gC+s~7^I
        }
x2Y1it3tc fw051Testing软件测试网W@z"vi*wn
  失败了,没关系。以下代码可以使其通过:51Testing软件测试网&iG{-Hrj*p

{(f"t['Y2\S)g~)]0        public override string ToString()51Testing软件测试网T9`Xl]
        {51Testing软件测试网/?2a AR0G#n)k
            string[] items = new string[elements.Length];51Testing软件测试网? FM7]Ltp%t
            for (int index = 0; index < elements.Length; index++)
T"SH6Pj*z \Y0            {51Testing软件测试网W{-Z"G3?,Hf2w
                items[index] = elements[index].ToString();
b1D2`8O?0            }
8gf1W_RY|'X6xU0            return String.Join(", ", items);
)K|` ib6pJX0        }
B:ri#}u](uk0
_]"pIipZq0启用 Foreach
gDp$]q6J0  许多用户希望能够使用 foreach 遍历我的列表。为此,我需要在类中实现 Ienumerable,并定义一个单独的用于实现 Ienumerable 的类。第一步,测试:
[;oOT j2{#@051Testing软件测试网*xd;Svsie3Rwl
        [Test]51Testing软件测试网r Bz1?.u
        public void TestForeach()
i;zFaA`"i!te8m/{0        {51Testing软件测试网M jR^8hW
            IntegerList list = new IntegerList();
Mtp9L \+|:B-hQ0            list.Add(5);51Testing软件测试网5Hd w"n,y4{L
            list.Add(10);51Testing软件测试网-dQ3a N EN\:MH
            list.Add(15);
_0Bb `7Qp0            list.Add(20);51Testing软件测试网 s;^ I!H,U9xz
51Testing软件测试网(XU9v&NpUj
            ArrayList items = new ArrayList();
n5o&e7T0{B051Testing软件测试网S ywz$mx
            foreach (int value in list)51Testing软件测试网ul,v"k^p
            {
[? xt|Hg F7h0                items.Add(value);51Testing软件测试网 d.y.u_bi'j {o1US]
            }
VP|nX0
_3pUn?9vNc:E0            Assertion.AssertEquals("Count", 4, items.Count);
@1Ffl6iH0            Assertion.AssertEquals("index 0", 5, items[0]);
l Fs _Oa-lz0            Assertion.AssertEquals("index 1", 10, items[1]);51Testing软件测试网Ps'r Q$w*ec
            Assertion.AssertEquals("index 2", 15, items[2]);
n1dL;v)w@V0            Assertion.AssertEquals("index 3", 20, items[3]);51Testing软件测试网)U0\:j@ N2[+{/An
        }51Testing软件测试网:N Z|rqahb
51Testing软件测试网'sW0a_,U\a.@
  我还通过 IntegerList 实现 IEnumerable:
0^$x!i"Mh051Testing软件测试网sE$fthq6IH]
        public IEnumerator GetEnumerator()51Testing软件测试网3Y0yP9p+g
        {51Testing软件测试网@a^A{#}8j
            return null;
7O&|+t/x | p0        }51Testing软件测试网9U0] Z4gc Y q}C9v

\[L&`3R2` \:] Cb]v0  运行测试时,此代码生成异常。为了正确地实现此功能,我将使用一个嵌套类作为枚举器。51Testing软件测试网r~1S4}t

't3ov^?MEg;Q0    class IntegerListEnumerator: IEnumerator51Testing软件测试网/r.u#LSR
    {51Testing软件测试网4x\x m.xh"HD2z4U
        IntegerList    list;51Testing软件测试网B!oP ?Y5Ko t;A
        int index = -1;
;?s+TE9}#w$X{)K051Testing软件测试网V%o.r"L.u1U R
        public IntegerListEnumerator(IntegerList list)51Testing软件测试网 c-d0v`(d*F ]Y
        {
6ElYm] U0            this.list = list;51Testing软件测试网_)Q8AP:e8?Kh k
        }51Testing软件测试网z4[ c&a l!r
        public bool MoveNext()
"Z2~vE O0        {
3QF,U)B Ij_$A0            index++;51Testing软件测试网T4Sj^(i z(K:]P
            if (index == list.Count)51Testing软件测试网(Y9i"B\ m-T/P
                return(false);
$Ca'MQ}h,l#^3d0            else51Testing软件测试网o b r ZbD
                return(true);51Testing软件测试网:?C2N1L$L i F0oT2?
        }
!i6dZi4VTd*Rw y0        public object Current
{)Qf{3Y'Ka0        {51Testing软件测试网PN{J#_&}%?}q+]
            get
(E/H,m9Y,}*j.Yqg3Z0            {
"u&g v$N#[_RWR.an0                return(list[index]);
:J]%j1bex`|0            }51Testing软件测试网8L5\4}s'XrFs
        }
8BL[@ \r*uG2cH0        public void Reset()
+c n,[t)z2^YYGF(@0        {
%E3lCS ioXv0]&E0            index = -1;
X{6HM#p P0        }
(`a'n9ce@K,B!oK0    }51Testing软件测试网n(GR4J6g({M
51Testing软件测试网^*\3M'Rf q s
  此类将一个指针传递给 IntegerList 对象,然后只返回此对象中的元素。
M&g]4c{X X8Kt0
}*L~`t e0  这样,便可以对列表执行 foreach 操作,但遗憾的是 Current 属性属于对象类型,这意味着每个值将被装箱才能将其返回。此问题可采用一种基于模式的方法加以解决,此方法酷似当前方法,但它通过 GetEnumerator() 返回一个真正的类(而非 IEnumerator),且此类中的 Current 属性为 int 类型。
ExN7|3sa-T051Testing软件测试网+JMrTygk7B
  然而执行此操作后,我要确保在不支持该模式的语言中仍然可以使用这种基于接口的方法。我将复制编写的上一个测试并修改 foreach 以转换为接口:
eXmF,N4rm&Dl0
"ZQNp)n,nT0            foreach (int value in (IEnumerable) list)51Testing软件测试网 W` bU$~$C }

nU S@q8Kac}0  只需少许改动,列表即可在两种情况下正常运行。
DZ&Rq4tw0

TAG: 单元测试

 

评分:0

我来说两句

Open Toolbar