用 Groovy 更迅速地对 Java 代码进行单元测试

开始之前,我首先要招认:我是一个单元测试狂。实际上,我总是无法编写足够的单元测试。如果我相当长一段时间都在进行开发,而 没有编写相应的单元测试,我就会觉得紧张。单元测试给我信心,让我相信我的代码能够工作,而且我只要看一下,可以修改它,就不会害怕它会崩溃。

而且,作为一个单元测试狂,我喜欢编写多余的测试用例。但是,我的兴奋不是来自 编写测试用例,而是 看着它们生效。所以,如果我能用更快的方式编写测试,我就能更迅速地看到它们的结果。这让我感觉更好。更快一些!

后 来,我找到了 Groovy,它满足了我的单元测试狂,而且至今为止,对我仍然有效。这种新语言给单元测试带来的灵活性,非常令人兴奋,值得认真研究。本文是介绍 Groovy 实践方面的新系列的第一部分,在文中,我将向您介绍使用 Groovy 进行单元测试的快乐。我从概述开始,概述 Groovy 对 Java 平台上的开发所做的独特贡献,然后转而讨论使用 Groovy 和 JUnit 进行单元测试的细节,其中重点放在 Groovy 对 JUnit 的 TestCase 类的扩展上。最后,我用一个实用的示例进行总结,用第一手材料向您展示如何把 groovy 的这些特性与 Eclipse 和 Maven 集成在一起。

不要再坚持 Java 纯粹主义了!

在 我开始介绍用 Groovy 进行单元测试的实际经验之前,我认为先谈谈一个更具一般性的问题 —— 它在您的开发工具箱中的位置,这非常重要。事实是,Groovy 不仅是运行在 Java 运行时环境(JRE)中的脚本语言,它还被提议作为用于 Java 平台的标准语言。正如您们之中的人已经从 alt.lang.jre 系列(请参阅 参考资料)中学习到的,在为 Java 平台进行脚本编程的时候,有无数的选择,其中大多数是面向快速应用程序开发的高度灵活的环境。

虽然有这么丰富的选择,但还是有许多开发人选择坚持自己喜欢的、最熟悉的范式:Java 语言。虽然大多数情况下,Java 编程都是很好的选择,但是它有一个非常重要的缺点,蒙住了只看见 Java 的好处的这些人的眼睛。正如一个智者曾经指出的: 如果您仅有的一个工具是一把锤子,那么您看每个问题时都会觉得它像是钉子。我认为这句谚语道出了适用于软件开发的许多事实。

虽然我希望用这个系列说服您 Java 不是也不应当是开发应用程序的惟一选择,但该脚本确实既有适用的地方也有不适用的地方。专家和新手的区别在于:知道什么时候 运用该脚本,什么时候 避免使用它。

关于本系列

把工具整合到开发实践中的关键是了解什么时候使用它,以及什么时候把它留在工具箱中。脚本语言能够成为工具包中极为强大的附件,但是只有正确地应用在适当的场合时才是这样。为了实现 实战 Groovy 系列文章这个目标,我专门研究了 Groovy 的一些实战,教给您什么时候怎样才能成功地应用它们。

例如,对于高性能、事务密集型、企业级应用程序,Groovy 脚本通常不太适合;在这些情况下,您最好的选择 可能是 普通的 J2EE 系统。但另一方面,一些脚本 —— 特别是用 Groovy 编写的脚本 —— 会非常有用,因为它能迅速地为小型的、非常特殊的、不是性能密集型的应用程序(例如配置系统/生成系统)快速制作原型。对于报表应用程序来 说,Groovy 脚本也是近乎完美的选择,而最重要的是,对单元测试更是如此。





回页首


为什么用 Groovy 进行单元测试?

是 什么让 Groovy 比起其他脚本平台显得更具有吸引力呢?是它与Java 平台无缝的集成。还是因为它是基于 Java 的语言(不像其他语言,是对 JRE 的替代,因此可能基于旧版的处理器),对于 Java 开发人员来说,Groovy 意味着一条短得让人难以置信的学习曲线。而且一旦将这条学习曲线拉直,Groovy 就能提供一个无与伦比的快速开发平台。

