国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Programming DSL:JSpec

TANKING / 881人閱讀

摘要:命名模式為了做到自動發(fā)現(xiàn)機制,在運行時完成用例的組織,規(guī)定所有的測試用例必須遵循的函數(shù)原型。在后文介紹,可以將理解為及其的運行時行為其中,對于于子句,對于于子句。將的執(zhí)行序列行為固化。

There are two ways of constructing a software design. One way is to make it so simple that there are obviously no deficiencies. And the other way is to make it so complicated that there are no obvious deficiencies. -- C.A.R. Hoare

前世今生

本文是《Programming DSL》系列文章的第2篇,如果該主題感興趣,可以查閱如下文章:

Programming DSL: Implements JHamcrest

正交設計

本文通過「JSpec」的設計和實現(xiàn)的過程,加深認識「內(nèi)部DSL」設計的基本思路。JSpec是使用Java8實現(xiàn)的一個簡單的「BDD」測試框架。

動機

Java社區(qū)中,JUnit是一個廣泛被使用的測試框架。不幸的是,JUnit的測試用例必須遵循嚴格的「標識符」命名規(guī)則,給程序員帶來了很大的不便。

命名模式

Junit為了做到「自動發(fā)現(xiàn)」機制,在運行時完成用例的組織,規(guī)定所有的測試用例必須遵循public void testXXX()的函數(shù)原型。

public void testTrue() {
  Assert.assertTrue(true);
}
注解

Java 1.5支持「注解」之后,社區(qū)逐步意識到了「注解優(yōu)于命名模式」的最佳實踐,JUnit使用@Test注解,增強了用例的表現(xiàn)力。

@Test
public void alwaysTrue() {
  Assert.assertTrue(true);
}
Given-When-Then

經(jīng)過實踐證明,基于場景驗收的Given-When-Then命名風格具有強大的表現(xiàn)力。但JUnit遵循嚴格的標示符命名規(guī)則,程序員需要承受巨大的痛苦。

這種混雜「駝峰」和「下劃線」的命名風格,雖然在社區(qū)中得到了廣泛的應用,但在重命名時,變得非常不方便。

public class GivenAStack {
  @Test
  public void should_be_empty_when_created() { 
  }

  @Test
  public void should_pop_the_last_element_pushed_onto_the_stack() { 
  }
}
新貴

