Feature Toggle JUnit

  Feature Toggle,简单来讲,就是一个开关,将未完成功能的代码屏蔽后发布到生产环境,从而避免多分支的状况。之因此有本文的产生,就是源于此情景。在引入Feature Toggle的同时,咱们发现以前对这些未开发完功能的代码的单元测试不是很稳定,并且若是咱们在用feature toggle关掉这个功能以后,这些测试也是对发布毫无价值可言,全部咱们须要将这些测试所有屏蔽掉,以避免影响运行其余测试结果。app

  在通过项目组讨论以后,咱们毅然决然摒弃了直接采用@Ignore的低级作法,决定本身来实现一个简单的toggle,用annotation读取配置文件的方式管理须要被屏蔽的测试。下面先介绍两种方式来实现这一toggle:框架

  这两种方法都必须定义一个annotation和配置文件,定义以下:ide

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface FeatureToggle {
    public String feature();
}

1.先介绍一种简单的实现,也就是给测试实现一个runner, 封装一层判断条件,让他处理掉包含咱们本身定义的annotation从而达到toggle的开关做用。函数

public class FeatureRunner extends BlockJUnit4ClassRunner {
    @Override
    protected void runChild(FrameworkMethod method, RunNotifier notifier) {
        FeatureToggle annotationOnMethod = method.getAnnotation(FeatureToggle.class);
        FeatureToggle annotationOnClass = method.getMethod().getDeclaringClass().getAnnotation(FeatureToggle.class);

        if (annotationOnClass != null) {
            annotationOnMethod = annotationOnClass;
        }

        String featureToggle = getToggleValue(annotationOnMethod); //从配置文件中读取annotationOnMethod.feature() 在properties文件中对应的value

        if (annotationOnMethod != null && "off".equals(featureToggle)) {
            notifier.fireTestIgnored(describeChild(method));
        } else {
            super.runChild(method, notifier);
        }
    }
}

接下来在测试代码中使用方法以下:单元测试

@RunWith(FeatureRunner.class)
public class FeatureRunnerTest {

    @Test
    @FeatureToggle(feature = "feature")
    public void testTurnOn() throws Exception {
        fail();
    }
}

配置文件只需设置 feature=off 便可关掉全部同一个功能的全部加有annotation的测试。测试

 

2.采用@Rule的方式。JUnit 4.7 已经发布了,该版本具备一个重要的新特性:Rule。来一段最新junit框架里BlockJUnit4ClassRunner类中的代码来lua

    protected Statement methodBlock(FrameworkMethod method) {
        Object test;
        try {
            test= new ReflectiveCallable() {
                @Override
                protected Object runReflectiveCall() throws Throwable {
                    return createTest();
                }
            }.run();
        } catch (Throwable e) {
            return new Fail(e);
        }
        Statement statement= methodInvoker(method, test);
        statement= possiblyExpectingExceptions(method, test, statement);
        statement= withPotentialTimeout(method, test, statement);
        statement= withBefores(method, test, statement);
        statement= withAfters(method, test, statement);
        statement= withRules(method, test, statement);
        return statement;
    }

这段代码实际上能够看出在执行@Before和@After操做的同时,还会调用@Rule的,对于具体的实现你们能够去这个类里面深究,我就直接告诉你们,@Rule会在before以前执行。spa

下面是如何本身写一个类来实现TestRule历来控制测试是否执行:code

public class FeatureToggleRule implements TestRule {
    @Override
    public Statement apply(Statement base, Description description) {
        FeatureToggle annotationOnClass = description.getTestClass().getAnnotation(FeatureToggle.class);
        FeatureToggle annotationOnMethod = description.getAnnotation(FeatureToggle.class);
        if (annotationOnClass != null) {
            annotationOnMethod = annotationOnClass;
        }

        if (annotationOnMethod != null && isToggleOFF(annotationOnMethod)) { //读取配置文件
            return new IgnoreStatement();
        }
        return base;
    }

    class IgnoreStatement extends Statement {
        @Override
        public void evaluate() throws Throwable {
          throw new AssumptionViolatedException("ignore by rule");
        }
    }
}

这里若是方法名或者函数名上又跟第一种实现一样的annotation和配置文件,加上以下声明:blog

public class FeatureToggleRuleTest {
    @Rule
    public TestRule rule = new FeatureToggleRule();

    @Test
    @FeatureToggle(feature = "feature")
    public void testTurnOn() throws Exception {
        fail();
    }
}

Rule更具备灵活性,而且功能不单单如此,咱们能够在给本身的测试添加不少rule规则,这里不进行深究。

 

总结:两种方式,都实现同一个思想,用注解加配置文件来控制全部未完成功能测试部分的开关控制,并且简单易行,而且能够为你们拓展更多的junit需求提供指导参考,谢谢。

相关文章
相关标签/搜索