从这个角度来说,Groovy 成功的秘密,在于它的语法 就是 Java 语法,但是规则更少。例如,Groovy 不要求使用分号,变量类型和访问修饰符也是可选的。而且,Groovy 利用了标准的 Java 库,这些都是您已经很熟悉的,包括 CollectionsFile/IO。而且,您还可以利用任何 Groovy 提供的 Java 库,包括 JUnit。

事实上,令人放松的类 Java 语法、对标准 Java 库的重用以及快捷的生成-运行周期,这些都使 Groovy 成为快速开发单元测试的理想替代品。但是会说的不如会做的,还是让我们在代码中看看它的实际效果!





回页首


JUnit 和 Groovy

用 Groovy 对 Java 代码进行单元测试简单得不能再简单了,有很多入门选择。最直接的选择是沿袭行业标准 —— JUnit。Unit 的简单性和其功能的强大都是无与伦比的,作为非常有帮助的 Java 开发工具,它的普遍性也是无与伦比的,而且没有什么能够阻挡 JUnit 和 Groovy 结合,所以为什么多此一举呢?实际上,只要您看过 JUnit 和 Groovy 在一起工作,我敢打赌您就永远再也不会回头!在这里,要记住的关键的事,您在 Java 语言中能用 JUnit 做到的事,在 Groovy 中用 JUnit 也全都能做到;但是需要的代码要少得多。

入门

在您下载了 JUnit 和 Groovy(请参阅 参考资料)之后,您将有两个选择。第一个选择是编写普通的 JUnit 测试用例,就像以前一直做的那样,扩展 JUnit 令人称赞的 TestCase。第二个选择是应用 Groovy 简洁的 GroovyTestCase 扩展,它会扩展 JUnit 的 TestCase。第一个选项是您最快的成功途径,它拥有最多 与 Java 类似的相似性。而另一方面,第二个选择则把您推进了 Groovey 的世界,这个选择有最大的敏捷性。

开始的时候,我们来想像一个 Java 对象,该对象对指定的 string 应用了一个过滤器,并根据匹配结果返回 boolean 值。该过滤器可以是简单的 string 操作,例如 indexOf(),也可以更强大一些,是正则表达式。可能要通过 setFilter() 方法在运行时设置将使用的过滤器, apply() 方法接受要过滤的 string。清单 1 用普通的 Java 代码显示了这个示例的 Filter 接口:


清单 1. 一个简单的 Java Filter 接口
public interface Filter {
void setFilter(String fltr);
boolean applyFilter(String value);
}

我们的想法是用这个特性从大的列表中过滤掉想要的或者不想要的包名。所以,我建立了两个实现: RegexPackageFilterSimplePackageFilter

把 Groovy 和 JUnit 的强大功能与简单性结合在一起,就形成了如清单 2 所示的简洁的测试套件:


清单 2. 用 JUunit 制作的 Groovy RegexFilterTest
import junit.framework.TestCase
import com.vanward.sedona.frmwrk.filter.impl.RegexPackageFilter
class RegexFilterTest extends TestCase {
void testSimpleRegex() {
fltr = new RegexPackageFilter()
fltr.setFilter("java.*")
val = fltr.applyFilter("java.lang.String")
assertEquals("value should be true", true, val)
}
}

不管您是否熟悉 Groovy,清单 2 中的代码对您来说应当很面熟,因为它只不过是没有分号、访问修饰符或变量类型的 Java 代码而已!上面的 JUnit 测试中有一个测试用例 testSimpleRegex(),它试图断言 RegexPackageFilter 用正则表达式 "java.*" 正确地找到了与 “ java.lang.String”匹配的对象。





回页首


Groovy 扩展了 JUnit

扩展 JUnit 的 TestCase 类,加入附加特性,实际上是每个 JUnit 扩展通常采用的技术。例如,DbUnit 框架(请参阅 参考资料)提供了一个方便的 DatabaseTestCase 类,能够比以往任何时候都更容易地管理数据库的状态,还有重要的 MockStrutsTestCase(来自 StrutsTestCase 框架;请参阅 参考资料),它生成虚拟的 servlet 容器,用来执行 struts 代码。这两个强大的框架都极好地扩展了 JUnit,提供了 JUnit 核心代码中所没有的其他特性;而现在 Groovy 来了,它也是这么做的!

