细思量之--等价类方法

小题大做之【等价类】

前言

对于做测试的同学来说,“等价类”用例设计方法可以说是最简单、大家最熟悉,使用最频繁的一种用例设计方法了。

就等价类和其关联的一些内容,近来抽空把平时碰到的一些问题总结了一下,细思之后,其实这个方法也是有一定的复杂性和微妙之处的,当然也非常有意思,这里分享一下,欢迎大家指正和补充。

1. 等价类方法简介

对等价类这个用例设计方法,通用解释大概整理归纳为以下几点:

  • 在黑盒功能测试中,一个功能点的测试一般主要涉及三块:输入、处理、输出。等价类用例设计方法最主要适用于测试功能的 “输入” 部分;
  • 完成等价类划分:
    • 先将所有的可输入数据划分为多个不相交的子集,每个子集内的输入数据统称之为一个 “等价类”;
    • 再将所有的 “等价类” 划分为两类:“有效等价类” 和 “无效等价类”。有效等价类中的输入数据符合规格说明中的要求,无效等价类中的输入数据则不符合规格说明中的要求。
  • 等价类划分方法使用前提:
    • 可输入数据量过大时,测试不能穷尽,即不可能去测试每一个可能的输入值;
    • 每个等价类中的每个元素在程序处理中的方式相同;
    • 每个等价类不相交。
  • 设计输入规则:
    • 从每个等价类中选择一个(代表)值进行测试,如果该值测试通过,则代表本等价类中的每个值都可以通过此项测试,反之亦然;
    • 一次测试可以同时跨越(覆盖)多个有效等价类,但不可以跨越(覆盖)多个无效等价类。原因是如果系统出现问题,无法准确定位出是哪个无效等价类里的数据导致的,为了精准定位问题,就要求每个输入只覆盖一个无效等价类即可。

举例说明:

2. 追问几个为什么?

在划分等价类的时候,下意识会按我们的常识进行分类,如:

  • 把字母划分为一类,或者再次细分为“大写字母”和“小写字母”两个子集,每个子集包含26个字母;
  • 把数字划分为一类,可以输入的包括0到9一共10个数字;
  • 把特殊符号划分为一类;
  • 其它 。。。。。。


那么,这么划分的依据是什么?

  • 是依据我们常见的字符形式,还是依据键盘上可见的所有符号呢?
  • 数字0到9是否一定等价呢?
  • 字母是不是一定只有26个呢? 汉字又有多少个呢?
  • 特殊符号是什么?
  • 上面例子中为什么不区分整数、小数和负数呢?
  •  。。。。。。

3. 思考一:到底什么是等价?

见字知意,先从这个名字分析入手。“价”是价值,“等”是相同,简单看就是如果多个输入值具有相同的价值,则这些值就算等价了。那么价值是相对于谁来讲的呢?是对于用户的价值,还是对于测试人员的价值?这里认为应该是对于被测系统的价值!

对于黑盒测试,被测的功能可能是一个函数、一个接口或者是一串方法的链路。如果两个输入的数据,在被测系统中拥有相同的处理逻辑,那么这两个输入的数据就是等价的,测试的时候只选取任意一个即可。

针对等价类划分的依据,我们试着打破黑白盒的限制,举几个简化的例子说明一下。

3.1. 显而易见的等价类划分

         

如图:

  • 如果输入0、1、5、3.5、-9 这些值,程序都输出N。
    • 此时这些值在被测系统中都拥有相同的处理逻辑,即测试的都是同一部分代码,因此它们就属于同一个等价类。测试的时候,任选其一即可。
    • 至于是否再细分为小数、负数、整数等更小的等价类,其实此处大可不必,因为同一份代码被测试一遍和测试多遍没有区别。
  • 如果输入100、9、28 这些值,程序都输出Y,同样这些值也属于同一个等价类。
  • 综上,根据程序处理逻辑,划分为两个等价类:
    • 等价类1: x <= 5 区间内的数值
    • 等价类2: X > 5  区间内的数值

那能否将输出是否相同当做等价类划分的依据呢?

3.2. 不要用输出做等价类划分的依据

       

如图:

如果输入0、1、-9、100 这些值,程序都输出N,但它们能都划分到一个等价类里面么?显然是不可以的。由图可见,x=1的时候,走的是最左侧分支,测试的是最左侧分支上的代码。而当x=100的时候,走的是最右侧分支,测试的是最右侧分支上的代码,二者的处理逻辑明显不同。

