【问题标题】:Get name of currently executing test in JUnit 4获取 JUnit 4 中当前正在执行的测试的名称
【发布时间】:2010-10-03 04:05:53
【问题描述】:

在 JUnit 3 中,我可以像这样获取当前正在运行的测试的名称:

public class MyTest extends TestCase
{
    public void testSomething()
    {
        System.out.println("Current test is " + getName());
        ...
    }
}

将打印“当前测试是 testSomething”。

在 JUnit 4 中是否有任何开箱即用或简单的方法可以做到这一点?

背景:显然,我不想只打印测试的名称。我想加载存储在与测试同名的资源中的测试特定数据。你知道,convention over configuration 等等。

【问题讨论】:

  • 以上代码在 JUnit 4 中为您提供了什么?
  • JUnit 3 测试扩展了定义 getName() 的 TestCase。 JUnit 4 测试不扩展基类,因此根本没有 getName() 方法。
  • 我有一个类似的问题,我想设置测试名称,因为我使用的参数化运行器只给我编号的测试用例。
  • 使用 Test 或 TestWatcher 的可爱解决方案......只是想知道(大声)是否应该需要这个?您可以通过查看 Gradle 给出的时序输出图表来确定测试是否运行缓慢。您永远不需要知道测试运行的顺序...?

标签: java unit-testing junit


【解决方案1】:

JUnit 4.7 添加了这个功能,似乎使用TestName-Rule。看起来这将为您提供方法名称:

import org.junit.Rule;

public class NameRuleTest {
    @Rule public TestName name = new TestName();

    @Test public void testA() {
        assertEquals("testA", name.getMethodName());
    }

    @Test public void testB() {
        assertEquals("testB", name.getMethodName());
    }
}

