使用 AspectJ 记录单元测试结果

Logging of unit test result using AspectJ

我正在尝试使用 AspectJ 记录我的测试套件结果。我想 'inject' 在我的代码中的每个 @Test 方法之后的结果标识代码,因此使用以下方法创建了一个方面:

  @After("execution(* *(..)) && @annotation(org.junit.Test)")
  public void afterTestMethod(JoinPoint joinPoint) {
      //identify test result
  }

但是,找不到如何检索测试方法结果 (passed/failed/skipped)。 有什么建议吗?

谢谢!

A) JUnit 运行 监听器

我假设您使用 Maven 或 Gradle 之类的东西构建您的项目,并将向您展示 JUnit 4 的 Maven 示例 RunListener:

在模块 test-tools 中,您将添加

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.13</version>
  <!--
    We build something with the JUnit API, not just run a test,
    so scope 'test' is not enough here
  -->
  <scope>compile</scope>
</dependency>

到您的 POM,然后在 src/main/java/...:

中有类似的东西
package de.scrum_master.testing;

import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;

public class ResultPrintingRunListener extends RunListener {
  @Override
  public void testRunStarted(Description description) {
    System.out.println("[RunStarted] description = " + description);
  }

  @Override
  public void testRunFinished(Result result) {
    System.out.println("[RunFinished] result = " + result);
    System.out.println("  run count = " + result.getRunCount());
    System.out.println("  failure count = " + result.getFailureCount());
    System.out.println("  assumption failure count = " + result.getAssumptionFailureCount());
    System.out.println("  ignored count = " + result.getIgnoreCount());
    System.out.println("  run time (ms) = " + result.getRunTime());
  }

  @Override
  public void testSuiteStarted(Description description) {
    System.out.println("[SuiteStarted] description = " + description);
  }

  @Override
  public void testSuiteFinished(Description description) {
    System.out.println("[SuiteFinished] description = " + description);
  }

  @Override
  public void testStarted(Description description) {
    System.out.println("[Started] description = " + description);
  }

  @Override
  public void testFinished(Description description) {
    System.out.println("[Finished] description = " + description);
  }

  @Override
  public void testFailure(Failure failure) {
    System.out.println("[Failure] failure = " + failure);
  }

  @Override
  public void testAssumptionFailure(Failure failure) {
    System.out.println("[AssumptionFailure] failure = " + failure);
  }

  @Override
  public void testIgnored(Description description) {
    System.out.println("[Ignored] description = " + description);
  }
}

您可以将 API 调用放在适当的位置,而不是记录日志。

在您的应用程序测试模块中,您将添加 test-tools 模块作为依赖项,然后像这样配置您的 Maven Surefire/Failsafe 插件:

<plugin>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>2.22.2</version>
  <configuration>
    <properties>
      <property>
        <name>listener</name>
        <value>de.scrum_master.testing.ResultPrintingRunListener</value>
      </property>
    </properties>
  </configuration>
</plugin>

如果你 运行 在 Maven 中进行这样的测试,...

package de.scrum_master.agent.aspect;

import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;

public class MyTest {
  @Test
  public void one() {
    Assert.assertEquals("xander", "Alexander".substring(3));
  }

  @Test
  public void two() {
    Assert.assertEquals("Alex", "Alexander".substring(3));
  }

  @Test
  public void three() {
    Assert.assertEquals(11, 1 / 0);
  }

  @Test
  @Ignore
  public void four() {
    Assert.assertNull(null);
  }
}

... Maven 打印:

