草庐IT

【实际开发01】- 单元测试 ( 追求正确性 )

猫猫聚会Ing 2023-09-30 原文

目录

0. 单元测试 概念 / 解析

1. 为什么要进行单元测试

1. JUnit ~ @Test

2. IDEA 中使用 junit 单元测试 , 不能使用 Scanner 的解决方法

3. Junit 测试 Tutorial

1. daiding

4. @Test 修饰的方法必须 public

1. validatePublicVoidNoArgMethods(Test.class, false, errors);

2. public static void main(String[] args) {} ~ 程序入口

1. main 概念 / 解析

1. 为什么 main 方法是静态的(static)

2. 为什么main 方法是公有的 ( public )

3. 为什么 main 方法没有返回值 ( void )

2. 使用 main() 方法来测试有很多坏处

3. String[] 字符串数组

3. Junit 单元测试 和 main 函数区别 ( 踩坑 )

1. Junit 单元测试 不支持 多线程

1. Junit

2. main 函数

3. 解决方案

2. 结论 : 尽量不要在 @test 开启新线程


0. 单元测试 概念 / 解析

单元测试 , 就是针对最小的功能单元编写测试代码

在 Java 中 , 最小的功能单元就是方法 , 因此 , 对 Java 程序员进行单元测试实际上就是对 Java 方法的测试。

单元测试 : 任何一个小模块、小功能;模块式测试

 1、可以减少出错、缩小调试影响面
 ​
 2、一步步测 , 才是最快的方法;
 ​
 3、完成一部分测试一部分 , 防止自己懵逼!

写完一节 , 就进行单元测试 ;避免追求一气呵成的错误集中情况! (单元测试包 , 需保证唯一)


1. 为什么要进行单元测试

因为单元测试可以确保你编写的代码是符合软件需求和遵循开发规范的。

单元测试是所有测试中最底层的一类测试 , 是第一个环节 , 也是最重要的一个环节 , 是唯一一次能够达到代码覆盖率 100% 的测试 , 是整个软件测试过程的基础和前提。

可以这么说 , 单元测试的性价比是最好的。

微软公司之前有这样一个统计:

bug 在单元测试阶段被发现的平均耗时是 3.25 小时 , 如果遗漏到系统测试则需要 11.5 个小时。

经我这么一说 , 你应该已经很清楚单元测试的重要性了。

那在你最初编写测试代码的时候 , 是不是经常这么做?就像下面这样。


1. JUnit ~ @Test

IDEA 会自动在当前类所在的包下生成一个类名带 Test ( 惯例 ) 的测试类


2. IDEA 中使用 junit 单元测试 , 不能使用 Scanner 的解决方法

参考:

解决IDEA中使用junit单元测试不能使用Scanner的方法_花開彼岸天丶的博客-CSDN博客_junit不能接收sacanner 解决IDEA中使用junit单元测试不能使用Scanner的方法

编程时在 IDEA 中引入单元测试 , 并在里面用了 Scanner 时 , 运行时会出一直在跑的问题 :

方案1 : 将测试类的代码放入 main 函数运行

方案2 : 在 IDEA 中点击 help -> Edit Custom Vm Options… , 进入 , 在最后一行加入:-Deditable.java.test.console=true

最后 : 在 eclipse 中则不会出现这个问题;


3. Junit 测试 Tutorial


1. daiding


4. @Test 修饰的方法必须 public


1. validatePublicVoidNoArgMethods(Test.class, false, errors);

查阅 Junit 源码 :

     protected void validateInstanceMethods(List<Throwable> errors) {
         
         validatePublicVoidNoArgMethods(After.class, false, errors);
         validatePublicVoidNoArgMethods(Before.class, false, errors);
         
         validateTestMethods(errors);
         if (computeTestMethods().size() == 0)
             errors.add(new Exception(" No runnable methods "));
     }
 ​
     protected void validateTestMethods(List<Throwable> errors) {
         validatePublicVoidNoArgMethods(Test.class, false, errors);
     }

这表明 @Before、@After、@Test 注解的方法必须是 : public,void,非静态,不带参数。


2. public static void main(String[] args) {} ~ 程序入口

Main 方法是 Java 程序的入口

记住,我们这里不会讨论 Servlet、MIDlet 和其他任何容器管理的 java 程序,在 java 核心编程中,JVM 会查找类中的 public static void main(String[]args),如果找不到该方法就抛出错误 NoSuchMethodError:main 程序终止。

