public void producerConsumer() { BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>(); Thread producerThread = new Thread(() -> { for (int i = 0; i < 10; i++) { blockingQueue.add(i + ThreadLocalRandom.current().nextInt(100)); } }); Thread consumerThread = new Thread(() -> { try { while (true) { Integer result = blockingQueue.take(); System.out.println(result); } } catch (InterruptedException ignore) { } }); producerThread.start(); consumerThread.start(); }
高阶函数
public int f() {return ThreadLocalRandom.current().nextInt(100) + 1;}
public Supplier<Integer> g(Supplier<Integer> integerSupplier) {return () -> integerSupplier.get() + 1;}
public void testG() {Supplier<Integer> result = g(() -> 1);assert result.get() == 2;}
public int g2(Supplier<Integer> integerSupplier) {return integerSupplier.get() + 1;}
因为这个例子比较简单,“可测试” 带来的收益看起来没有那么高,真实业务中的逻辑一般比 +1 要复杂多了,此时如果能构建有效的测试将是非常有益的。
第一轮重构
public <T> void producerConsumerInner(Consumer<Consumer<T>> producer,Consumer<Supplier<T>> consumer) {BlockingQueue<T> blockingQueue = new LinkedBlockingQueue<>();new Thread(() -> producer.accept(blockingQueue::add)).start();new Thread(() -> consumer.accept(() -> {try {return blockingQueue.take();} catch (InterruptedException e) {throw new RuntimeException(e);}})).start();}
public <T> void producerConsumerInner(Executor executor,Consumer<Consumer<T>> producer,Consumer<Supplier<T>> consumer) {BlockingQueue<T> blockingQueue = new LinkedBlockingQueue<>();executor.execute(() -> producer.accept(blockingQueue::add));executor.execute(() -> consumer.accept(() -> {try {return blockingQueue.take();} catch (InterruptedException e) {throw new RuntimeException(e);}}));}
private void testProducerConsumerInner() {producerConsumerInner(Runnable::run,(Consumer<Consumer<Integer>>) producer -> {producer.accept(1);producer.accept(2);},consumer -> {assert consumer.get() == 1;assert consumer.get() == 2;});}
public abstract class ProducerConsumer<T> {
private final Executor executor;
private final BlockingQueue<T> blockingQueue;
public ProducerConsumer(Executor executor) { this.executor = executor; this.blockingQueue = new LinkedBlockingQueue<>(); } public void start() { executor.execute(this::produce); executor.execute(this::consume); }
abstract void produce();
abstract void consume();
protected void produceInner(T item) { blockingQueue.add(item); }
protected T consumeInner() { try { return blockingQueue.take(); } catch (InterruptedException e) { throw new RuntimeException(e); } }}
private void testProducerConsumerAbCls() {new ProducerConsumer<Integer>(Runnable::run) {void produce() {produceInner(1);produceInner(2);}void consume() {assert consumeInner() == 1;assert consumeInner() == 2;}}.start();}
很显然这种测试无法验证多线程运行的情况,但我故意这么做的,这部分单元测试的主要目的是验证逻辑的正确性,只有先验证逻辑上的正确性,再去测试并发才比较有意义,在逻辑存在问题的情况下就去测试并发,只会让问题隐藏得更深,难以排查。一般开源项目中会有专门的单元测试去测试并发,但是因为其编写代价比较大,运行时间比较长,数量会远少于逻辑测试。
public void producerConsumer() { new ProducerConsumer<Integer>(Executors.newFixedThreadPool(2)) { void produce() { for (int i = 0; i < 10; i++) { produceInner(i + ThreadLocalRandom.current().nextInt(100)); } }
void consume() { while (true) { Integer result = consumeInner(); System.out.println(result); } } }.start(); }
第二轮重构
public class NumberProducerConsumer extends ProducerConsumer<Integer> {
private final Supplier<Integer> numberGenerator;
private final Consumer<Integer> numberConsumer;
public NumberProducerConsumer(Executor executor, Supplier<Integer> numberGenerator, Consumer<Integer> numberConsumer) { super(executor); this.numberGenerator = numberGenerator; this.numberConsumer = numberConsumer; }
void produce() { for (int i = 0; i < 10; i++) { produceInner(i + numberGenerator.get()); } }
void consume() { while (true) { Integer result = consumeInner(); numberConsumer.accept(result); } }}
private void testProducerConsumerInner2() {AtomicInteger expectI = new AtomicInteger();producerConsumerInner2(Runnable::run, () -> 0, i -> {assert i == expectI.getAndIncrement();});assert expectI.get() == 10;}
public void producerConsumer() {new NumberProducerConsumer(Executors.newFixedThreadPool(2),() -> ThreadLocalRandom.current().nextInt(100),System.out::println).start();}
重构:将代码重构得更加优雅
确定测试边界
不断迭代,扩大测试边界到理想状态
public byte[] export(FormConfig config, DataService dataService, ExportStatusStore statusStore) { //... 省略具体逻辑, 其中包括所有可测试的逻辑, 包括表单数据转换,excel 生成}
public interface DataService {
PageList<FormData> batchGet(String formId, Long cursor, int pageSize);
}
public interface ExportStatusStore {/*** 将状态切换为 RUNNING*/void runningStatus();/*** 将状态置为 finish* @param fileId 文件 id*/void finishStatus(Long fileId);/*** 将状态置为 error* @param errMsg 错误信息*/void errorStatus(String errMsg);}
public void testExport() {// 这里的 export 就是刚刚展示的导出测试边界byte[] excelBytes = export(new FormConfig(), new LocalDataService(),new LocalStatusStore());assertExcelContent(excelBytes, Arrays.asList(Arrays.asList("序号", "表格", "表格", "表格", "创建时间", "创建者"),Arrays.asList("序号", "物品编号", "物品名称", "xxx", "创建时间", "创建者"),Arrays.asList("1", "22", "火车", "而非", "2020-10-11 00:00:00", "悬衡")));}
技术人如何打造个人品牌影响力?五大顶级创作者独家传授!
开发者社区联合InfoQ特别策划《技术创作训练营》,2位金牌导师+5位顶级作者独家传授写作方法论,带您开启高质量技术创作之路。
点击阅读原文查看详情。