单元测试实践

接受测试驱动开发也已经有些时光了,仍然只能依靠ide来创建一些测试对象,即使偶尔自己写几个测试用例,也是环境相关、依赖多多的实例,为什么我们的代码就需要单元测试呢,部分是自己的想法,部分是与几个朋友讨论收获的,浏览了<>之后不免想记录下自己的想法了。
自从有了代码就有了单元测试,就是最简单的debug调试也算作单元测试的一部分吧;测试驱动开发是在单元测试滞后产生的一种软件开发的方法学。只要有单元测试,它本身就含有驱动软件设计和代码重构的思想在里面。我们为了代码能够被单元测试,在设计阶段我们就会考虑怎么测试代码,同时测试用例也是代码的第一个用户;我们也需要为了测试了对代码进行重构。
1.单元测试的验证性:任何代码都是有一定缺陷的,单元测试能够尽量的发现代码中的问题和缺陷,验证了设计、业务逻辑、一些相关的特殊领域对象(一些Javabean等),保证了代码的完整性。从功能上对代码进行了验证,至少证明代码不存在功能上的问题。
2.单元测试的探索性:对于单元测试同样我们首先有一个测试列表,这个测试列表精心设计和长期习惯的积累,可能它就存在你的脑子里面。对于列表中的测试点,我们只测试可能出错的点;象那些不依赖于环境能够被间接的测试的点我们没有必要一定去测试。对于那些必要的测试点中,同样有业务逻辑的测试、模拟异常条件测试、对于预期的测试,需要我们良好的设计来实现它。在这个测试列表之外的是为测试区域,我记得哲学上有一个道理,一个圆越大,那么它与未知世界接触的区域就大。我们是不是也在做这种用例呢?单元测试就是尽可能保证我们所知道的每一个区域是“安全”的。
3.单元测试促使设计:
代码==è单元测试==è重构==è单元测试回归
首先我们对于代码进行单元测试,无论代码如何,设计如何,要保证现在代码是能够被单元测试的,这时候代码遇到了它的第一个用户:单元测试。代码如何被测试,考验代码的适应性和对变化的承受力。只有不断的重构才能使得测试更简洁。这里需要我们对于代码的重构设计成竹在胸,23个设计模式是不可或缺的方法。
4.测试用力是代码的使用手册:代码如何使用,可能客户不能很快知晓,测试用例就是一个demo,只要客户按着测试用例一样使用我们的代码,保证我们的代码一定能够被正确的使用。
4.单元测试的回归性:测试过的代码、功能等,不能证明随着时间的推移,在频繁构建之下,永远是好的。回归测试保证了在当前的测试用例之下当前的代码的良好性,是一张代码质量的协约书。谁都能“误”以为代码满足了编写者的设计意图。如果代码不能通过单元测试了,可能就是被损坏了。保证了重构中系统的完整性和一致性,随着代码的不断递增,通过机器自动保证代码测试无需人来参加。
5.防止衰退,减少调试:利用测试的回归性来进行代码的衰退,使得自动测试必须存在,他是我们代码的“守护神”(如果你也想为你的代码增加一个神,就从单元测试开始吧)。在代码、测试、重构的过程中,也是不断调试程序的过程,良好的单元测试能帮助我们发现错误和定位错误,逻辑单元测试本身就是一个细粒度的测试。
6.团队协作的可能:在一个团队,成员之间是互相信任的。我们通过什么来保证代码的可信任,单元测试让我们的代码是可以依赖的。有了单元测试的习惯,你不会在代码没有测试之前就提交它。模块之上的功能测试也是很粗糙的,对于代码的逻辑和集成性还是心有余而力不足。
突然想到,单元测试存在一块充满地雷的区域上的排雷器一样。我们验证什么地方有雷,什么地方没有;我们探索那些未知的区域;通过不断的排雷来评估这个区域的安全性;利用回归的探测来证明有没有新地雷扔到已测地区。

写好代码