RSpec, Cucumber, Jasmine等為代表的[BDD」(Behavior-Driven Development)測試框架以強大的表現(xiàn)力,迅速得到了社區(qū)的廣泛應用。其中,RSpec, Jasmine就是我較為喜愛的測試框架。例如,JasmineJavaScript測試用例是這樣的。

describe("A suite", function() {
  it("contains spec with an expectation", function() {
    expect(true).toBe(true);
  });
});
JSpec

我們將嘗試設計和實現(xiàn)一個Java版的BDD測試框架:JSpec。它的風格與Jasmine基本類似,并與Junit4配合得完美無瑕。

@RunWith(JSpec.class)
public class JSpecs {{
  describe("A spec", () -> {
    List items = new ArrayList<>();

    before(() -> {
      items.add("foo");
      items.add("bar");
    });

    after(() -> {
      items.clear();
    });

    it("runs the before() blocks", () -> {
      assertThat(items, contains("foo", "bar"));
    });

    describe("when nested", () -> {
      before(() -> {
        items.add("baz");
      });

      it("runs before and after from inner and outer scopes", () -> {
        assertThat(items, contains("foo", "bar", "baz"));
      });
    });
  });
}}
初始化塊
public class JSpecs {{
  ......
}}

嵌套兩層{},這是Java的一種特殊的初始化方法,常稱為初始化塊。其行為與如下代碼類同,但它更加簡潔、漂亮。

public class JSpecs {
  public JSpecs() {
    ......
  }
}
代碼塊

describe, it, before, after都存在一個() -> {...}代碼塊,以便實現(xiàn)行為的定制化,為此先抽象一個Block的概念。

@FunctionalInterface
public interface Block {
  void apply() throws Throwable;
}
雛形

定義如下幾個函數(shù),明確JSpec DSL的基本雛形。

public class JSpec {
  public static void describe(String desc, Block block) {
    ......
  }

  public static void it(String behavior, Block block) {
    ......
  }

  public static void before(Block block) {
    ......
  }

  public static void after(Block block) {
    ......
  }
上下文

describe可以嵌套describe, it, before, after的代碼塊,并且外層的describe給內(nèi)嵌的代碼塊建立了「上下文」環(huán)境。

例如,items在最外層的describe中定義,它對describe整個內(nèi)部都可見。

隱式樹

describe可以嵌套describe,并且describe為內(nèi)部的結構建立「上下文」,因此describe之間建立了一棵「隱式樹」。

領域模型

為此,抽象出了Context的概念,用于描述describe的運行時。也就是是,Context描述了describe內(nèi)部可見的幾個重要實體:

List befores:before代碼塊集合

List afters:after代碼塊集合

Description desc:包含了父子之間的層次關系等上下文描述信息

Deque executors:執(zhí)行器的集合。

Executor在后文介紹,可以將Executor理解為Context及其Spec的運行時行為;其中,Context對于于desribe子句,Spec對于于it子句。

因為describe之間存在「隱式樹」的關系,ContextSpec之間也就形成了「隱式樹」的關系。

參考實現(xiàn)
public class Context {

  private List befores = new ArrayList<>();
  private List afters = new ArrayList<>();

  private Deque executors = new ArrayDeque<>();
  private Description desc;
  
  public Context(Description desc) {
    this.desc = desc;
  }
  
  public void addChild(Context child) {
    desc.addChild(child.desc);
    executors.add(child);
    
    child.addBefore(collect(befores));
    child.addAfter(collect(afters));
  }

  public void addBefore(Block block) {
    befores.add(block);
  }

  public void addAfter(Block block) {
    afters.add(block);
  }

  public void addSpec(String behavior, Block block) {
    Description spec = createTestDescription(desc.getClassName(), behavior);
    desc.addChild(spec);
    addExecutor(spec, block);
  }

  private void addExecutor(Description desc, Block block) {
    Spec spec = new Spec(desc, blocksInContext(block));
    executors.add(spec);
  }

  private Block blocksInContext(Block block) {
    return collect(collect(befores), block, collect(afters));
  }
}
實現(xiàn)addChild

describe嵌套describe時,通過addChild完成了兩件重要工作:

子Context」向「父Context」的注冊;也就是說,Context之間形成了「樹」形結構;

控制父Context中的before/after的代碼塊集合對子Context的可見性;

public void addChild(Context child) {
  desc.addChild(child.desc);
  executors.add(child);
    
  child.addBefore(collect(befores));
  child.addAfter(collect(afters));
}

其中,collect定義于Block接口中,完成before/after代碼塊「集合」的迭代處理。這類似于OO世界中的「組合模式」,它們代表了一種隱式的「樹狀結構」。

public interface Block {
  void apply() throws Throwable;

  static Block collect(Iterable blocks) {
    return () -> {
      for (Block b : blocks) {
        b.apply();
      }
    };
  }
}
實現(xiàn)addExecutor

其中,Executor存在兩種情況:

Spec: 使用it定義的用例的代碼塊

Context: 使用describe定義上下文。

為此,addExecutoraddSpec, addChild所調(diào)用。addExecutor調(diào)用時,將Spec注冊到Executor集合中,并定義了Spec的「執(zhí)行規(guī)則」。

private void addExecutor(Description desc, Block block) {
    Spec spec = new Spec(desc, blocksInContext(block));
    executors.add(spec);
  }

  private Block blocksInContext(Block block) {
    return collect(collect(befores), block, collect(afters));
  }

blocksInContextit的「執(zhí)行序列」行為固化。

首先執(zhí)行before代碼塊集合;

然后執(zhí)行it代碼塊;

最后執(zhí)行after代碼塊集合;

抽象Executor

之前談過,Executor存在兩種情況:

Spec: 使用it定義的用例的代碼塊

Context: 使用describe定義上下文。

也就是說,Executor構成了一棵「樹狀」的數(shù)據(jù)結構;it扮演了「葉子節(jié)點」的角色;Context扮演了「非葉子節(jié)點」的角色。為此,Executor的設計采用了「組合模式」。

import org.junit.runner.notification.RunNotifier;

@FunctionalInterface
public interface Executor {
  void exec(RunNotifier notifier);
}
葉子節(jié)點:Spec

Spec完成對it行為的封裝,當exec時完成it代碼塊() -> {...}的調(diào)用。

public class Spec implements Executor {

  public Spec(Description desc, Block block) {
    this.desc = desc;
    this.block = block;
  }

  @Override
  public void exec(RunNotifier notifier) {
    notifier.fireTestStarted(desc);
    runSpec(notifier);
    notifier.fireTestFinished(desc);
  }

  private void runSpec(RunNotifier notifier) {
    try {
      block.apply();
    } catch (Throwable t) {
      notifier.fireTestFailure(new Failure(desc, t));
    }
  }

  private Description desc;
  private Block block;
}
非葉子節(jié)點:Context
public class Context implements Executor {
  ......
  
  private Description desc;
  
  @Override
  public void exec(RunNotifier notifier) {
    for (Executor e : executors) {
      e.exec(notifier);
    }
  }
}
實現(xiàn)DSL

有了Context的領域模型的基礎,DSL的實現(xiàn)變得簡單了。

public class JSpec {
  private static Deque ctxts = new ArrayDeque();

  public static void describe(String desc, Block block) {
    Context ctxt = new Context(createSuiteDescription(desc));
    enterCtxt(ctxt, block);
  }

  public static void it(String behavior, Block block) {
    currentCtxt().addSpec(behavior, block);
  }

  public static void before(Block block) {
    currentCtxt().addBefore(block);
  }

  public static void after(Block block) {
    currentCtxt().addAfter(block);
  }

  private static void enterCtxt(Context ctxt, Block block) {
    currentCtxt().addChild(ctxt);
    applyBlock(ctxt, block);
  }

  private static void applyBlock(Context ctxt, Block block) {
    ctxts.push(ctxt);
    doApplyBlock(block);
    ctxts.pop();
  }

  private static void doApplyBlock(Block block) {
    try {
      block.apply();
    } catch (Throwable e) {
      it("happen to an error", failing(e));
    }
  }

  private static Context currentCtxt() {
    return ctxts.peek();
  }
}
上下文切換

但為了控制Context之間的「樹型關系」(即describe的嵌套關系),為此建立了一個Stack的機制,保證運行時在某一個時刻Context的唯一性。

只有describe的調(diào)用會開啟「上下文的建立」,并完成上下文「父子關系」的鏈接。其余操作,例如it, before, after都是在當前上下文進行「元信息」的注冊。

虛擬的根結點

使用靜態(tài)初始化塊,完成「虛擬根結點」的注冊;也就是說,在運行時初始化時,棧中已存在唯一的 Context("JSpec: All Specs")虛擬根節(jié)點。

public class JSpec {
  private static Deque ctxts = new ArrayDeque();

  static {
    ctxts.push(new Context(createSuiteDescription("JSpec: All Specs")));
  }
  
  ......
}
運行器

為了配合JUnit框架將JSpec運行起來,需要定制一個JUnitRunner

public class JSpec extends Runner {
  private Description desc;
  private Context root;

  public JSpec(Class suite) {
    desc = createSuiteDescription(suite);
    root = new Context(desc);
    enterCtxt(root, reflect(suite));
  }

  @Override
  public Description getDescription() {
    return desc;
  }

  @Override
  public void run(RunNotifier notifier) {
    root.exec(notifier);
  }
  
  ......
}

在編寫用例時,使用@RunWith(JSpec.class)注解,告訴JUnit定制化了運行器的行為。

@RunWith(JSpec.class)
public class JSpecs {{
  ......
}}

在之前已討論過,JSpecrun無非就是將「以樹形組織的」Executor集合調(diào)度起來。

實現(xiàn)reflect

JUnit在運行時,首先看到了@RunWith(JSpec.class)注解,然后反射調(diào)用JSpec的構造函數(shù)。

public JSpec(Class suite) {
  desc = createSuiteDescription(suite);
  root = new Context(desc);
  enterCtxt(root, reflect(suite));
}

通過Block.reflect的工廠方法,將開始執(zhí)行測試用例集的「初始化塊」。

public interface Block {
  void apply() throws Throwable;
  
  static Block reflect(Class c) {
    return () -> {
      Constructor cons = c.getDeclaredConstructor();
      cons.setAccessible(true);
      cons.newInstance();
    };
  }
}

此刻,被@RunWith(JSpec.class)注解標注的「初始化塊」被執(zhí)行。

@RunWith(JSpec.class)
public class JSpecs {{
  ......
}}

在「初始化塊」中順序完成對describe, it, before, after等子句的調(diào)用,其中:

describe開辟新的Context

describe可以遞歸地調(diào)用內(nèi)部嵌套的describe

describe調(diào)用it, before, after時,將信息注冊到了Context中;

最終Runner.runExecutor集合按照「樹」的組織方式調(diào)度起來;

GitHub

JSpec已上傳至GitHub:https://github.com/horance-liu/jspec,代碼細節(jié)請參考源代碼。

文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/65594.html

相關文章

  • Programming DSL: JMatchers

    摘要:袁英杰回顧設計上次在軟件匠藝小組上分享了正交設計的基本理論,原則和應用,在活動線下收到了很多朋友的反饋。強迫用戶雖然的設計高度可復用性,可由用戶根據(jù)實際情況,自由拼裝組合各種算子。鳴謝正交設計的理論原則及其方法論出自前軟件大師袁英杰先生。 軟件設計是一個「守破離」的過程。 --袁英杰 回顧設計 上次在「軟件匠藝小組」上分享了「正交設計」的基本理論,原則和應用,在活動線下收到了很多朋友的...

    Yuanf 評論0 收藏0
  • 【jOOQ中文】2. jOOQ與Spring和Druid整合

    摘要:在這個例子中,我們將整合但您也可以使用其他連接池,如,,等。作為構建和執(zhí)行。 jOOQ和Spring很容易整合。 在這個例子中,我們將整合: Alibaba Druid(但您也可以使用其他連接池,如BoneCP,C3P0,DBCP等)。 Spring TX作為事物管理library。 jOOQ作為SQL構建和執(zhí)行l(wèi)ibrary。 一、準備數(shù)據(jù)庫 DROP TABLE IF EXIS...

    pingink 評論0 收藏0
  • 手把手教你從零寫一個簡單的 VUE--模板篇

    摘要:轉換成為模板函數(shù)聯(lián)系上一篇文章,其實模板函數(shù)的構造都大同小異,基本是都是通過拼接函數(shù)字符串,然后通過對象轉換成一個函數(shù),變成一個函數(shù)之后,只要傳入對應的數(shù)據(jù),函數(shù)就會返回一個模板數(shù)據(jù)渲染好的字符串。 教程目錄1.手把手教你從零寫一個簡單的 VUE2.手把手教你從零寫一個簡單的 VUE--模板篇 Hello,我又回來了,上一次的文章教會了大家如何書寫一個簡單 VUE,里面實現(xiàn)了VUE 的...

    feng409 評論0 收藏0

發(fā)表評論

0條評論

TANKING

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<