因此针对该被测程序,应该划分为3个等价类:

  • 等价类1: x < 5 区间内的数值
  • 等价类2: 5 < x < 10 区间内的数值
  • 等价类3: x > 10 区间内的数值

所以,仅看程序的输出是否相同,不能作为等价类划分的充分依据。

3.3. 分析处理逻辑和路径

       

如图:

看以上这个程序,针对输入Q又如何划分等价类呢?我们借鉴白盒测试的思路试试,该程序一共有6个被测语句,其中2个判断语句(1、4)和4个赋值语句(2、3、5、6)。

  • 设计输入:Q=2,执行后覆盖语句依次为:1 -> 2 -> 4 -> 6,输出:28
  • 设计输入:Q=16,执行后覆盖语句依次为:1 -> 3 -> 4 -> 5,输出:30

上面设计的两个输入,分别将1 - 6的每个语句都测试到了,是否就意味这是一个充分的测试呢?显然还不是,接下来再设计两个输入,考虑语句之间的组合测试:

  • 设计输入:Q=5,执行后覆盖语句依次为:1 -> 2 -> 4 -> 5,输出:20
  • 设计输入:Q=9,执行后覆盖语句依次为:1 -> 3 -> 4 -> 6,输出:32

以上四个输入的设计,也可以看做是遍历程序路径,如下图红色线条所示,明显测试覆盖程度更加充分:

Q=2 【上左 -> 下右】

Q=16 【上右 -> 下左】

Q=5 【上左 -> 下左】

Q=9 【上右 -> 下右】

等价类1: Q < 5

等价类2: Q >= 11

等价类3: Q=5

等价类4: 5 < Q < 11

以上设计实际引入了白盒单元测试中“路径覆盖”的思路,不但遍历每条语句,还要考虑语句之间的组合关系,从而做到最充分的测试覆盖。当然这种覆盖方式成本极高,尤其是程序逻辑复杂的时候,因此公司内一般会要求达到“语句覆盖”或者“分支覆盖”即可的程度,这种覆盖率的统计也是靠类似单元测试工具自动分析得出的,从而作为开发阶段的输出准则或者测试阶段的进入准则,此处不赘述了。

回过头再看“等价”,判断多个输入数据是否等价的依据,是这些输入在系统中的逻辑处理是否一致,如果一致则认为它们是等价的,属于一个输入集合,测试的时候从中选取一个代表性的输入即可。

任何方法的使用不可绝对化,需要结合人员经验、时间、复杂度、优先级等因素综合考量,选择适当的测试粒度,过犹不及。在深挖测试方案的时候,如果得不到明确的处理逻辑,可以尽量和开发人员沟通问询,帮助我们判断等价类的划分标准,争取使我们的测试设计更加精炼准确。

4. 思考二:到底有多少个等价类?

假设某系统前端页面中要求用户输入自己的年龄,典型的等价类用例设计应用场景。

  • 有效等价类好办,选择1到150之间的整数即可。
  • 那无效等价类到底有多少呢? 字母、汉字、小数、负数、0、符号、空格,还没完,字母又可以细分为大写字母和小写字母,也可以分为全角字母和半角字母,除了英文字母还有法语字母、俄语字母,汉字可以分为简体字和繁体字。。。怎么感觉像是子子孙孙无穷匮也。

好吧,用前面分析的方案,这些所有的无效输入,其实内部处理没有区别,因此它们属于一个大的等价类,我们任选一个就行了。

这么干好像也没啥错哈,但是我们再纠结一下下,这些牛鬼蛇神的分类方法到底是从哪里来的,能不能捋一捋,溯溯源。

我们在系统中所有能输入的字符都集合一个地方中 —— 字符集。就从这儿简单说起吧。

4.1. 关于字母类别的划分

4.1.1. ASCII字符编码

计算机出现后,第一个可用于输入的字符集合——ASCII码。里面都有什么呢?

ASCII码中包括大小写英文字母(52个)、阿拉伯数字(10个)、标点及运算符号(33个)、控制字符(33个)等共128个字符,分别用7位二进制数对其进行编码,但一个ASCII码的存储占一个字节(8位,最高位统一为0)。

