在过去的十年中,我们已经编写了成千上万的JUnit3测试,现在正尝试将结果合并到数据库中,而不是分散的日志文件中。 事实证明,扩展TestCase类非常容易做到这一点。 注意:这种方法并不直接适用于JUnit4或其他测试框架,但是通常可以做类似的事情。

被测类及其测验

出于演示目的,我们可以使用单个方法来定义一个类进行测试。

public class MyTestedClass {public String op(String a, String b) {return ((a == null) ? "" : a) + ":" + ((b == null) ? "" : b);}
}

具有单个要测试方法的类比您想象的要少的限制。 在前面提到的数千个测试中,我们仅测试了四种方法。

这是上述类的一些测试。

public class MySimpleTest extends SimpleTestCase {private MyTestedClass obj = new MyTestedClass();public void test1() {assertEquals("a:b", obj.op("a", "b"));}public void test2() {assertEquals(":b", obj.op(null, "b"));}public void test3() {assertEquals("a:", obj.op("a", null));}public void test4() {assertEquals(":", obj.op(null, null));}public void test5() {// this will failassertEquals(" : ", obj.op(null, null));}
}

使用TestListener捕获基本信息

JUnit3允许向侦听器添加其测试过程。 在测试运行之前和之后以及测试失败或有错误(引发异常)的任何时间调用此侦听器。 此TestListener将基本测试信息写入System.out,以作为概念证明。 修改它以将信息写入数据库,JMS主题等很容易。

public class SimpleTestListener implements TestListener {private static final TimeZone UTC = TimeZone.getTimeZone("UTC");private long start;private boolean successful = true;private String name;private String failure = null;SimpleTestListener() {}public void setName(String name) {this.name = name;}public void startTest(Test test) {start = System.currentTimeMillis();}public void addError(Test test, Throwable t) {// cache information about error.successful = false;}public void addFailure(Test test, AssertionFailedError e) {// cache information about failure.failure = e.getMessage();successful = false;}/*** After the test finishes we can update the database with statistics about* the test - name, elapsed time, whether it was successful, etc.*/public void endTest(Test test) {long elapsed = System.currentTimeMillis() - start;SimpleDateFormat fmt = new SimpleDateFormat();fmt.setTimeZone(UTC);System.out.printf("[%s, %s, %s, %d, %s, %s]\n", test.getClass().getName(), name, fmt.format(new Date(start)),elapsed, failure, Boolean.toString(successful));// write any information about errors or failures to database.}
}

生产TestListener应该在错误和失败方面做更多的事情。 我将其忽略,以便专注于更广泛的问题。

该侦听器不是线程安全的,因此我们将要使用Factory模式为每个测试创建一个新实例。 我们可以在工厂中创建重量级对象,例如,在工厂中打开SQL DataSource并将新的Connection传递给每个实例。

public class SimpleTestListenerFactory {public static final SimpleTestListenerFactory INSTANCE = new SimpleTestListenerFactory();public SimpleTestListenerFactory() {// establish connection data source here?}public SimpleTestListener newInstance() {// initialize listener.SimpleTestListener listener = new SimpleTestListener();return listener;}
}

如果我们知道测试框架是纯串行的,我们可以通过创建缓冲区并在startTest()中调用System.setOut()然后在endTest()中还原原始System.out来捕获所有控制台输出。 只要测试永不重叠,此方法就行得通,否则会引起问题。 但是,这可能会出现问题– IDE可能具有自己的允许并行执行的测试运行程序。

我们用自己的方法覆盖标准的run()方法,该方法在调用现有的run()方法之前创建并注册一个侦听器。

public class SimpleTestCase extends TestCase {public void run(TestResult result) {SimpleTestListener l = SimpleTestListenerFactory.INSTANCE.newInstance();result.addListener(l);l.setName(getName());super.run(result);result.removeListener(l);}}

现在,我们将预期的结果发送到System.out。

[MySimpleTest, test1, 8/2/15 11:58 PM, 0, null, true]
[MySimpleTest, test2, 8/2/15 11:58 PM, 10, null, true]
[MySimpleTest, test3, 8/2/15 11:58 PM, 0, null, true]
[MySimpleTest, test4, 8/2/15 11:58 PM, 0, null, true]
[MySimpleTest, test5, 8/2/15 11:58 PM, 4, expected same:<:> was not:< : >, false]

使用外观和TestListener捕获呼叫信息

这是一个好的开始,但我们可能会做得更好。 在上面提到的数千个测试中,仅调用了4种方法-如果我们能够捕获这些调用的输入和输出值,则将非常强大。

如果由于某些原因不可接受AOP,则可以使用AOP或日志记录外观包装这些功能。 在简单的情况下,我们可以简单地捕获输入和输出值。

public class MyFacadeClass extends MyTestedClass {private MyTestedClass parent;private String a;private String b;private String result;public MyFacadeClass(MyTestedClass parent) {this.parent = parent;}public String getA() {return a;}public String getB() {return b;}public String getResult() {return result;}/*** Wrap tested method so we can capture input and output.*/public String op(String a, String b) {this.a = a;this.b = b;String result = parent.op(a, b);this.result = result;return result;}}

我们像以前一样记录基本信息,并添加一些新代码来记录输入和输出。

public class AdvancedTestListener extends SimpleTestListener {AdvancedTestListener() {}/*** Log information as before but also log call details.*/public void endTest(Test test) {super.endTest(test);// add captured inputs and outputsif (test instanceof MyAdvancedTest) {MyTestedClass obj = ((MyAdvancedTest) test).obj;if (obj instanceof MyFacadeClass) {MyFacadeClass facade = (MyFacadeClass) obj;System.out.printf("[, , %s, %s, %s]\n", facade.getA(), facade.getB(), facade.getResult());}}}
}

日志现在显示基本信息和呼叫详细信息。

[MyAdvancedTest, test2, 8/3/15 12:13 AM, 33, null, true]
[, , null, b, :b]
[MyAdvancedTest, test3, 8/3/15 12:13 AM, 0, null, true]
[, , a, null, a:]
[MyAdvancedTest, test4, 8/3/15 12:13 AM, 0, null, true]
[, , null, null, :]
[MyAdvancedTest, test1, 8/3/15 12:13 AM, 0, null, true]
[, , a, b, a:b]

我们希望将基本详细信息和呼叫详细信息相关联,但是通过添加唯一的测试ID可以轻松实现。

在现实世界中,这种方法还不够,在单个测试中,被测方法可能被多次调用。 在这种情况下,我们要么需要一种方法来缓存多组输入和输出值,要么需要扩展侦听器,以便我们可以在每个涵盖方法的末尾调用它。

通过将结果编码为XML或JSON而不是简单的列表,可以使结果更具扩展性。 这将使我们仅捕获感兴趣的值或轻松处理将来添加的字段。

[MyAdvancedTest, test2, 8/3/15 12:13 AM, 33, null, true]
{"a":null, "b":"b", "results":":b" }
[MyAdvancedTest, test3, 8/3/15 12:13 AM, 0, null, true]
{"a":"a", "b":null, "results":"a:" }
[MyAdvancedTest, test4, 8/3/15 12:13 AM, 0, null, true]
{"a":null, "b":null, "results":":" }
[MyAdvancedTest, test1, 8/3/15 12:13 AM, 0, null, true]
{"a":" a", "b":"b", "results":" a:b" }

捕获

现在我们可以通过重放捕获的输入来重新运行测试,但是盲目比较结果存在两个问题。 首先,如果我们只关心单个值,这将是很多不必要的工作。 其次,许多测试是不确定的(例如,它们使用随时间变化的固定数据甚至实时数据),而我们不关心的事情可能会改变。

这不是一个容易的问题。 如果幸运的话,测试将遵循标准模式,我们可以对正在执行的测试做出很好的猜测,但需要手动进行验证。

首先,我们需要使用捕获某些或所有方法调用的外观包装测试方法的结果。 调用历史记录应该以一种我们以后可以重播的形式提供,例如一系列方法名称和序列化参数。

其次,我们需要包装TestCase assertX方法,以便捕获最近的方法调用以及传递给assert调用的值(当然还有结果)。

通过示例最容易展示和删除该过程。 让我们从一个简单的POJO开始。

public class Person {private String firstName;private String lastName;public String getFirstName() { return firstName; }public String getLastName() { return lastName; }
}

在这种情况下,我们的外观仅需要记录方法名称。

典型的测试方法是

public void test1() {Person p = getTestPerson();assertEquals("John", p.getFirstName());assertEquals("Smith", p.getLastName());
}

使用包装的assertX方法

static PersonFacade person;public static void assertEquals(String expected, String actual) {// ignoring null handling...boolean results = expected.equals(actual);LOG.log("assertEquals('" + expected + "',"+person.getMethodsCalled()+ ") = " + results);person.clearMethodsCalled();if (!results) {throw new AssertionFailedError("Expected same:<" + expected + " > was not:<" + actual + ">");}
}

所以我们会得到像

assertEquals('John', getFirstName()) = true;
assertEquals('Smith', getLastName()) = false;

不难看出如何通过测试框架来解析它,但是现在还为时过早。 第二种测试方法是

public void test1() {Person p = getTestPerson();assertEquals("john", p.getFirstName().toLowerCase());
}

并且我们的简单代码不会捕获toLowerCase() 。 我们的日志将错误记录:

assertEquals('John', getFirstName()) = false;

更为病理的情况是:

public void test1() {Person p = getTestPerson();LOG.log("testing " + p.getFirstName());assertEquals("john", "joe");
}

断言与包装的类无关。

有明显的创可贴,例如,我们可以捕获外观中的返回值,但这是一个非常深的兔子洞,我们希望远离它。 我认为答案是做出合理的第一次尝试,手动验证结果,然后再做。 (可选:将测试重写为可以捕获的形式。)

翻译自: https://www.javacodegeeks.com/2015/08/adding-database-logging-to-junit3.html

将数据库日志添加到JUnit3相关推荐