[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[RunStarted] description = null
[INFO] Running de.scrum_master.agent.aspect.MyTest
[SuiteStarted] description = de.scrum_master.agent.aspect.MyTest
[Started] description = one(de.scrum_master.agent.aspect.MyTest)
[Finished] description = one(de.scrum_master.agent.aspect.MyTest)
[Started] description = two(de.scrum_master.agent.aspect.MyTest)
[Failure] failure = two(de.scrum_master.agent.aspect.MyTest): expected:<[Alex]> but was:<[xander]>
[Finished] description = two(de.scrum_master.agent.aspect.MyTest)
[Ignored] description = four(de.scrum_master.agent.aspect.MyTest)
[Started] description = three(de.scrum_master.agent.aspect.MyTest)
[Failure] failure = three(de.scrum_master.agent.aspect.MyTest): / by zero
[Finished] description = three(de.scrum_master.agent.aspect.MyTest)
[SuiteFinished] description = de.scrum_master.agent.aspect.MyTest
[ERROR] Tests run: 4, Failures: 1, Errors: 1, Skipped: 1, Time elapsed: 0.11 s <<< FAILURE! - in de.scrum_master.agent.aspect.MyTest
[ERROR] de.scrum_master.agent.aspect.MyTest.two  Time elapsed: 0.007 s  <<< FAILURE!
org.junit.ComparisonFailure: expected:<[Alex]> but was:<[xander]>
    at de.scrum_master.agent.aspect.MyTest.two(MyTest.java:31)

[ERROR] de.scrum_master.agent.aspect.MyTest.three  Time elapsed: 0.001 s  <<< ERROR!
java.lang.ArithmeticException: / by zero
    at de.scrum_master.agent.aspect.MyTest.three(MyTest.java:36)

[RunFinished] result = org.junit.runner.Result@79be0360
  run count = 3
  failure count = 2
  assumption failure count = 0
  ignored count = 1
  run time (ms) = 0

JUnit 测试观察器规则

如果您喜欢在测试中独立于特定 JUnit 运行ner 的东西,请使用 JUnit TestWatcher:

创建一个像这样的基础 class
package de.scrum_master.agent.aspect;

import org.junit.Rule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;

public class TestBase {
  @Rule(order = Integer.MIN_VALUE)
  public TestWatcher testWatcher = new TestWatcher() {
    @Override
    protected void failed(Throwable e, Description description) {
      System.out.println("[TestWatcher failed] description = " + description +", e = " + e);
    }

    @Override
    protected void succeeded(Description description) {
      System.out.println("[TestWatcher succeeded] description = " + description);
    }
  };
}

然后直接或间接地进行所有测试classes extends TestBase。如果你 运行 测试,例如从 IDE,你看到(缩短输出,仅测试观察者日志):

[TestWatcher succeeded] description = one(de.scrum_master.agent.aspect.MyTest)
[TestWatcher failed] description = two(de.scrum_master.agent.aspect.MyTest), e = org.junit.ComparisonFailure: expected:<[Alex]> but was:<[xander]>
[TestWatcher failed] description = three(de.scrum_master.agent.aspect.MyTest), e = java.lang.ArithmeticException: / by zero

你看,测试观察器中的事件较少,例如忽略的测试不会被报告。

尽管我非常喜欢 AspectJ(这就是我发现这个问题的方式),但我认为您应该首先尝试适当地配置 JUnit,并且可能会对它感到满意。如果出于某种原因 - 请解释,如果是 - 你仍然坚持 AOP 解决方案,请告诉我。


(C) 直接拦截 JUnit 测试

因为你坚持:

package de.scrum_master.aspectj;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class TestResultInterceptor {
  @AfterReturning(value = "execution(* *(..)) && @annotation(org.junit.Test)", returning = "result")
  public void allMethods(JoinPoint joinPoint, Object result) {
    System.out.println(joinPoint + " -> PASSED");
  }

  @AfterThrowing(value = "execution(* *(..)) && @annotation(org.junit.Test)", throwing = "throwable")
  public void allMethods(JoinPoint joinPoint, Throwable throwable) {
    System.out.println(joinPoint + " -> FAILED: " + throwable);
  }
}

当运行在我的IDE中进行上面的JUnit测试时,控制台日志是:

execution(void de.scrum_master.testing.MyTest.one()) -> PASSED
execution(void de.scrum_master.testing.MyTest.two()) -> FAILED: org.junit.ComparisonFailure: expected:<[Alex]> but was:<[xander]>
execution(void de.scrum_master.testing.MyTest.three()) -> FAILED: java.lang.ArithmeticException: / by zero

我想你可以从这里开始。