在这里面我们找到了字母(大小写)、数字和特殊符号的来源,也是平常测试中最常见的输入来源,或者说这是部分等价类划分的依据。

4.1.2. ISO8859系列字符编码

ISO8859是一系列字符编码的集合,目前包括了15个字符集,主要针对欧洲国家的语言进行编码,标准编号分别为 ISO8859-1 至 ISO8859-15,其中ISO8859-1收录了西欧语言,是最常用的一种字符集。其它标准分别针对如东欧语言、中欧语言、希腊语、斯拉夫语等等。

这些ISO8859字符集都以单字节编码,即每个字符用一个字节(8位)来编码和存储,向下兼容ASCII码,即在ASCII码(128个字符)的基础上进行了扩展(又扩展了128个字符)。

 

说到这里,我们在做测试的时候,可以输入的字符类型就多起来了,西欧、东欧、南欧、北欧、斯拉夫。。。,头大了,别着急,一般情况下系统使用了统一的字符集形式,即便输入这些字符也不会导致系统出问题的。但是如果系统涉及到与外部数据的交互,则需要考虑对方传来的数据使用了何种编码方式,要进行相应的转码才可以,否则就会出现乱码。

4.2. 关于汉字类别的划分

4.2.1. GB2312 字符编码

该字符编码标准由中国国家标准总局于1980年发布,适用于汉字处理,共收6763个汉字和682个非汉字图形字符(全角字符),这些字符均使用2个字节来存储。

注意:为了排版的对齐和美观,对字母、符号又设计了“全角”字符,这些字符相对于ASCII码里面的字母和符号,全都变胖了一倍,即用2个字节表示一个字符。而原ASCII码里面的字母和符号我们可以对应称之为“半角”字符。其实全角字母等同于汉字,是我们特有的“中文字母”。

那么在测试的时候,为了严谨,针对“字母”的等价类输入,我们可以规范写成“半角字母”或“英文字母”,尽量减少不必要的歧义产生。

曾经见到过某些系统,注册时输入用户名,通过前端脚本判断,自动将用户输入的“全角字符”转成了“半角字符”,而没有直接给用户弹出“请输入英文字母”的提示,提升了系统的用户感官和易用性。

4.2.2. 其它中文编码

  • Big5(又称大五码):是针对繁体中文的编码标准,主要应用在中国台湾、香港等地区。
  • GBK:针对GB2312字符集的扩展,包含了更多的中文字符。
  • GB18030:针对GBK的超集。

4.2.3. Unicode 和 UTF8

前面提及的各类字符集基本都属于各自不同的国家或地区,编码的相互兼容性是令人头疼的问题,因此后来出现了Unicode字符集,它定义了世界上大部分字符的编码方式,包括字母、数字、符号、汉字等,基本上可以做到一套编码世界通用。随着Unicode的出现,逐渐取代了其它各类的字符集编码。

UTF是一种Unicode字符集的编码方式,简单理解就是Unicode中规定了字符的集合,UTF负责把这些字符转成不同格式的二进制编码,使计算机可以识别。常见的编码方式有UTF8、UTF16和UTF32,其中最常用的是UTF8(或写成UTF-8)。

UTF-8想必大家都不陌生了,无论是前端编码、后端开发还是数据库设计都离不开它,几乎已经成了开发过程中的一个事实上通用的规范标准了。

在测试过程中,我们所有能够输入的字符,在一定程度上说,都来自于UTF-8这个字符集,那么如果对输入字符进行分类,其就可以作为参考依据了。

另外,UTF-8是一种变长的编码方式,一个字符在存储的时候可能会占用1到4个字节不等,像MySQL定义字段类型 varchar(10) 时,表示最多可以存储10个字符,而不是10个字节。如果对存储空间敏感,测试的时候就要注意了,需要分析存储的字符类型及预估存储空间。大多情况下,UTF-8存储英文字母(ASCII码中)占用1个字节,存储扩展字母(ISO8859扩展区域)时占用2个字节,存储常见汉字占用3个字节。

4.3. 小数和负数的输入问题

4.3.1. 先看输入

在进行等价类划分的时候,经常看到有人对数字类型的输入细分为:整数、小数、负数等多个集合,我们再来分析一下在Web环境下这么分类所存在的问题。

