301
社区成员
发帖
与我相关
我的任务
分享目录
本单元除了按照JML规格写主体部分的代码以外,也是本学期第一次要求对代码进行测试,虽然在上学期的OOpre课程中对Junit测试有了一定的了解,但因为长时间没有进行Junit测试的编写同时本身对Junit测试掌握的不够熟练,在这单元的Junit测试编写过程中还是遇到了很多困难。
黑箱测试主要关注的是程序的输入和输出,我们在进行黑箱测试时,基本不需要对代码的逻辑以及结构有十分详细的了解,只需要根据指导书或者规格说明书等输入数据,通过观察代码输出的结果来判断代码的功能是否正确,我们在使用测评机测试代码的时候就使用的是黑箱测试;而白箱测试则关注的是程序的内部逻辑和结构,在进行白箱测试时,需要对代码的整体框架、内部逻辑和数据结构有较为清晰的了解,通过设计针对性的测试用例来检查代码的各个部分的功能是否正确,我们本单元作业中对指定的方法进行测试就采用的是白箱测试;在我看来,相比于黑箱测试,白箱测试更能够完全覆盖代码的所有分支条件,从而保证代码的正确性与可靠性,但思维上的难度比黑箱测试更大,黑箱测试更多的是从用户的角度来对代码的功能进行测试,而白箱测试更多的是从开发者的角度对代码的逻辑结构等进行测试。
单元测试是对代码中的最小单元进行测试,在本单元的作业中,我们对指定方法进行的测试就属于单元测试,单元测试可以帮助我们对代码的各个单元进行较为全面的测试,能够通过测试各个最小单元保障整体的正确性。
功能测试是测试我们的代码能否按照指导书或规格说明正常运行,是否拥有满足要求的功能,上文中的黑箱测试、白箱测试等都属于功能测试。
集成测试是在进行完单元测试的基础上进行的,在保障各个单元功能正确的情况下,我们还需要保障各个单元之间能够进行正确的协作以及交互,这时候就需要进行集成测试,将各单元集成起来测试单元之间的正确协作。
压力测试是测试代码在极端情况下能否正确运行,有利于我们发现代码性能不足的地方并进行改良,压力测试主要是通过构建极端数据来进行测试的,强测和互测环节主要也是通过压力测试来发现代码存在的问题的。
回归测试是在我们对代码进行完修改后,用之前的测试样例再次对代码进行测试,观察是否已经修改完原来的bug以及是否产生了新的bug,bug修复环节也是一种回归测试。
我在测试自己代码的时候,主要用的方法就是通过是用测评机生成大量随机数据来进行测试,在自己编写测试代码的时候,构造数据时主要采用的策略就是根据JML的描述以及自己对代码的理解来构建数据,从而覆盖尽可能多的分支情况。
本单元的架构除了各个异常类外,主要有的部分有Main、MyPerson、MyTag、MyNetwork、MyMessage及其子类等部分,其中Main类主要就是运行代码,主要的结构由后面几个类组成,除了这几个类以外,我还自己添加了一个并查集的类用于辅助方法的实现。与前两个单元需要自己构建设计架构不同,本单元作业整体的架构JML已经基本构建好了,只要根据JML描述的去实现方法,整体的框架就不会有问题。
本单元作业主要就是有MyPerson、MyTag、MyNetwork、MyMessage及其子类几个类构成邻接图/表,为了便于维护以及JML规格中方法的实现,我主要是采用HashMap容器来存储管理数据,只要按照JML来写,构建出的图应该都是大同小异,重点应该在于维护策略。
维护策略应该算是本单元作业中难度最大以及最为重要的一部分,JML的描述只会保证正确性而不能保障时间复杂度,完全按照JML来写虽然难度不大,但在互测和强测环节中就很容易出现大量超时数据,而这就需要我们自己来选择合适的维护策略来降低时间复杂度。在JML的描述中,queryTripleSum、queryBlockSum、queryValueSum等方法中会出现两重甚至是三重循环,这就使时间复杂度达到n的平方甚至立方,就很容易超时,需要采取合适的维护策略来代替直接计算的方法。以queryValueSum为例,我是设置了一个valuesum的变量来进行维护,然后addPerson、modifyRelation等方法中对valuesum进行修改,最后在实现queryValueSum方法时直接返回valuesum的值即可,其他方法的维护也大致都采用的相似的策略,这种策略能够有效降低时间复杂度,但其思维难度较大,需要全面考虑所有需要进行维护的方法,如果有一处有问题就会让整体的维护出现错误。除此以外,我还在完成本单元第一次作业时引入了并查集来实现对方法的快速查询,主要是通过设置根节点压缩查询路径来实现的。
因为本单元作业主要就是根据JML来完成代码,所以本单元代码的正确性基本都问题不大,主要出现的问题是代码的性能问题。我在第二次作业第三次作业中刚开始都是对queryValueSum等方法是按照JML描述直接计算的,这也导致我代码时间复杂度很高,在第二次作业第三次作业的互测强测中均出现了超时数据,后来我就采用上面所说的维护策略进行改进,以valuesum的维护为例,刚开始我在MyPerson类中设置了一个belong属性存储person在哪些tag里面,但有一个问题就是不同的人可能会有id相同的tag,我从人到tag的处理过程中就有可能出现将不同的人相同id的tag视作同一个tag的错误情况,在进行完修改后我的代码性能也有所优化;在第三次作业时还碰到一个问题,我中测第二个点提交一直是wa,刚开始我以为是之前的维护还存在问题,就牺牲时间复杂度改成直接计算以保障正确性,但这个点一直wa,而且用测评机也一直没测出问题,因为这个点一直没有数据也不知道怎么修改,这也导致我没能进入互测,但强测数据出来后我除了最后一个点超时外其他点都没问题,所以现在也还不知道我代码的问题究竟在哪。总而言之,规格与实现不是完全相同的,应当适当分离,规格的描述仅仅能够保障功能的正确性而无法保障性能,需要我们对规格进行充分的理解,再根据自己的理解以及JML的描述选择合适的实现方法,这样才能在保障正确性的情况下尽可能保障代码的性能。
除了上文提到的数据生成以外,对规格的检查测试也是Junit测试的一个重要部分,充分进行规格检查可以更好地保障代码的正确性。进行规格检查需要我们按照JML规格描述进行测试代码的编写,需要对JML中的requires、ensures等部分编写断言进行测试,同时也要检查pure的方法前后状态是否一致除此以外,我们在编写数据的时候,还可以根据JML对于异常的描述编写相关数据,观察异常处理等是否正确。总之,我们在编写Junit测试时也需要按照JML描述进行相关测试及数据的编写,在Junit测试与规格描述一致性较高的情况下,我们的代码一般来说就能得到较为充分的测试。
本单元主要是根据JML描述来编写代码,刚开始时对JML完全陌生时会感觉阅读十分困难,但在对JML有一定了解后就能做到较快理解。总而言之,本单元的作业难度相较于前两个单元的作业难度有多下降,除了充分理解JML以外,主要难点在于选择适合的实现维护策略以在保障正确性的基础上尽可能优化时间复杂度等提高代码性能。在对指定方法进行测试的时候也充分锻炼了我们编写测试的能力。本单元中,我认为我最大的收获就是了解了JML的基本语法语义,掌握了根据JML给出的规格编写相关代码的能力,也积累了根据规格编写代码的经验,在日后团队编写代码的过程中应该也会起到作用。