有一段时间一直在想,什么样子的代码是一个好的代码、高质量的代码,这些是在福建的时候写的,已经过去有一些日子了。想到那些日子里,承担者项目上的一些压力,工作之间
依然在思考代码,依然在读 软件敏捷开发 这本书。
软件设计的最终体现为源代码,满足设计标准的唯一软件文档就是源代码清单。源代码就是设计。具有灵活性、可维护性和可重用性的良好架构设计会带来高质量的代码,高质量的代码必然有良好的架构设计。
如何衡量软件的好坏呢?
僵化性(Rigidity):关联的地方太多,难以改动;实际发生改动之后,许多因改动带来的影响自己难以预测到,往往需要在庞大的代码中搜寻变动。
脆弱性(Fragility):关联了概念无关的地方,出现新问题的地方与改动的地方没有概念上的关联。
牢固性(Immobility):系统重能够被重用的地方难以抽取出来,需要巨大的努力和风险。
粘滞性(Viscosity):包括软件的粘滞性和环境的粘滞性。软件的粘滞性发生在保持系统设计的改动方法比那些破坏设计的生硬改动手法更难应用时;环境的粘滞性发生在环境迟钝、低效时,比如导致大规模重编译的改动或者需要几个小时check in几个文件中的改动。
不必要的复杂性(Needless Complexity):代码中预置的那些处理潜在变化的代码,致使设计中含有绝不会用到的结构。
不必要的重复性(Needless Repetition):开发人员忽视了抽象,对于系统的改动开始变得困难起来。
晦涩性(Opacity):模块难以理解。用清晰、富有表现力的方式编写代码。
――――读<>
这些都是软件在腐化的臭味道,软件的腐化是由代码来表现出来的。现在我对于代码的理解:
可读性:代码不仅是给用户写的,也是给team成员读,(对我们而言,还要给维护人员读)。只有代码具有可读性,才能具有好的可维护性、才能被复用。读好的代码能提高,读不好的代码也能发现问题。通过代码评审来提高团队的代码质量是一种非常有效的方式,能够直接影响了设计。
可测试性:测试用例是代码的第一个用户,如果代码难以测试,可以说是很难用,就更难复用。
复用性:保持代码的抽象性,代码能写到没有一点能让自己copy的地方只是第一步。如果我们自己设计的代码自己都不能进行复用,还谈什么系统复用,还谈什么提高复用性。我们不能等有一个万能的复用框架别人做好给我们使用,我们应该从建立自己的复用库开始,可以建立自己的CVS(CVSNT)来管理自己的源代码库。(在不断抽象之下,就是分析的结果,一般一个包的抽象类有50%左右的时候这个包是最稳定的。)
可维护性:代码要有好的复用性,必须有良好的架构,易读,易于测试,对于改动灵活。程序在运行中不可能不遇到各种各样的问题,对于我们的项目可能是对于现场运行环境的细小的变更,可能会是运行中的性能的问题?会不会导致系统的崩溃呢?能够提供观察系统运行状态的良好的接口,能够提供分析系统异常信息的合理的结构。当系统出现异常的时候,我们不希望系统立刻就会崩溃,我们不希望只是看到异常。

String In Java

JDK1.4中关于String的几个实现方法:

public String(String original) {
    this.count = original.count;
    if (original.value.length > this.count) {
        // The array representing the String is bigger than the new
        // String itself.  Perhaps this constructor is being called
        // in order to trim the baggage, so make a copy of the array.
        this.value = new char[this.count];
        System.arraycopy(original.value, original.offset,
        this.value, 0, this.count);
    } else {
        // The array representing the String is the same
        // size as the String, so no point in making a copy.
        this.value = original.value;
    }
}

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }

    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = count;

        if (n == anotherString.count) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = offset;
            int j = anotherString.offset;
            while (n-- != 0) {
                if (v1[i++] != v2[j++])
                return false;
            }
            return true;
        }
    }
    return false;
}

如何开始编码

1.程序run起来。
2.程序能够被单元测试。
3.考虑引起变化的因素。
4.面对引起变化的因素,重构。
5.如何对变化进行单元测试,即面对了变化,如何进行单元测试。
6.如何做准确性的单元测试,如何做边界的单元测试。
7.如何保证代码在引起变化的时候仍然具有很好的可维护性(维护中需要好的单元测试做为支持的)。

Go Well, NOT Fast

“We often blame managers for schedule pressure. We often complain that our companies set unreasonable deadlines and have unrealistic expectations. This might be true, but it’s only half the problem. The other half, the most important half, is within us. We consider our worth as programmers to be more associated with speed than with quality. And that’s a tragedy; because it leads us to create messes. It leads us down the slower path. By rushing we ruin our chance to truly go fast.”