Main 方法必须严格遵循它的语法规则,方法签名必须是 public static void,参数是字符串数组类型,如果是 Java1.5 及以后的版本还可以使用可变参数:

public static void main(String... args)

今天终于搞懂了:为什么 Java 的 main 方法必须是 public static void?

Public:访问权限最大。

static:不需要对象,直接类名即可。

void:主函数没有返回值。

Main:主函数特定的名称。

(String[] args):主函数的参数,是一个字符串数组类型的参数,jvm 调用 main() 方法时,传递的实际参数是 new String[0]。

 jvm 默认传递的是长度为0的字符串数组,
 我们在运行该类时,也可以指定具体的参数进行传递。
 可以在控制台,运行该类时,在后面加入参数。参数之间通过空格隔开。jvm会自动将这些字符串参数作为args数组中的元素,进行存储。


1. main 概念 / 解析

如果需要用 java 命令直接运行一个 Java 类 , 这个 Java 类必须包含 main 方法 ,

这个 main 方法必须使用 public 和 static 来修饰 , 必须使用 void 声明该方法的返回值 ,

而且该方法的参数类型只能是一个字符串数组 , 而不能是其他形式的参数。

对于这个 main 方法而言 , 前面的 public 和 static 修饰符的位置可以互换 , 但其他部分则是固定的。

定义 main 方法时 , 不要写成 Main 方法 , 如果不小心把方法名的首字母写成了大写 , 编译时不会出现任何问题 , 但运行该程序时将给出如图 2 的错误提示:

这个错误提示找不到 main 方法 , 因为 Java 虚拟机只会选择从 main 方法开始执行。

对于 Main 方法 , Java 虚拟机会把该方法当成一个普通方法 , 而不是程序的入口。

main 方法里可以放置程序员需要执行的可执行性语句 , 例如 System.out.println("Hello Java!") ,

这行语句是 Java 里的输出语句 , 用于向控制台输岀“Hello Java!”这个字符串内容 , 输出结束后还输出一个换行符。

在 Java 程序里执行输岀有两种简单的方式:

System.out.print(需要输出的内容) 和 System.out.println (需要输出的内容) , 其中前者在输出结束后不会换行 , 而后者在输出结束后会换行。

总结:

1、main 方法必须声明为 public、static、void,否则 JVM 没法运行程序 。

2、如果 JVM 找不到 main 方法就抛出 NoSuchMethodError:main 异常,

 例如:如果你运行命令:java HelloWrold,JVM 就会在 HelloWorld.class 文件中搜索 public static void main (String[] args) 方法。

3、main 方式是程序的入口,程序执行的开始处。

4、main 方法被一个特定的线程 ”main” 运行,程序会一直运行直到 main 线程结束或者 non-daemon 线程终止。

5、当你看到 “Exception in Thread main” 如:Excpetion in Thread main:Java.lang.NullPointedException,意味着异常来自于 main 线程。

6、你可以声明 main 方法使用 java1.5 的可变参数的方式 ; 如:publicstaticvoid main(String... args)。

7、除了 static、void、和 public,你可以使用 final,synchronized、和 strictfp 修饰符在 main 方法的签名中,如:public strict fp final synchronized static void main(String[] args)

8、main 方法在 Java 可以像其他方法一样被重载,但是 JVM 只会调用上面这种签名规范的 main 方法。

9、你可以使用 throws 子句在方法签名中,可以抛出任何 checked 和 unchecked 异常。

10、静态初始化块在 JVM 调用 main 方法前被执行,它们在类被 JVM 加载到内存的时候就被执行了。


1. 为什么 main 方法是静态的(static)

1、正因为 main 方法是静态的,JVM 调用这个方法就不需要创建任何包含这个 main 方法的实例。

2、因为 C 和 C++ 同样有类似的 main 方法作为程序执行的入口。

3、如果 main 方法不声明为静态的,JVM 就必须创建 main 类的实例,因为构造器可以被重载,JVM 就没法确定调用哪个 main 方法。

4、静态方法和静态数据加载到内存就可以直接调用 , 而不需要像实例方法一样创建实例后才能调用,如果 main 方法是静态的,那么它就会被加载到 JVM 上下文中成为可执行的方法。