【讨论】:

  • 另请注意,@before 中没有 TestName :( 请参阅:old.nabble.com/…
  • 显然较新版本的 JUnit 在 @Before 之前执行 @Rule - 我是 JUnit 的新手,在我的 @Before 中依赖 TestName 没有任何困难。
  • 如果您使用参数化测试“name.getMethodName()”将返回 {testA[0], testA[1], etc},因此我使用类似:assertTrue(name.getMethodName().匹配("testA(\[\\d\])?"));
  • @DuncanJones 为什么提议的替代方案“更有效”?
【解决方案2】:

JUnit 4.9.x 及更高版本

自 JUnit 4.9 起,TestWatchman 类已被弃用,取而代之的是 TestWatcher 类,该类具有调用:

@Rule
public TestRule watcher = new TestWatcher() {
   protected void starting(Description description) {
      System.out.println("Starting test: " + description.getMethodName());
   }
};

注意:包含的类必须声明为public

JUnit 4.7.x - 4.8.x

以下方法将为类中的所有测试打印方法名称:

@Rule
public MethodRule watchman = new TestWatchman() {
   public void starting(FrameworkMethod method) {
      System.out.println("Starting test: " + method.getName());
   }
};

【讨论】:

  • @takacsot 这太令人惊讶了。您能否发布一个关于此的新问题并在此处向我发送链接?
  • 为什么要使用public 字段?
【解决方案3】:

JUnit 5 及更高版本

JUnit 5 中,您可以注入 TestInfo,这简化了向测试方法注入测试元数据的过程。例如:

@Test
@DisplayName("This is my test")
@Tag("It is my tag")
void test1(TestInfo testInfo) {
    assertEquals("This is my test", testInfo.getDisplayName());
    assertTrue(testInfo.getTags().contains("It is my tag"));
}

查看更多:JUnit 5 User guideTestInfo javadoc

【讨论】:

    【解决方案4】:

    试试这个:

    public class MyTest {
            @Rule
            public TestName testName = new TestName();
    
            @Rule
            public TestWatcher testWatcher = new TestWatcher() {
                @Override
                protected void starting(final Description description) {
                    String methodName = description.getMethodName();
                    String className = description.getClassName();
                    className = className.substring(className.lastIndexOf('.') + 1);
                    System.err.println("Starting JUnit-test: " + className + " " + methodName);
                }
            };
    
            @Test
            public void testA() {
                    assertEquals("testA", testName.getMethodName());
            }
    
            @Test
            public void testB() {
                    assertEquals("testB", testName.getMethodName());
            }
    }
    

    输出如下:

    Starting JUnit-test: MyTest testA
    Starting JUnit-test: MyTest testB
    

    注意:如果您的测试是 TestCase 的子类,这不起作用!测试运行,但 @Rule 代码从未运行。

    【讨论】:

    • 上帝保佑你在这个例子中的注释。
    • “这不起作用” - 例如 - cucumber 忽略 @Rule 注释
    【解决方案5】:

    考虑使用 SLF4J(Java 的简单日志记录外观)使用参数化消息提供了一些巧妙的改进。将 SLF4J 与 JUnit 4 规则实现相结合可以提供更高效的测试类日志记录技术。

    import org.junit.Rule;
    import org.junit.Test;
    import org.junit.rules.MethodRule;
    import org.junit.rules.TestWatchman;
    import org.junit.runners.model.FrameworkMethod;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class LoggingTest {
    
      @Rule public MethodRule watchman = new TestWatchman() {
        public void starting(FrameworkMethod method) {
          logger.info("{} being run...", method.getName());
        }
      };
    
      final Logger logger =
        LoggerFactory.getLogger(LoggingTest.class);
    
      @Test
      public void testA() {
    
      }
    
      @Test
      public void testB() {
    
      }
    }
    

    【讨论】:

      【解决方案6】:

      一种复杂的方法是通过继承 org.junit.runners.BlockJUnit4ClassRunner 来创建自己的 Runner。

      然后你可以这样做:

      public class NameAwareRunner extends BlockJUnit4ClassRunner {
      
          public NameAwareRunner(Class<?> aClass) throws InitializationError {
              super(aClass);
          }
      
          @Override
          protected Statement methodBlock(FrameworkMethod frameworkMethod) {
              System.err.println(frameworkMethod.getName());
              return super.methodBlock(frameworkMethod);
          }
      }
      

      然后,对于每个测试类,您需要添加一个 @RunWith(NameAwareRunner.class) 注释。或者,如果您不想每次都记住它,可以将该注释放在 Test 超类上。当然,这会限制您对跑步者的选择,但这是可以接受的。

      此外,将当前测试名称从 Runner 中取出并放入您的框架中可能需要一点功夫,但这至少可以让您获得名称。

      【讨论】:

      • 至少在概念上,这个想法对我来说似乎相当简单。我的意思是:我不会说它令人费解。
      • “在测试超类上......” - 请不要再使用可怕的基于继承的设计模式。这就是 JUnit3!
      【解决方案7】:

      JUnit 4 没有任何开箱即用的机制让测试用例获得自己的名称(包括在设置和拆卸期间)。

      【讨论】:

      • 除了检查堆栈之外,是否还有一种开箱即用的机制?
      • 下面给出的答案并非如此!也许将正确的答案分配给其他人?
      【解决方案8】:
      String testName = null;
      StackTraceElement[] trace = Thread.currentThread().getStackTrace();
      for (int i = trace.length - 1; i > 0; --i) {
          StackTraceElement ste = trace[i];
          try {
              Class<?> cls = Class.forName(ste.getClassName());
              Method method = cls.getDeclaredMethod(ste.getMethodName());
              Test annotation = method.getAnnotation(Test.class);
              if (annotation != null) {
                  testName = ste.getClassName() + "." + ste.getMethodName();
                  break;
              }
          } catch (ClassNotFoundException e) {
          } catch (NoSuchMethodException e) {
          } catch (SecurityException e) {
          }
      }
      

      【讨论】:

      • 我可以说他只是想展示一个解决方案..不明白为什么投反对票..@downvoter:至少,至少,添加有用的信息..
      • @skaffman 我们都喜欢看到各种替代解决方案。这是我正在寻找的最接近的一个:不是直接在测试类中而是在测试期间使用的类中获取测试名称(例如在记录器组件中的某处)。在那里,与测试相关的注释不再起作用。
      【解决方案9】:

      基于之前的评论并进一步考虑,我创建了一个 TestWather 的扩展,您可以在您的 JUnit 测试方法中使用它:

      public class ImportUtilsTest {
          private static final Logger LOGGER = Logger.getLogger(ImportUtilsTest.class);
      
          @Rule
          public TestWatcher testWatcher = new JUnitHelper(LOGGER);
      
          @Test
          public test1(){
          ...
          }
      }
      

      测试助手类是下一个:

      public class JUnitHelper extends TestWatcher {
      private Logger LOGGER;
      
      public JUnitHelper(Logger LOGGER) {
          this.LOGGER = LOGGER;
      }
      
      @Override
      protected void starting(final Description description) {
          LOGGER.info("STARTED " + description.getMethodName());
      }
      
      @Override
      protected void succeeded(Description description) {
          LOGGER.info("SUCCESSFUL " + description.getMethodName());
      }
      
      @Override
      protected void failed(Throwable e, Description description) {
          LOGGER.error("FAILURE " + description.getMethodName());
      }
      }
      

      享受吧!

      【讨论】:

      • 嗨,ImportUtilsTest 是什么,我收到一个错误,它似乎是一个记录器类,我有更多信息吗?谢谢
      • 命名类只是JUnit测试类的一个例子:JUnitHelper的用户。我将更正使用示例。
      • 啊,现在我觉得自己很笨,这太明显了。非常感谢! ;)
      【解决方案10】:
      @ClassRule
      public static TestRule watchman = new TestWatcher() {
          @Override
          protected void starting( final Description description ) {
              String mN = description.getMethodName();
              if ( mN == null ) {
                  mN = "setUpBeforeClass..";
              }
      
              final String s = StringTools.toString( "starting..JUnit-Test: %s.%s", description.getClassName(), mN );
              System.err.println( s );
          }
      };
      

      【讨论】:

        【解决方案11】:

        在 JUnit 5 中,TestInfo 充当 JUnit 4 中 TestName 规则的替代品。

        来自文档:

        TestInfo 用于注入有关当前测试的信息或 容器到@Test、@RepeatedTest、@ParameterizedTest、 @TestFactory、@BeforeEach、@AfterEach、@BeforeAll 和 @AfterAll 方法。

        要检索当前执行的测试的方法名称,您有两个选择:String TestInfo.getDisplayName()Method TestInfo.getTestMethod()

        仅检索当前测试方法的名称TestInfo.getDisplayName() 可能不够,因为测试方法的默认显示名称为methodName(TypeArg1, TypeArg2, ... TypeArg3)
        @DisplayName("..") 中复制方法名称并不是一个好主意。

        作为替代方案,您可以使用 TestInfo.getTestMethod() 返回一个 Optional&lt;Method&gt; 对象。
        如果检索方法在测试方法中使用,您甚至不需要测试 Optional 包装的值。

        import org.junit.jupiter.api.Assertions;
        import org.junit.jupiter.api.TestInfo;
        import org.junit.jupiter.api.Test;
        
        @Test
        void doThat(TestInfo testInfo) throws Exception {
            Assertions.assertEquals("doThat(TestInfo)",testInfo.getDisplayName());
            Assertions.assertEquals("doThat",testInfo.getTestMethod().get().getName());
        }
        

        【讨论】:

          【解决方案12】:

          JUnit 5 通过 ExtensionContext

          优势:

          您可以通过覆盖afterEach(ExtensionContext context) 来获得ExtensionContext 的附加功能。

          public abstract class BaseTest {
          
              protected WebDriver driver;
          
              @RegisterExtension
              AfterEachExtension afterEachExtension = new AfterEachExtension();
          
              @BeforeEach
              public void beforeEach() {
                  // Initialise driver
              }
          
              @AfterEach
              public void afterEach() {
                  afterEachExtension.setDriver(driver);
              }
          
          }
          
          public class AfterEachExtension implements AfterEachCallback {
          
              private WebDriver driver;
          
              public void setDriver(WebDriver driver) {
                  this.driver = driver;
              }
          
              @Override
              public void afterEach(ExtensionContext context) {
                  String testMethodName = context.getTestMethod().orElseThrow().getName();
                  // Attach test steps, attach scsreenshots on failure only, etc.
                  driver.quit();
              }
          
          }
          

          【讨论】:

            【解决方案13】:

            我建议您将测试方法名称与测试数据集分离。我将建模一个 DataLoaderFactory 类,该类从您的资源中加载/缓存测试数据集,然后在您的测试用例中调用一些接口方法,该方法返回一组测试用例的测试数据。将测试数据绑定到测试方法名称假设测试数据只能使用一次,在大多数情况下,我建议在多个测试中使用相同的测试数据来验证业务逻辑的各个方面。

            【讨论】:

              【解决方案14】:

              您可以使用Slf4jTestWatcher 实现此目的

              private static Logger _log = LoggerFactory.getLogger(SampleTest.class.getName());
              
              @Rule
              public TestWatcher watchman = new TestWatcher() {
                  @Override
                  public void starting(final Description method) {
                      _log.info("being run..." + method.getMethodName());
                  }
              };
              

              【讨论】:

                【解决方案15】:

                我有一个扩展 TestCase 的 Junit4 测试类,因此带有 @Rule 的示例不起作用(如其他答案中所述)。

                但是,如果你的类扩展了 TestCase,你可以使用 getName() 来获取当前的测试名称,这样就可以了:

                @Before
                public void setUp() {
                  System.out.println("Start test: " + getName());
                }
                
                @After
                public void tearDown() {
                  System.out.println("Finish test: " + getName());
                }
                

                【讨论】:

                  【解决方案16】:

                  我通常使用这样的东西:

                  /** Returns text with test method name
                      @param offset index of method on call stack to print, 1 for a caller of this method.
                      */
                      static String getName(int offset)
                      { 
                          Throwable t = new Throwable();
                          t.fillInStackTrace();
                          return 
                                 t.getStackTrace()[offset].getMethodName()+":"+t.getStackTrace()[offset].getLineNumber(); 
                      };
                  

                  这正是打印堆栈跟踪时使用的异常。 根据确切的上下文,您可能必须找出正确的偏移值。它粗犷而原始坚韧,并没有使用任何花哨的现代期货。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2018-06-18
                    • 2013-06-18
                    • 2013-06-18
                    • 1970-01-01
                    • 2012-01-12
                    • 2011-03-11
                    • 2010-09-07
                    • 1970-01-01
                    相关资源
                    最近更新 更多