  1. junit测试找不到数据库_将数据库日志添加到JUnit3

    junit测试找不到数据库 在过去的十年中,我们已经编写了成千上万的JUnit3测试,现在正尝试将结果合并到数据库中,而不是分散的日志文件中. 事实证明,扩展TestCase类非常容易做到这一点. 注 ...

  2. Sql Server实用操作-无数据库日志文件恢复数据库两种方法

    数据库日志文件的误删或别的原因引起数据库日志的损坏 方法一 1.新建一个同名的数据库 2.再停掉sql server(注意不要分离数据库) 3.用原数据库的数据文件覆盖掉这个新建的数据库 4.再重启s ...

  3. Linux下定时切割Mongodb数据库日志并删除指定天数前的日志记录(转)

    文章转自:http://www.osyunwei.com/archives/8998.html 说明: 操作系统:CentOS Mongodb安装目录:/usr/local/mongodb Mongo ...

  4. Sqlserver系统数据库和用户数据库日志文件全部丢失的恢复

    系统数据库和用户数据库日志都丢失的情况下,数据库无法启动,需要先重建系统数据库日志文件以便把sqlserver service拉起来,再重建用户数据库 试过但是行不通的办法 1.-mClient单用户 ...

  5. always on sql 收缩日志_使用alwayson后如何收缩数据库日志的方法详解