在Web环境下,输入的测试一般通过浏览器展示的前端页面来完成,如登录页面、表单页面、查询页面等。而前端浏览器页面展示依靠的是HTML语言,这个语言中并没有数据类型的划分,或者说页面见到的一切文字和输入的字符,其本质都是一段字符串。

我们来看百度首页,查看HTML源码可知,该搜索框是一个“输入”类型的元素控件,其输入的内容都属于“文本(text)”类型,也就是说,不论输入3、3.8、-9 等任何这些数字,其本质都是文本字符:

  • “3”:代表1个字符,等价于ASCII里面的数字字符“3”,占用1个字节;
  • “3.8”:代表3个字符,等价于ASCII里面的数字字符“3”、“8”和符号字符“.”,占用3个字节;
  • “-9”:代表2个字符,等价于ASCII里面的数字字符“9”和符号字符“-”,占用2个字节。

如果在前端输入类型元素中,输入字符135,是怎么编码及传输到后端的呢?

  • 首先把输入拆解为独立的字符:“1”、“3”、“5”
  • 然后分别找到这几个字符的ASCII编码 (本例中这3个数字字符在ASCII、ISO8859、GB2312、UTF8中都拥有相同的编码 ),分别为“0011 0001”、“0011 0011”、“0011 0101”
  • 每个字符占用1个字节

4.3.2. 再看输出

在一定程度上看,用户的输入最终都保存到数据库中了,可以看作是程序的输出。我们知道数据库中的字段是有类型限定的,常见的有日期、文本、整型、浮点等等。接下来我们以MySQL数据库中整型进行分析。

MySQL存储整型数据有四种类型:

  • TinyInt:存储空间占用1个字节。存储有符号数据范围 [-128, 127]
  • SmallInt:存储空间占用2个字节。存储有符号数据范围 [-2^15, 2^15-1]
  • Int:存储空间占用4个字节。存储有符号数据范围 [-2^31, 2^31-1]
  • BigInt:存储空间占用8个字节。存储有符号数据范围 [-2^63, 2^63-1]

如果我们使用int类型,存储数字135,是怎么分配空间的呢?

  • 首先分配4个字节的空间
  • 然后将135转为2进制,为1000 0111
  • 最后将这个2进制数据存入到分配的空间,没有用到的地方补0即可

4.3.3. 综合起来看

同样都是处理135,可以看到前端和数据库端明显不一样,前端是文本text类型,数据库端是整型int类型,说明中间的程序处理,必须要进行一次“类型转换”,将文本类型的135转为整型的135才可以。

回到等价类划分。假设测试一个年龄的输入,此时测试逻辑就抽象为“将输入的字符转为正整型数据存储在数据库中”,在不考虑边界情况下:

  • 类似“135”这种“类整型”字符就属于有效等价类
  • 类似“Abc”、“9.7”、“5%67”这些输入都属于无效等价类,因为它们都无法进行类型转换
  • 类似“0”、“-4”、“-56”这些输入,也属于无效等价类,虽然他们可以进行类型转换,但不满足需求条件(正整数)

另外做个补充。如果是浮点类型,除了考虑以上各种情况,还要注意构造输入数据时小数点的个数,前端如果只限定了输入的数据只能是“数字”和“小数点”两种字符类型,没有校验小数点的个数,那么像“2.3.6”这样的数据,符合前端的限定,但在后端进行类型转换的时候一样也会报错,当然后端接口上对输入的参数数据也要先进行校验。

4.4. 关于空格问题

在进行测试的时候,“空格”往往是个恼人的因素,到底是有效呢还是无效呢?

在输入中存在空格一般有三种情况,前面有空格、后面后空格、中间有空格。而且主要影响前端的显示效果,给用户造成无端的困扰。

前端页面HTML对于空格的显示和其他字符不一样,默认情况下,你的数据中不管有多少个空格,都显示为1个空格,即便在数据库中存在的空格数量是正确的,但是在页面显示效果上只可见1个空格。在用户看来就是3条重复的数据。

对于空格的处理应该纳入开发规范,明确说明在使用数据前必须先自动去除首尾空格,不允许中间使用空格等。

当然如果业务确实需要使用空格,那么前端页面必须对空格进行额外的处理,如预格式化文本,使用实体的方式表现空格等等。

综上,在测试时可以将空格拿出来进行统筹的考虑,而不用纠结置于等价类的那个等价集合中。

5. 思考三:等价类还能干点儿啥?