Today’s Links:

http://www.artima.com/weblogs/viewpost.jsp?thread=51769

设计模式之分层实现(Layering Implements)

原文第一次发布:http://www.blogbus.com/navigating-logs/455608.html
今天去听课,老师讲到persistence layer的时候,提到dao的出现主要应付数据库的移植,可能他出现的最初的原型确实如此,仔细想一下,真实的enterprise application的数据源有多少在移植呢,就我们一般而言,一般相对都是很固定,在软件的最初设计阶段就已经选型了。(在OO中讲,如果一个变化可能要等三五年,那么你就得去考虑现在考虑这个变化是不是有意义?)

现在在设计模式的基础上,提出了企业架构应用的诸多模式,这么多模式都是在依赖于设计模式来实现的。那么企业架构应用的诸多模式的价值何在?就persistence layer而言,抽象出数据持久化的一般问题解决方案,看下面的图:

在与数据源(一般都是大型数据库)交互的时候,对于网络的依赖是必不可少的。基本的原则:minimize distributed communication(今天老师刚讲的)。在持久层,既是DAO还需要做一定的数据缓冲,减少网络访问,对于本地persistence object的管理才是比较重要的。对于我们的工作中,不一定要做数据缓冲,但是应该考虑对于网络访问是否最小化了。

为什么要分层?

  1. can understand a single layer as a coherent whole without knowing much about the other layers。就如现在我们在设计dao的时候,很少去考虑什么business logic,单纯的去研究数据的访问。
  1. can substitute layers with alternative implementations of the same basic services.这个问题就跟接口有关了,还要讨论的。很好的满足了DIP(依赖倒置原则);
  1. minimize dependencies between the layers.对象之间的应用不再是杂乱五章的,大家都依赖于抽象。
  1. Layers make good places for standardization.现在datasource layer(persistence layer),domain layer,presentation lay.经过大家的群策群力,每一个层次都有了模式,加快了解决问题的速度,使得开发人员集中于一些必要的挑战。最近有提出了service layer.(今天老师讲了The Business Delegate Design Pattern,但是老师却讲到了field和object的对比,这个方式我们早已讲过了,这次SUN单独提出来,它的意义远不再这里,我理解和service layer有一定的关系。)
  1. Once you have a layer built you can use it for many higher level services.这也是分离出层的原型吧。

我觉得Pattern of Enterprise Application Architecture讲分层的第一句话特别好:

Layering is one of the most common techniques that software designers use to break apart a complicated software system.

依然有了分层,就产生了层,有了层,就有了层与层之间的关系,那么就面对这些 层关系的设计和实现问题。

在企业应用架构有三层(也有四层的分法),这些层之间必然会有一些依赖,我们都知道依赖有一个原则:依赖倒置原则(DIP)

  1. 高层模块不应该依赖于底层模块。二者都应该依赖于抽象。
  1. 抽象不应该依赖于细节。细节应该以来于抽象。

对应于分层,较“高”层包含了什么,包含了策略选择和业务模型,而这些真是应用程序的价值所在,如果“高”层以来于“低”层,那么“低”层的变化就会影响到“高”层。

更不可能去让“低”层依赖于“高”层。

现在只有一种可能,“高”层和“低”层是独立的。这不也正是我们使用framework的价值所在吗?

这样层与层之间的依赖就必须是抽象,而不是实现和细节。

Service layer的出现,是为了面对客户层接入集成了一系列高效的操作集。主要目的提高数据存取和业务逻辑的利用率,减少重复调用。

Serverice layer是一个边缘层(boundary)。

转:《敏捷软件开发:原则、模式与实践》中文版序

“最好的软件开发人员都知道一个秘密:美的东西比丑的东西创建起来更廉价,也更快捷。构建、维护一个美的软件系统所花费的时间、金钱都要少于丑的系统。软件开发新手往往不理解这一点。他们认为做每件事情都必须要快,他们认为美是不实用的。错!由于事情做得过快,他们造成的混乱致使软件僵化,难以理解。美的系统是灵活、易于理解的,构建、维护它们就是一种快乐。丑陋的系统才是不实用的。丑陋会降低你的开发速度,使你的软件昂贵而又脆弱。构建、维护美的系统所花费的代价最少,交付起来也最快。”