    问题描述: 在使用了alwayson后,主从库实时同步,原理是通过事务日志同步的,所以造成主数据库的事务日志一直在使用,而且无法收缩主数据库的事务日志. 在主从库同步时,收缩数据库是不起作用的.由于主 ...

  6. mongodb集群linux日志分割,Linux下Mongodb数据库日志切割及定时删除

    操作系统:CentOS Mongodb安装目录:/usr/local/mongodb Mongodb数据库存放目录:/home/data/mongodb/mongodb_data Mongodb日志存 ...

  7. shell脚本批量导出MYSQL数据库日志/按照最近N天的形式导出二进制日志[连载之构建百万访问量电子商务网站]...

    shell脚本批量导出MYSQL数据库日志/自动本地导出MYSQL二进制日志,按天备份[连载之构建百万访问量电子商务网站] 出处:http://jimmyli.blog.51cto.com/我站在巨人 ...

  8. 数据库日志系统之删库跑路后的亡羊补牢

    提出问题 服务器数据库异常重启了会造成什么样的影响? 不小心删除了数据库怎么办,或者不小心删除了数据库表中数据怎么办? 一条更新语句在数据库系统内部执行时与数据库日志系统有什么联系? 数据库备份,是每 ...

  9. 【MySQL 第17章_其他数据库日志】

    第17章_其他数据库日志 1.MySQL支持的日志 1.1日志类型 1.2日志的弊端 2.慢查询日志(slow query log) 3.1问题场景 3.2 查看当前状态 3.3 启动日志 3.4 查 ...

最新文章

  1. 2019微生物组—宏基因组分析技术研讨会第六期
  2. 企业搜索引擎开发之连接器connector(二十九)
  3. 软件工程结对开发团队成员以及题目介绍
  4. SharePoint 2013 APP 开发示例 (三)使用远程的web资源
  5. 遭遇11gR2 bug:kewastUnPackStats(): bad magic 1
  6. Xamarin使用ListView开启分组视图Cell数据展示bug处理
  7. 搞清这些陷阱,NULL和三值逻辑再也不会作妖
  8. SpringBoot运行原理初探
  9. 十二、java知识点——类加载机制(硬货)
  10. 团队-象棋游戏-代码设计规范
  11. 《现代操作系统教程》课程课后习题及答案
  12. xp3系统配置cocos2dx
  13. PreferenceScreen 的使用
  14. SQL SERVER 数据库日志已满,清理数据库日志的方法
  15. jQuery基础-定位与修改
  16. SpringBoot修改启动横幅标语(banner)
  17. JAVA后台对接苹果APNS(VOIP)实现推送
  18. 显示设备的接口分类:VGA,HDMI,DVI等等
  19. 关于微星主板官网下载网卡驱动后,网络适配器消失的问题
  20. java三元运算符用的多不多_Java多个三元运算符

热门文章

  1. 我们在进行着一场拔河比赛……
  2. 某同学工作之后的感悟
  3. android之微信分享图片
  4. 解决高版本SpringBoot整合swagger时启动报错:Failed to start bean ‘documentationPluginsBootstrapper‘ 问题
  5. ISO语言代码和国家代码+Locale常量+ISO货币符号
  6. 使用poi调整字体格式、添加单元格注释、自动调整列宽
  7. java 函数式编程 示例_功能Java示例 第8部分–更多纯函数
  8. gradle使用maven_使用Gradle – 2019版从Travis可靠发布到Maven Central
  9. 技术停滞_检测和测试停滞的流– RxJava常见问题解答
  10. web.xml.jsf_看一下即将发布的JSF 2.3 Push支持