前面的介绍我们基本都在关注于前端页面的输入部分,尤其是通过键盘可以敲入的字符输入。等价类其实是一种测试的思想,并不仅局限于在前端页面中进行字符输入的测试。

1). 下拉列表

下拉列表是页面中常见的元素控件,列表项有固定写死的,也有从数据库动态读取的。如果一个列表中有多个列表项,怎么设计测试策略呢?

借鉴等价类思想,把列表项按不同的处理逻辑进行分类,每个分类选取一个进行测试。这里的等价类往往有个特点,就是全都是有效等价类,而没有无效等价类。

2). 地址栏构造无效输入

还是用百度首页举例,怎么给那个经典的搜索框设计一个无效字符的输入呢?好像你能输入进去的都能给你找到搜索结果。

观察一下,输入“北京”点击搜索后,输入的字符“北京”出现在了地址栏中(典型的Get方式提交表单),把这个地址栏中的内容都拷贝出来,粘贴到一个记事本中,发现“北京”二字又变成了一段十六进制编码(以百分号分隔),用前面的知识分析,这是“北京”二字的UTF-8编码。

到这里就好办了。在搜索框中输入字符和在地址栏中输入字符的UTF-8编码,二者的提交效果是一致的,类似图中的办法,我们构造一个无效的UTF-8编码,然后粘贴回地址栏上回车,百度的后端服务器会判断这里有一个无效的输入字符。

  • 如果只把 “北” 的编码无效,“京”的编码不变,百度会只返回对“京”字的查询结果;
  • 如果把 “北” 的编码无效,去掉“京”字编码部分,百度会直接返回到空白的首页。

当然这种测试方法的本质其实也是一次接口测试,对接口的入参进行检测。

3). 接口测试

接口测试其本质也属于黑盒测试。测试的时候一般也不关注接口的实现代码,而是通过构造入参检查接口的输出,是不是像黑盒测试,只是测试的时候需要借助类似JMeter等专门的工具,而不是手工去完成。其实也好理解,黑盒测试、白盒测试是按测试的类型划分的,单元测试、接口测试、系统测试是按测试的阶段和对象划分的,他们之间不是互斥的。就像男人、女人的划分与青年、老年的划分一样不冲突。

接口需要对入参进行校验。测试的时候我们就需要对接口的入参分别设计各类情况:

  • 整体所有参数校验:考虑参数的个数、参数顺序
  • 针对单独参数校验:有效、无效、空等情况(这里又用到等价类了)

4). 其它等价类方法的应用场景

  • 翻页处理。不用每个页面都要点击一遍哈。但是如果数据量大,还要考虑一下深度翻页。
  • 查询处理。如果查询条件很多,是否真的需要每一个条件都单独测试一次呢?
  • 权限选项。权限的处理也是可以分类的。
  • 文件处理。按文件类型进行分类,每个类型选取一个文件。
  • 兼容测试。浏览器有很多,根据其渲染引擎也可以进行分类。
  • 等等。。。。。。

6. 尽可能再提高测试效率

再次强调,等价类是一种思想,作为测试的同学必须要掌握。但是能不能把它更加简化和高效呢?就像不懂机械原理也能开好车,但是懂得机械原理开车时会更有自信和得心应手,嗯,这是个关于开车的问题。

以下参考权当抛砖引玉了。

6.1. 检查正则表达式 PK 页面一遍遍的输入

利用正则表达式对输入的数据进行类型、格式等校验。此时完全可以借助现有的正则工具对所有构造的数据进行一次性快速校验。

  • 根据规格说明,利用等价类等思想构造测试数据。
  • 从程序中提取正则表达式。或向开发人员索要。
  • 将所有构造的测试数据和正则表达式置入相应工具中。
  • 根据工具反馈的结果和构造测试数据时的预期结果,判断正则表达式的编写是否正确。

6.2. 检查注解 PK 页面一遍遍的输入

例如在Java开发体系中,有很多现有的框架提供注解的方式进行参数校验。我们可以通过走查的方式直接检查注解的使用,而没有必要手工在页面去一遍遍的输入试错。

此时像一些“类型检查”、“非空检查”、“数值区间检查”、“边界检查”、“日期检查”、“正则检查”、“Email检查”等等都可以交给现成的注解来完成。