——摘自“Robert C. Martin《敏捷软件开发:原则、模式与实践》中文版序”

Robert C. Martin《敏捷软件开发:原则、模式与实践》中文版序

  除了我的家庭,软件是我的挚爱。通过它,我可以创造出美的东西。软件之美在于它的功能,在于它的内部结构,还在于团队创建它的过程。对用户来说,通过直观、简单的界面呈现出恰当特性的程序就是美的。对软件设计者来说,被简单、直观地分割,并具有最小内部耦合的内部结构就是美的。对开发人员和管理者来说,每周都会取得重大进展,并且生产出无缺陷代码的具有活力的团队就是美的。美存在于所有这些层次之中,它们都是本书内容的一部分。
  软件开发人员如何学到创造美的知识呢?在本书中,我讲授了一些原则、模式以及实践,它们可以帮助软件开发人员在追求美的程序、设计以及团队的道路上迈出第一步。其中,我们探索了基本的设计原则,软件设计结构的通用模式以及有助于团队融为一个有机整体的一系列实践。由于本书是关于软件开发的,所以包含了许多代码。仔细研究这些代码是学习本书所教授的原则、模式以及实践的最有效方法。
  人们需要软件—需要许多的软件。50年前,软件还只是运行在少量大型、昂贵的机器之上。30年前,软件可以运行在大多数公司和工业环境之中。现在,移动电话、手表、电器、汽车、玩具以及工具中都运行有软件,并且对更新、更好软件的需求永远不会停止。随着人类文明的发展和壮大,随着发展中国家不断构建它们的基础设施,随着发达国家努力追求更高的效率,就需要越来越多的软件。如果在所有这些软件之中,都没有美存在,这将会是一个很大的遗憾。
  我们知道软件可能会是丑陋的。我们知道软件可能会难以使用、不可靠并且是粗制滥造的;我们知道有一些软件系统,其混乱、粗糙的内部结构使得对它们的更改既昂贵又困难;我们还见过那些通过笨拙、难以使用的界面展现其特性的软件系统;我们同样也见过那些易崩溃且行为不当的软件系统。这些都是丑陋的系统。糟糕的是,作为一种职业,软件开发人员所创建出来的美的东西却往往少于丑的东西。如果你正在阅读这本书,那么你也许就是那个想去创造美而不是丑的人。
  最好的软件开发人员都知道一个秘密:美的东西比丑的东西创建起来更廉价,也更快捷。构建、维护一个美的软件系统所花费的时间、金钱都要少于丑的系统。软件开发新手往往不理解这一点。他们认为做每件事情都必须要快,他们认为美是不实用的。错!由于事情做得过快,他们造成的混乱致使软件僵化,难以理解。美的系统是灵活、易于理解的,构建、维护它们就是一种快乐。丑陋的系统才是不实用的。丑陋会降低你的开发速度,使你的软件昂贵而又脆弱。构建、维护美的系统所花费的代价最少,交付起来也最快。
  我希望你能喜爱这本书。我希望你能像我一样学着以创建美的软件而骄傲,并享受其中的快乐。如果你从本书中略微看到了这种快乐,如果本书使你开始感受到了这种骄傲,如果本书点燃了你内心欣赏这种美的火花,那么就远超过我的目标了。

创建对象的两种方式

One is to create a object with a rich constructor so that its at least created with all
least created with all its mandatory data.
The other is to create o object an empty object and then populate it with the mandatory
data.
The former could have a well-formed object from the start.This aslo meants that, if you
have an immutable field, you can enforce it by not providing any method to change its
value.
The problem with a rich constructor is that you have to be aware of cyclic references.
Avoiding this requires special case code, often usingg lazy load.
You can do this creating an empty object.Use a non-arg constructor to create a blank object
and insert that empty object immediately into other object.The way,if you have a cycle,
other object will return an object to stop the recursive loading that two objects reference
each other.
Using an empty object like this means you may need some setters for values that are truely
immutalbe when the object is loaded.A combination of a naming convention and perhaps some
status-checking guards can fix this.You can alse use reflection for loading data.

(Spring supports Setter Injection very nice.Sometimes using rich constructor to create a object
is preferable after you evaluating.)