2. 为什么main 方法是公有的 ( public )

Java 指定了一些可访问的修饰符 ;如:private、protected、public,任何方法或变量都可以声明为 public,Java 可以从该类之外的地方访问。

因为 main 方法是公共的,JVM 就可以轻松的访问执行它。


3. 为什么 main 方法没有返回值 ( void )

因为 main 返回任何值对程序都没任何意义,所以设计成 void,意味着 main 不会有任何值返回。


2. 使用 main() 方法来测试有很多坏处

1、测试代码没有和源代码分开。

2、不够灵活 , 很难编写一组通用的测试代码。

3、无法自动打印出预期和实际的结果 , 没办法比对。

JUnit 的话 , 就不会再有这种困扰了。

我可以非常简单地组织测试代码 , 并随时运行它们 , 还能给出准确的测试报告 , 让你在最短的时间内发现自己编写的代码到底哪里出了问题。


3. String[] 字符串数组


3. Junit 单元测试 和 main 函数区别 ( 踩坑 )


1. Junit 单元测试 不支持 多线程

当 Thread 了新的线程后 , Junit 单元测试 在主线程运行结束后就关闭了 , 而不会等子线程运行结束。

而 main 函数就不存在这个问题了...

测试对比如下:


1. Junit

书写一个定时任务 , 每500ms执行一次:

运行结果:

可以看出 , 当主线程执行结束后 , 就关闭了 , 不会等 子线程运行


2. main 函数

同上 , 书写一个定时任务 , 每 500ms 执行一次:

运行结果如下:  


3. 解决方案

如果想使用Junit 进行多线程测试 , 可以先睡眠主线程 , 例如:

    @Test
    public void test(){
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+ ": ==========Junit 定时任务 =========");
            }
        } ,  new Date() ,  500);

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName()+ "==========Junit 主线程任务=========");
    }


2. 结论 : 尽量不要在 @test 开启新线程

@Test 单测方法和 main 方法

 在做一个多线程测试的时候发现的一个比较好玩的问题 , 
     在@Test的单测方法中开启了两个线程 , 本来应该在B()方法中的打印 , 没打出来 , 
     看了一下单测方法的线程是Thread[main , 5 , main] , 这方法开启的是main线程。
 ​
 网上一些人的观点是单测方法结束的时候会把里面的资源释放了 , 导致里面线程提前结束。
public class 多线程测试01 {
    int a ;
    boolean flag ;


    public static void main(String[] agrs) {
        多线程测试01 shen = new 多线程测试01();
        new Thread(new Runnable() {
            @Override
            public void run() {
                shen.A();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                    shen.B();
            }
        }).start();
    }

    // @Test 单测方法有一个问题 , 当单测方法结束的时候里面的线程也要结束 , 不管是不是执行完成
    // 结论不要在@Test开启线程
    @Test
    public void play() {
        多线程测试01 shen = new 多线程测试01();
        new Thread(new Runnable() {
            @Override
            public void run() {
                shen.A();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                shen.B();
            }
        }).start();

        System.out.println(Thread.currentThread());

    }


    public void A() {
//        try {
//            Thread.sleep(100);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        a = 1;
        flag = true;
    }


    public void B()  {
        if (flag) {
            System.out.println("flag = true  " + a);
        } else if(!flag){
            System.out.println("falg = false  " + a);
        }
    }
}

有关【实际开发01】- 单元测试 ( 追求正确性 )的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  3. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  4. ruby - Ruby 的 Hash 在比较键时使用哪种相等性测试? - 2

    我有一个围绕一些对象的包装类,我想将这些对象用作散列中的键。包装对象和解包装对象应映射到相同的键。一个简单的例子是这样的:classAattr_reader:xdefinitialize(inner)@inner=innerenddefx;@inner.x;enddef==(other)@inner.x==other.xendenda=A.new(o)#oisjustanyobjectthatallowso.xb=A.new(o)h={a=>5}ph[a]#5ph[b]#nil,shouldbe5ph[o]#nil,shouldbe5我试过==、===、eq?并散列所有无济于事。

  5. ruby-on-rails - 如何使用 instance_variable_set 正确设置实例变量? - 2

    我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击

  6. ruby - RSpec - 使用测试替身作为 block 参数 - 2

    我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

  7. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

    Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

  8. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

    我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

  9. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  10. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

随机推荐