与 StrutsTestCase 和 DbUnit 一样,Groovy 对 JUnit 的 TestCase 的扩展给您的工具箱带来了一些重要的新特性。这个特殊的扩展允许您通过 groovy 命令运行测试套件,而且提供了一套新的 assert 方法。可以用这些方法很方便地断言脚本的运行是否正确,以及断言各种数组类型的长度和内容等。





回页首


享受 GroovyTestCase 的快乐

了解 GroovyTestCase 的能力最好的办法,莫过于实际看到它的效果。在清单 3 中,我已经编写了一个新的 SimpleFilterTest,但是这次我要扩展 GroovyTestCase 来实现它:


清单 3. 一个真正的 GroovyTestCase
import groovy.util.GroovyTestCase
import com.vanward.sedona.frmwrk.filter.impl.SimplePackageFilter
class SimpleFilterTest extends GroovyTestCase {

void testSimpleJavaPackage() {
fltr = new SimplePackageFilter()
fltr.setFilter("java.")
val = fltr.applyFilter("java.lang.String")
assertEquals("value should be true", true, val)
}
}

请注意,可以通过命令行来运行该测试套件,没有运行基于 Java 的 JUnit 测试套件所需要的 main() 方法。实际上,如果我用 Java 代码编写上面的 SimpleFilterTest,那么代码看起来会像清单 4 所示的那样:


清单 4. 用 Java 代码编写的同样的测试用例
import junit.framework.TestCase;
import com.vanward.sedona.frmwrk.filter.Filter;
import com.vanward.sedona.frmwrk.filter.impl.SimplePackageFilter;
public class SimplePackageFilterTest extends TestCase {
public void testSimpleRegex() {
Filter fltr = new SimplePackageFilter();
fltr.setFilter("java.");
boolean val = fltr.applyFilter("java.lang.String");
assertEquals("value should be true", true, val);
}

public static void main(String[] args) {
junit.textui.TestRunner.run(SimplePackageFilterTest.class);
}
}

用断言进行测试

除了可以让您通过命令行运行测试之外, GroovyTestCase 还向您提供了一些特别方便的 assert 方法。例如, assertArrayEquals,它可以检查两个数据中对应的每一个值和各自的长度,从而断言这两个数据是否相等。从清单 5 的示例开始,就可以看到 Groovy 断言的实际效果,清单 5 是一个简洁的基于 Java 的方法,它把 string 分解成数组。(请注意,我可能使用了 Java 1.4 中新添加的 string 特性编写以下的示例类。我采用 Jakarta Commons StringUtils 类来确保与 Java 1.3 的后向兼容性。)


清单 5. 定义一个 Java StringSplitter 类
import org.apache.commons.lang.StringUtils;
public class StringSplitter {
public static String[] split(final String input, final String separator){
return StringUtils.split(input, separator);
}
}

清单 6 展示了用 Groovy 测试套件及其对应的 assertArrayEquals 方法对这个类进行测试是多么简单:


清单 6. 使用 GroovyTestCase 的 assertArrayEquals 方法
import groovy.util.GroovyTestCase
import com.vanward.resource.string.StringSplitter
class StringSplitTest extends GroovyTestCase {

void testFullSplit() {
splitAr = StringSplitter.split("groovy.util.GroovyTestCase", ".")
expect = ["groovy", "util", "GroovyTestCase"].toArray()
assertArrayEquals(expect, splitAr)
}
}





回页首


其他方法

Groovy 可以让您单独或成批运行测试。使用 GroovyTestCase 扩展,运行单个测试毫不费力。只要运行 groovy 命令,后面跟着要运行的测试套件即可,如清单 7 所示:


清单 7. 通过 groovy 命令运行 GroovyTestCase 测试用例
$./groovy test/com/vanward/sedona/frmwrk/filter/impl/SimpleFilterTest.groovy
.
Time: 0.047
OK (1 test)

Groovy 还提供了一个标准的 JUnit 测试套件,叫作 GroovyTestSuite。只要运行该测试套件,把脚本的路径传给它,它就会运行脚本,就像 groovy 命令一样。这项技术的好处是,它可以让您在 IDE 中运行脚本。例如,在 Eclipse 中,我只是为示例项目建立了一个新的运行配置(一定要选中 “Include external jars when searching for a main class”),然后找到主类 groovy.util.GroovyTestSuite,如图 1 所示:


图 1. 用 Eclipse 运行 GroovyTestSuite
图 1. 用 Eclipse 运行 GroovyTestSuite

在图 2 中,您可以看到当点击 Arguments 标签,写入脚本的路径时,会发生了什么:


图 2. 在 Eclipse 中指定脚本的路径
图 2. 在 Eclipse 中指定脚本的路径

运行一个自己喜欢的 JUnit Groovy 脚本,实在是很简单,只要在 Eclipse 中找到对应的运行配置就可以了。





回页首


用 Ant 和 Maven 进行测试

这 个像 JUnit 一样的框架的美妙之处还在于,它可以把整套测试作为 build 的一部分运行,不需要人工进行干预。随着越来越多的人把测试用例加入代码基(code base),整体的测试套件日益增长,形成一个极好的回归平台(regression platform)。更妙的是,Ant 和 Maven 这样的 build 框架已经加入了报告特性,可以归纳 Junit 批处理任务运行的结果。

把一组 Groovy 测试用例整合到某一个构建中的最简单的方法是把它们编译成普通的 Java 字节码,然后把它们包含在 Ant 和 Maven 提供的标准的 Junit 批命令中。幸运的是,Groovy 提供了一个 Ant 标签,能够把未编译的 Groovy 脚本集成到字节码中,这样,把脚本转换成有用的字节码的处理工作就变得再简单不过。例如,如果正在使用 Maven 进行构建工作,那么只需在maven.xml 文件中添加两个新的目标、在 project.xml 中添加两个新的相关性、在 build.properties 文件中添加一个简单的标志就可以了。

我要从更新 maven.xml 文件开始,用新的目标来编译示例脚本,如清单 8 所示:


清单 8. 定义 Groovyc 目标的新 maven.xml 文件
 <goal name="run-groovyc" prereqs="java:compile,test:compile">

<path id="groovy.classpath">
<pathelement path="${maven.build.dest}"/>
<pathelement path="target/classes"/>
<pathelement path="target/test-classes"/>
<path refid="maven.dependency.classpath"/>
</path>
<taskdef name="groovyc" classname="org.codehaus.groovy.ant.Groovyc">
<classpath refid="groovy.classpath"/>
</taskdef>
<groovyc destdir="${basedir}/target/test-classes" srcdir="${basedir}/test/groovy"
listfiles="true">
<classpath refid="groovy.classpath"/>
</groovyc>
</goal>

上面代码中发生了以下几件事。第一,我定义一个名为 run-groovyc 的新目标。该目标有两个前提条件, java:compile 编译示例源代码, test:compile 编译普通的 Java-JUnit 类。我还用 <path> 标签创建了一个 classpath。在该例中,classpath 把 build 目录(保存编译后的源文件)和与它相关的所有依存关系(即 JAR 文件)整合在一起。接着,我还用 <taskdef> Ant 标签定义了 groovyc 任务。

而且,请您注意我在 classpath 中是如何告诉 Maven 到哪里去找 org.codehaus.groovy.ant.Groovyc 这个类。在示例的最后一行,我定义了 <groovyc> 标签,它会编译在 test/groovy 目录中发现的全部 Groovy 脚本,并把生成的 .class 文件放在 target/test-classes 目录中。

一些重要细节

为了编译 Groovy 脚本,并运行生成的字节码,我必须要通过 project.xml 文件定义两个新的依存关系( groovyasm),如清单 9 所示:


清单 9. project.xml 文件中的新的依存关系
  <dependency>
<groupId>groovy</groupId>
<id>groovy</id>
<version>1.0-beta-6</version>
</dependency>
<dependency>
<groupId>asm</groupId>
<id>asm</id>
<version>1.4.1</version>
</dependency>

一旦将脚本编译成普遍的 Java 字节码,那么任何标准的 JUnit 运行器就都能运行它们。因为 Ant 和 Maven 都拥有 JUnit 运行器标签,所以下一步就是让 JUnit 挑选新编译的 Groovy 脚本。而且,因为 Maven 的 JUnit 运行器使用模式匹配来查找要运行的测试套件,所以需要在 build.properties 文件中添加一个特殊标记,如清单 10 所示,该标记告诉 Maven 去搜索类而不是搜索 .java 文件。


清单 10. Maven 项目的 build.properties 文件
 maven.test.search.classdir = true