如果通过一个类似检查表的东西是不是就可以大大提高测试的效率,不用再去有效等价类、无效等价类的去设计输入,也不用耗时间去执行测试用例了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这是一个简单的Java系统代码,包含登录注册功能和选择五句古诗词默写或者做100以内的加减法测试。 ```java import java.util.Scanner; public class Main { static String[] users = new String[10]; static String[] passwords = new String[10]; static boolean[] loggedIn = new boolean[10]; static int currentUser = -1; public static void main(String[] args) { Scanner scanner = new Scanner(System.in); while (true) { System.out.println("1. 注册"); System.out.println("2. 登录"); System.out.println("3. 退出"); System.out.print("请选择:"); int choice = scanner.nextInt(); switch (choice) { case 1: register(scanner); break; case 2: login(scanner); break; case 3: System.exit(0); break; default: System.out.println("请选择正确的选项"); } } } static void register(Scanner scanner) { System.out.print("请输入用户名:"); String username = scanner.next(); System.out.print("请输入密码:"); String password = scanner.next(); for (int i = 0; i < users.length; i++) { if (users[i] == null) { users[i] = username; passwords[i] = password; System.out.println("注册成功"); return; } else if (users[i].equals(username)) { System.out.println("用户名已存在"); return; } } System.out.println("注册失败,用户数量已达上限"); } static void login(Scanner scanner) { System.out.print("请输入用户名:"); String username = scanner.next(); System.out.print("请输入密码:"); String password = scanner.next(); for (int i = 0; i < users.length; i++) { if (users[i] != null && users[i].equals(username) && passwords[i].equals(password)) { loggedIn[i] = true; currentUser = i; System.out.println("登录成功"); selectAction(scanner); return; } } System.out.println("用户名或密码错误"); } static void selectAction(Scanner scanner) { System.out.println("请选择:"); System.out.println("1. 选择五句古诗词默写"); System.out.println("2. 做100以内的加减法测试"); System.out.println("3. 注销"); int choice = scanner.nextInt(); switch (choice) { case 1: poemTest(); break; case 2: mathTest(scanner); break; case 3: logout(); break; default: System.out.println("请选择正确的选项"); } } static void poemTest() { String[] poems = { "朝辞白帝彩云间,千里江陵一日还。", "明月几时有?把酒问青天。", "登高壮观天地间,大江茫茫去不还。", "两个黄鹂鸣翠柳,一行白鹭上青天。", "十年生死两茫茫,不思量,自难忘。" }; int score = 0; Scanner scanner = new Scanner(System.in); for (String poem : poems) { System.out.println("请默写:" + poem); String answer = scanner.nextLine(); if (answer.equals(poem)) { System.out.println("答对了!"); score++; } else { System.out.println("答错了!"); } } System.out.println("测试结束,你的成绩是:" + score + "/5"); selectAction(scanner); } static void mathTest(Scanner scanner) { int score = 0; for (int i = 0; i < 10; i++) { int a = (int) (Math.random() * 100); int b = (int) (Math.random() * 100); int op = (int) (Math.random() * 2); // 0 for addition, 1 for subtraction System.out.print((i + 1) + ". "); if (op == 0) { System.out.print(a + " + " + b + " = "); int answer = scanner.nextInt(); if (answer == a + b) { System.out.println("答对了!"); score++; } else { System.out.println("答错了!"); } } else { System.out.print(a + " - " + b + " = "); int answer = scanner.nextInt(); if (answer == a - b) { System.out.println("答对了!"); score++; } else { System.out.println("答错了!"); } } } System.out.println("测试结束,你的成绩是:" + score + "/10"); selectAction(scanner); } static void logout() { loggedIn[currentUser] = false; currentUser = -1; System.out.println("注销成功"); } } ``` 代码解释: - `users`和`passwords`是两个数组,用于存储用户的用户名和密码。 - `loggedIn`是一个布尔数组,用于记录用户是否已登录。 - `currentUser`是一个整数,用于记录当前登录的用户的编号(从0开始)。 - `register`函数用于注册新用户。 - `login`函数用于登录已有用户。 - `selectAction`函数用于用户选择测试类型。 - `poemTest`函数进行五句古诗词默写测试。 - `mathTest`函数进行100以内的加减法测试。 - `logout`函数用于注销当前登录的用户。 注意,这只是一个简单的示例代码,实际上还有很多需要改进和完善的地方。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值