最后,我在 maven.xml 文件中定义了一个测试目标( goal),如清单 11 所示。这样做可以确保 在单元测试运行之前,使用新的 run-groovyc 目标编译 Groovy 脚本。


清单 11. maven.xml 的新目标
  <goal name="test">
<attainGoal name="run-groovyc"/>
<attainGoal name="test:test"/>
</goal>

最后一个,但并非最不重要

有了新定义的两个目标(一个用来编译脚本,另外一个用来运行 Java 和 Groovy 组合而成的单元测试),剩下的事就只有运行它们,检查是不是每件事都顺利运行!

在清单 12 中,您可以看到,当我运行 Maven,给 test 传递目标之后,会发生了什么,它首先包含 run-groovyc 目标(而该目标恰好还包含 java:compiletest:compile 这两个目标),然后包含 Maven 中自带的标准的 test:test 目标。请注意观察目标 test:test 是如何处理新生成的 Groovy 脚本(在该例中,是新 编译的 Groovy 脚本) 以及普通的 Java JUnit 测试。


清单 12. 运行新的测试目标
$ ./maven test
test:
java:compile:
[echo] Compiling to /home/aglover/dev/target/classes
[javac] Compiling 15 source files to /home/aglover/dev/target/classes
test:compile:
[javac] Compiling 4 source files to /home/aglover/dev/target/test-classes
run-groovyc:
[groovyc] Compiling 2 source files to /home/aglover/dev/target/test-classes
[groovyc] /home/aglover/dev/test/groovy/test/RegexFilterTest.groovy
[groovyc] /home/aglover/dev/test/groovy/test/SimpleFilterTest.groovy
test:test:
[junit] Running test.RegexFilterTest
[junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.656 sec
[junit] Running test.SimpleFilterTest
[junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.609 sec
[junit] Running test.SimplePackageFilterTest
[junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.578 sec
BUILD SUCCESSFUL
Total time: 42 seconds
Finished at: Tue Sep 21 17:37:08 EDT 2004





回页首


结束语

实战 Groovy 系列的第一期中,您学习了 Groovy 这个令人兴奋的脚本语言最实用的应用当中的一个。对于越来越多开发人员而言,单元测试是开发过程的重要组成部分;而使用 Groovy 和 JUnit 对 Java 代码进行测试就变成了轻而易举的事情。

Groovy 简单的语法、内部的灵活性,使其成为迅速编写有效的 JUnit 测试、将测试整合到自动编译中的一个优秀平台。对于像我一样为代码质量发狂的人来说,这种组合极大地减少了我的 神经紧张,还让我可以得到我想做得最好的东西:编写“防弹”软件。快点行动吧。

因为这是一个新的系列,所以我非常希望您能一起来推动它前进。如果您对 Groovy 有什么想了解的事情,请 发邮件给我,让我知道您的要求!我希望您会继续支持本系列的下一期,在下期中,我将介绍用 Groovy 进行 Ant 脚本编程。






来源:网络


智能推荐

网络应用层概述 -- 概念与结构

1. 概念 应用层,在集成的是7层模型中的表示层,会话层,应用层的总和,在TCP/IP三层模型中的最高层,其实现的协议包括TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet等   作用: 每个应用层协议都是为了解决某一类应用问题,而问题的解决是通过位于不同主机中的多个应用进程之间的通信和协同工作来完成的。应用层的具体内容就是规定应用进程在通信时所遵循的协议。 2 . 层...

自定义控件之绘图篇(一):概述及基本几何图形绘制

一、Paint与Canvas 像我们平时画图一样,需要两个工具,纸和笔。Paint就是相当于笔,而Canvas就是纸,这里叫画布。 所以,凡有跟要要画的东西的设置相关的,比如大小,粗细,画笔颜色,透明度,字体的样式等等,都是在Paint里设置;同样,凡是要画出成品的东西,比如圆形,矩形,文字等相关的都是在Canvas里生成。 下面先说下Paint的基本设置函数: paint.setAntiAlia...

Android--Toast 两个 Crash

Android Toast 两个 Crash   地址:http://tao93.top/2018/12/22/Android%20Toast%20%E4%B8%A4%E4%B8%AA%20Crash/   Toast 是 Android 系统一种非常简单的提示性小工具,最近我尝试修复 Toast 相关的两种 Crash,所以把相关的原委和过程记录了下来。先来看一下第一种 Cr...

WPF教你如何使用psd文件转化Path路径

首先你需要得到一个能使用的psd文件 下载安装Microsoft Expression Design 4,可百度自行下载安装。 安装好后双击打开,你会看到如下界面 将准备好的psd文件导入或直接拖进去,兼容性图片选项不要勾选,直接确定 导入成功之后选中你需要导出的图片,在菜单编辑中找到复制XAML,这样路径就已经复制到你的剪切板上了 复制成功的话,新建个文档之类的文件粘贴进去,我这里复制到了TXT...

单词小助手完善

题目分析 任务二:例2为单词小助手,要求完成以下任务: 1.改写程序为良好程序风格(文档注释,函数注释,语句注释)。 2.将单词测试中的功能完善,可针对做错的单词重复记忆。 3.查询单词的功能添加英文词查询,中文查询的功能完善(考虑如何显示同样中文意义,不同的英文单词) 提高要求: 可将程序中的不足(如数据验证,排名功能)等根据自己的能力与理解完成。 算法构造 流程图: 总图: 英中单词测试: 中...

猜你喜欢

IAR 修改工程名称

IAR 修改工程名称 很多时候用IAR开发都是基于已有工程模板开发的,但是工程模板的名称经常让人头疼;以下是修改办法: 从一个实例工程复制后缀名为“dep,ewd,ewp,eww”的四个文件,并将其重命名为 你自己的名字,如:enddev_module.dep,enddev_module.ewd,enddev_module.ewp,enddev_module.eww。 修改...

VS Key错误解决

问题 解决方案 1.在VS Error List里面找到你的VS Key然后复制(可以copy到txt截取) 2.找到该solution文件夹下面的client 3.管理员权限打开Deveper Command Prompt 4.打开2的文件夹,运行图片上的指令 4.输入密码:password...

软件测试(一)

软件测试:由“验证”和“确认”活动构成的整体。 软件测试的目的:尽可能发现并排除软件中潜藏的错误,提高软件的可靠性。 软件测试是采用测试用例执行软件的活动。 测试用例:为某个特殊目标而编制的一组测试输入、执行条件以及预期结果,以便测试某个程序路径或核实是否满足某个特定需求。是发现软件缺陷的最小测试执行单元。 软件缺陷:(1)内部:软件产品开发或维护过...

移植uboot出现:include/config.h:8:22: fatal error: configs/.h: No such file or directory

移植uboot出现:include/config.h:8:22: fatal error: configs/.h: No such file or directory 请检查如下信息是否配置正确: 1.检查 board/freescale/my_mx6ull_emmc/Kconfig文件,查看下图中的位置是否修改: 2.检查 board/freescale/my_mx6ull_emmc/MAINT...

CAS与Atomic类

CAS 什么是CAS? CAS全称Compare and swap(比较与替换), 该技术用于并发更新一个值数据时,使用不加锁的方式去安全的更新, 因为加锁是一种比较昂贵的操作,不加锁又无法保证多线程下的安全性,CAS在赋值时,有三个关键值, 预期值 新值 旧值 该操作的原子性由操作系统保证,在赋值时,使用预期值和旧值对比,当这两个值相等时,才将内存中的值替换为新值,否则就失败(和乐观锁同理, 也...

问答精选

delete a specific row with where clause in sqlite for android

I try to delete a specific row with where clause in sqlite for android but it not working plz any body help. try this :...

UIImageview Programmatically in swift

I'm just trying to create a UIImage View programmatically, I have a new view and I tried doing this This code doesn't work. please help me as soon as possible This is for image with 100 height and 100...

nsISocketTransportService using Firefox addon sdk

I'm trying to use Firefox to read the SSH banner. ie. when you initially connect to an SSH server the server sends you its banner, identifying the server software and you send the SSH server your bann...

Unable to customize html range input

I need to customize the range input. The slider must be green for the lower part(the area which the thumb has moved) and the remaining should be grey. Basically I have changed the default styles from ...

Counting the number of occurrences

I have the following problem. I want to count the number of occurrences of values that are smaller or equal to zero. Example in the following data I have 3 occurrences 1(0,0,0),2(-1,-2),3(0,0). Is the...

相关问题

相关文章

热门文章

推荐文章

相关标签

推荐问答