關(guān)于本文
雖然接觸 Java 已經(jīng) 8 年之久,可惜學(xué)習(xí)之初的筆記文檔沒能很好地保存下來。本文是近幾年工作學(xué)習(xí)中遇到的一些零散的知識(shí)點(diǎn),包括了 基礎(chǔ)概念、實(shí)用的編程技巧、代碼可讀性、設(shè)計(jì)模式、性能優(yōu)化(工具&編碼)、測(cè)試相關(guān)、JVM 相關(guān)、常用的工具和常見問題。本著好記性不如爛筆頭的初衷,在不斷地踩坑和爬坑的過程中,慢慢地記錄成文。期待著本文能起到拋磚引玉的作用,以看到大家的真知灼見。
基礎(chǔ)知識(shí)
注解
GuardedBy
@GuardedBy 注解可以作用于某一個(gè)屬性或者方法,約定在訪問這些被注解標(biāo)記的資源時(shí),能被同步代碼塊保護(hù)著。簡(jiǎn)單的使用案例如下:
@GuardedBy("obj")
private ConcurrentMap<String, String> map = new ConcurrentHashMap<>();
private final Object obj = new Object();
public void put(String k, String v) {
synchronized (obj) {
map.put(k, v);
}
}
/**
* If you use `error prone` tool to check this, this annotation should be `@SuppressWarnings("GuardedBy")`
* {@see https://errorprone.info/bugpattern/GuardedBy}
* {@see https://github.com/apache/incubator-druid/pull/6868#discussion_r249639199}
*/
@SuppressWarnings("FieldAccessNotGuarded")
public void remove(String k) {
map.remove(k);
}
@Override
public String toString() {
synchronized (obj) {
return "GuardedByExample{" +
"map=" + map +
'}';
}
}
Tips: Code Example from Apache Druid;另外,error-prone 工具支持對(duì)多種版本的 @GuardedBy 進(jìn)行檢查
InterfaceStability
@InterfaceStability.Stable
主版本是穩(wěn)定的,不同主版本間,可能不兼容@InterfaceStability.Evolving
不斷變化中,不同的次版本間,可能不兼容@InterfaceStability.Unstable
不對(duì)可靠性和健壯性做任何保證
InterfaceAudience
@InterfaceAudience.Public
對(duì)所有工程可用@InterfaceAudience.LimitedPrivate
僅限特定的工程,如 HBase、Zookeeper、HDFS 等(以 Hadoop 為例)@InterfaceAudience.Private
僅限于工程內(nèi)部使用
序列化
transient
給實(shí)現(xiàn)了 Serializable 接口的類中的字段,增加 transient 修飾符,則可以讓該字段跳過序列化的過程
Tips: Full code is here and here.
類中包含沒有實(shí)現(xiàn) Serializable 接口的字段
需要自己實(shí)現(xiàn) serialize 和 deserialize 方法
Tips: Full code is here and here.
實(shí)用技巧
Collection 內(nèi)元素類型轉(zhuǎn)換
// 對(duì)集合里面的元素類型進(jìn)行轉(zhuǎn)換(A -> B)
List<B> variable = (List<B>)(List<?>) collectionOfListA;
集合的差集、交集、并集
// 使用 Guava 中封裝的 Sets 類
import com.google.common.collect.Sets;
Sets.difference(set1, set2)
Sets.intersection(set1, set2)
Sets.union(set1, set2)
數(shù)組轉(zhuǎn)為 Set
// JDK8
new HashSet<>(Arrays.asList(str.trim().split(",")))
// JDK9+
Set.of(str.trim().split(","));
TransmittableThreadLocal 解決跨父子線程和線程池緩存問題
編碼
String tlMsg = "tl";
String ttlMsg = "ttl";
final ThreadLocal<String> tl = new ThreadLocal<>();
tl.set(tlMsg);
final TransmittableThreadLocal<String> ttl = new TransmittableThreadLocal<>();
ttl.set(ttlMsg);
assertEquals(tl.get(), tlMsg);
assertEquals(ttl.get(), ttlMsg);
new Thread(() -> {
assertNull(tl.get());
assertEquals(ttl.get(), ttlMsg);
}).start();
Tips: Full code is here.
參考
可讀性
魔法數(shù)字
編碼過程中,應(yīng)該避免出現(xiàn)沒有聲明含義的純數(shù)字
反面示例
// org.apache.kafka.connect.runtime.distributed.DistributedHerder#stop
@Override
public void stop() {
if (!forwardRequestExecutor.awaitTermination(10000L, TimeUnit.MILLISECONDS))
forwardRequestExecutor.shutdownNow();
}
正面示例
// 這里除了需要將 10000L 抽象成 FORWARD_REQUEST_SHUTDOWN_TIMEOUT_MS 靜態(tài)變量
// 還可以進(jìn)一步使用 10_000L 方便閱讀
// 因?yàn)檫@里和時(shí)間有關(guān),更進(jìn)一步,可以使用 TimeUnit.SECONDS.toMillis(10) 來代替純數(shù)字
private static final long FORWARD_REQUEST_SHUTDOWN_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
@Override
public void stop() {
if (!forwardRequestExecutor.awaitTermination(FORWARD_REQUEST_SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS))
forwardRequestExecutor.shutdownNow();
}
Tips: Full code is here.
字符串判空
反面示例
// org.apache.kafka.connect.runtime.distributed.DistributedHerder#reconfigureConnector
leaderUrl.equals("")
正面示例
// 一方面,需要考慮不能將對(duì)象放在 equals 方法之前,避免空指針異常
// 另一方面,使用 `str.length() == 0` 的方式,效率會(huì)高一些
// 進(jìn)一步,使用 isEmpty() 方法,則可以使得代碼更加可讀
leaderUrl == null || leaderUrl.trim().isEmpty()
Tips: Full code is here.
箭頭型代碼
反面示例
public void m(String s) {
if (s != null) {
if (s.trim().length() > 0) {
if (s.contains("yuzhouwan")) {
System.out.println("https://yuzhouwan.com");
}
}
}
}
正面示例
public void m(String s) {
if (s == null) {
return;
}
if (s.trim().length() == 0) {
return;
}
if (!s.contains("yuzhouwan")) {
return;
}
System.out.println("https://yuzhouwan.com");
}
參考
函數(shù)式編程
Optional
反面示例
public Long deserialize(ByteArrayDataInput in) {
return isNullByteSet(in) ? null : in.readLong();
}
正面示例
return Optional.ofNullable(in)
.filter(InputRowSerde::isNotNullByteSet)
.map(ByteArrayDataInput::readLong)
.get();
參考
- Write null byte when indexing numeric dimensions with Hadoop #7020
anyMatch
反面示例
private boolean isTaskPending(Task task) {
for (TaskRunnerWorkItem workItem : taskRunner.getPendingTasks()) {
if (workItem.getTaskId().equals(task.getId())) {
return true;
}
}
return false;
}
正面示例
final String taskId = task.getId();
return taskRunner.getPendingTasks()
.stream()
.anyMatch(t -> taskId.equals(t.getTaskId()));
參考
- Run pending tasks when assigned a task that is already pending #6991
設(shè)計(jì)模式
里氏替換原則
描述
里氏替換原則(Liskov substitution principle,LSP)強(qiáng)調(diào)的是 面向?qū)ο蟪绦蛟O(shè)計(jì)中的 可替代性,說明在計(jì)算機(jī)程序中,如果 S 是 T 的子類型,那么類型 T 的對(duì)象可以用類型 S 的對(duì)象替換(即 T 類型的對(duì)象可以被任何子類型 S 的對(duì)象替換),而不改變程序的任何期望屬性(正確地執(zhí)行的任務(wù)等)
參考
性能優(yōu)化
工具層面
性能指標(biāo)監(jiān)控
<metrics.version>3.2.0</metrics.version>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
<version>${metrics.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
JMH 基準(zhǔn)測(cè)試
增加 Maven 依賴
<jmh.version>1.19</jmh.version>
<!-- JMH -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
</dependency>
編寫 JMH 測(cè)試案例
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
public class BenchmarkSimple {
@Benchmark
public void bench() {
add(1, 1);
}
private static int add(int a, int b) {
return a + b;
}
/*
Benchmark Mode Cnt Score Error Units
BenchmarkSimple.bench thrpt 5 13352311.603 ± 767137.272 ops/ms
*/
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(BenchmarkSimple.class.getSimpleName())
.forks(1)
.warmupIterations(5)
.measurementIterations(5)
.threads(10)
.build();
new Runner(opt).run();
}
}
Tips: Full code is here.
優(yōu)勢(shì)
- 不和會(huì) JUnit 沖突,不用擔(dān)心 Jenkins 會(huì)自動(dòng)跑 Benchmark 測(cè)試而影響效率(否則需要添加
@Ignore讓 CI 系統(tǒng)忽略掉性能相關(guān)的 JUnit 測(cè)試用例) - 支持
warm up,可以解決 JIT 預(yù)熱問題
BTrace
介紹
BTrace is a safe, dynamic tracing tool for the Java platform. BTrace can be used to dynamically trace a running Java program (similar to DTrace for OpenSolaris applications and OS). BTrace dynamically instruments the classes of the target application to inject tracing code ("bytecode tracing").
參考
LooseJar
介紹
分析沒有被加載任何 class 的 jar 包,幫助刪除工程中不必要的 jar 包。不過,需要注意的是,有些類是動(dòng)態(tài)加載的(比如數(shù)據(jù)類型轉(zhuǎn)換類的,只有加載數(shù)據(jù)時(shí)才會(huì)用到),需要盡可能地多測(cè)試,才能保證 LooseJar 分析準(zhǔn)確
使用步驟
下載
在 LooseJar 的 release 頁面,下載 loosejar-1.1.0.jar拷貝
將 loosejar.jar 放到應(yīng)用的 WEB-INF 下的 lib 目錄中,比如說路徑是/yuzhouwan/yuzhouwan-site/yuzhouwan-site-web/src/main/webapp/WEB-INF/lib/loosejar.jar配置
在 IDE 中的 installed JRES 里面的 JDK 處配置-Dfile.encoding=uft8 -javaagent:/yuzhouwan/yuzhouwan-site/yuzhouwan-site-web/src/main/webapp/WEB-INF/lib/loosejar.jar啟動(dòng)
啟動(dòng)應(yīng)用,盡可能做到路徑全覆蓋地測(cè)試應(yīng)用,讓每段代碼都被執(zhí)行到查看結(jié)果
運(yùn)行 JDK 自帶的 Jconsole 工具,選擇 BootStrap 的那個(gè)端口,然后選擇 MBean 下的com.googlecode.loosejar,并點(diǎn)擊 summary,即可看到分析結(jié)果
編碼層面
并發(fā)相關(guān)
synchronized
詳見,《如何運(yùn)用 JVM 知識(shí)提高編程水平 - synchronized 的性能之爭(zhēng)》
StampedLock
特性
該類是一個(gè)讀寫鎖的改進(jìn),它的思想是讀寫鎖中讀不僅不阻塞讀,同時(shí)也不應(yīng)該阻塞寫
參考
集合優(yōu)化
HashMap
為什么 Java 8 版本中引入紅黑樹
-
原因
JDK8 以前 HashMap 的實(shí)現(xiàn)是 數(shù)組+鏈表,即使哈希函數(shù)取得再好,也很難達(dá)到元素百分百均勻分布
當(dāng) HashMap 中有大量的元素都存放到同一個(gè)桶中時(shí),這個(gè)桶下有一條長(zhǎng)長(zhǎng)的鏈表,這個(gè)時(shí)候 HashMap 就相當(dāng)于一個(gè)單鏈表,假如單鏈表有 n 個(gè)元素,遍歷的時(shí)間復(fù)雜度就是
,完全失去了它的優(yōu)勢(shì)
針對(duì)這種情況,JDK8 中引入了 紅黑樹(查找時(shí)間復(fù)雜度為
)來優(yōu)化這個(gè)問題
-
流程
添加時(shí),當(dāng)桶中鏈表個(gè)數(shù)超過 8 時(shí)會(huì)轉(zhuǎn)換成紅黑樹
刪除、擴(kuò)容時(shí),如果桶中結(jié)構(gòu)為紅黑樹,并且樹中元素個(gè)數(shù)太少的話,會(huì)進(jìn)行修剪或者直接還原成鏈表結(jié)構(gòu)
查找時(shí)即使哈希函數(shù)設(shè)計(jì)不合理,大量元素集中在一個(gè)桶中,由于有紅黑樹結(jié)構(gòu),性能也不會(huì)差
Collections 類
空集合
Collections.emptyList() 重用一個(gè)對(duì)象而不是創(chuàng)建一個(gè)新對(duì)象,就像 Arrays.asList() 一樣。不同的是,Collections.singletonList(something) 是不可變的,而 Arrays.asList(something) 是一個(gè)固定大小的 List,其中 List 和 Array 在 Heap 中已經(jīng)連接。
參考
- Empty list: What is the difference between Arrays.asList() and Collections.emptyList()?
Arrays.asList()vsCollections.singletonList()
LinkedList vs. ArrayList
LinkedList
-
get(int index)isaverage
-
add(E element)is -
add(int index, E element)isaverage, but
when
index = 0 -
remove(int index)isaverage
-
Iterator.remove()is -
ListIterator.add(E element)is
Note: is average,
best case (e.g. index = 0),
worst case (middle of list)
ArrayList
-
get(int index)is -
add(E element)isamortized, but
worst-case since the array must be resized and copied
-
add(int index, E element)isaverage
-
remove(int index)isaverage
-
Iterator.remove()isaverage
-
ListIterator.add(E element)isaverage
參考
contains 方法
HashSet
時(shí)間復(fù)雜度 和 內(nèi)存使用率 角度看,HashSet 為最佳之選
參考
toArray 方法
說明
There are two styles to convert a collection to an array: either using a pre-sized array (like c.toArray(new String[c.size()])) or using an empty array (like c.toArray(new String[0]).
In older Java versions using pre-sized array was recommended, as the reflection call which is necessary to create an array of proper size was quite slow. However since late updates of OpenJDK 6 this call was intrinsified, making the performance of the empty array version the same and sometimes even better, compared to the pre-sized version. Also passing pre-sized array is dangerous for a concurrent or synchronized collection as a data race is possible between the size and toArray call which may result in extra nulls at the end of the array, if the collection was concurrently shrunk during the operation.
This inspection allows to follow the uniform style: either using an empty array (which is recommended in modern Java) or using a pre-sized array (which might be faster in older Java versions or non-HotSpot based JVMs).
壓測(cè)
@State(Scope.Thread)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public class ToArrayBenchmark {
@Param({"1", "100", "1000", "5000", "10000", "100000"})
private int n;
private final List<Object> list = new ArrayList<>();
@Setup
public void populateList() {
for (int i = 0; i < n; i++) {
list.add(0);
}
}
@Benchmark
public Object[] preSize() {
return list.toArray(new Object[n]);
}
@Benchmark
public Object[] resize() {
return list.toArray(new Object[0]);
}
/*
Integer List:
Benchmark (n) Mode Cnt Score Error Units
ToArrayBenchmark.preSize 1 avgt 3 41.552 ± 108.030 ns/op
ToArrayBenchmark.preSize 100 avgt 3 216.449 ± 799.501 ns/op
ToArrayBenchmark.preSize 1000 avgt 3 2087.965 ± 6027.778 ns/op
ToArrayBenchmark.preSize 5000 avgt 3 9098.358 ± 14603.493 ns/op
ToArrayBenchmark.preSize 10000 avgt 3 24204.199 ± 121468.232 ns/op
ToArrayBenchmark.preSize 100000 avgt 3 188183.618 ± 369455.090 ns/op
ToArrayBenchmark.resize 1 avgt 3 18.987 ± 36.449 ns/op
ToArrayBenchmark.resize 100 avgt 3 265.549 ± 1125.008 ns/op
ToArrayBenchmark.resize 1000 avgt 3 1560.713 ± 2922.186 ns/op
ToArrayBenchmark.resize 5000 avgt 3 7804.810 ± 8333.390 ns/op
ToArrayBenchmark.resize 10000 avgt 3 24791.026 ± 78459.936 ns/op
ToArrayBenchmark.resize 100000 avgt 3 158891.642 ± 56055.895 ns/op
Object List:
Benchmark (n) Mode Cnt Score Error Units
ToArrayBenchmark.preSize 1 avgt 3 36.306 ± 96.612 ns/op
ToArrayBenchmark.preSize 100 avgt 3 52.372 ± 84.159 ns/op
ToArrayBenchmark.preSize 1000 avgt 3 449.807 ± 215.692 ns/op
ToArrayBenchmark.preSize 5000 avgt 3 2080.172 ± 2003.726 ns/op
ToArrayBenchmark.preSize 10000 avgt 3 4657.937 ± 8432.624 ns/op
ToArrayBenchmark.preSize 100000 avgt 3 51980.829 ± 46920.314 ns/op
ToArrayBenchmark.resize 1 avgt 3 16.747 ± 85.131 ns/op
ToArrayBenchmark.resize 100 avgt 3 43.803 ± 28.704 ns/op
ToArrayBenchmark.resize 1000 avgt 3 404.681 ± 132.986 ns/op
ToArrayBenchmark.resize 5000 avgt 3 1972.649 ± 174.691 ns/op
ToArrayBenchmark.resize 10000 avgt 3 4021.440 ± 1114.212 ns/op
ToArrayBenchmark.resize 100000 avgt 3 44204.167 ± 76714.850 ns/op
*/
public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder()
.include(ToArrayBenchmark.class.getSimpleName())
.forks(1)
.warmupIterations(1)
.measurementIterations(3)
.threads(1)
.build();
new Runner(opt).run();
}
}
Tips: Full code is here.
instanceof
| Operation | Runtime in nanoseconds per operation | Relative to instanceof |
|---|---|---|
| INSTANCEOF | 39,598 ± 0,022 ns/op | 100,00 % |
| GETCLASS | 39,687 ± 0,021 ns/op | 100,22 % |
| TYPE | 46,295 ± 0,026 ns/op | 116,91 % |
| OO | 48,078 ± 0,026 ns/op | 121,42 % |
參考
字符串相關(guān)
repeat
借鑒 JDK11 中新增的 String#repeat 特性,實(shí)現(xiàn)高效的 repeat 工具方法
import java.nio.charset.StandardCharsets;
/**
* Returns a string whose value is the concatenation of the
* string {@code s} repeated {@code count} times.
* <p>
* If count or length is zero then the empty string is returned.
* <p>
* This method may be used to create space padding for
* formatting text or zero padding for formatting numbers.
*
* @param count number of times to repeat
* @return A string composed of this string repeated
* {@code count} times or the empty string if count
* or length is zero.
* @throws IllegalArgumentException if the {@code count} is negative.
* @link https://bugs.openjdk.java.net/browse/JDK-8197594
*/
public static String repeat(String s, int count) {
if (count < 0) {
throw new IllegalArgumentException("count is negative, " + count);
}
if (count == 1) {
return s;
}
byte[] value = s.getBytes(StandardCharsets.UTF_8);
final int len = value.length;
if (len == 0 || count == 0) {
return "";
}
if (len == 1) {
final byte[] single = new byte[count];
Arrays.fill(single, value[0]);
return new String(single, StandardCharsets.UTF_8);
}
if (Integer.MAX_VALUE / count < len) {
throw new OutOfMemoryError();
}
final int limit = len * count;
final byte[] multiple = new byte[limit];
System.arraycopy(value, 0, multiple, 0, len);
int copied = len;
for (; copied < limit - copied; copied <<= 1) {
System.arraycopy(multiple, 0, multiple, copied, copied);
}
System.arraycopy(multiple, 0, multiple, copied, limit - copied);
return new String(multiple, StandardCharsets.UTF_8);
}
參考
Fork / Join 思想
這里以查找最小數(shù)為例,具體實(shí)現(xiàn)如下:
import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
/**
* Copyright @ 2019 yuzhouwan.com
* All right reserved.
* Function:Minimum Finder
*
* @author Benedict Jin
* @since 2019/4/13
*/
public class MinimumFinder extends RecursiveTask<Integer> {
private static final int JOIN_THRESHOLD = 5;
private final int[] data;
private final int start;
private final int end;
private MinimumFinder(int[] data, int start, int end) {
this.data = data;
this.start = start;
this.end = end;
}
private MinimumFinder(int[] data) {
this(data, 0, data.length);
}
@Override
protected Integer compute() {
final int len = end - start;
if (len < JOIN_THRESHOLD) {
return internal();
}
final int split = len / 2;
final MinimumFinder left = new MinimumFinder(data, start, start + split);
left.fork();
final MinimumFinder right = new MinimumFinder(data, start + split, end);
return Math.min(right.compute(), left.join());
}
private Integer internal() {
System.out.println(Thread.currentThread() + " computing: " + start + " to " + end);
int min = Integer.MAX_VALUE;
for (int i = start; i < end; i++) {
if (data[i] < min) {
min = data[i];
}
}
return min;
}
public static void main(String[] args) {
final int[] data = new int[1000];
final Random random = new Random(System.nanoTime());
for (int i = 0; i < data.length; i++) {
data[i] = random.nextInt(100);
}
final ForkJoinPool pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());
final MinimumFinder finder = new MinimumFinder(data);
System.out.println(pool.invoke(finder));
}
}
參考
- Java Fork Join 框架
- Move Caching Cluster Client to java streams and allow parallel intermediate merges #5913
堆棧優(yōu)化
ByteBuffer
通過 allocateDirect(int capacity) 方法可以避開堆棧,直接通過操作系統(tǒng)創(chuàng)建內(nèi)存塊作為緩沖區(qū)。該方式與操作系統(tǒng)能更好地耦合,因而能進(jìn)一步提高 I/O 操作的速度。缺點(diǎn)是,分配直接緩沖區(qū)的系統(tǒng)開銷很大。因此,只有在緩沖區(qū)較大并會(huì)長(zhǎng)期存在,或者需要經(jīng)常重用時(shí),才使用這種緩沖區(qū)
位運(yùn)算
奇偶數(shù)
public static boolean isEven(int i) {
return (i & 1) == 0;
}
毫秒
public static final long SECOND_MASK = 0xFFFFFFFF00000000L;
public static boolean isMillis(long timestamp) {
return (timestamp & SECOND_MASK) != 0;
}
參考
- 在寫代碼的過程中使用位運(yùn)算的好處?
- 只用位運(yùn)算實(shí)現(xiàn)比較兩整數(shù)大小,有沒有簡(jiǎn)短優(yōu)雅的
O(1)的解法? - Bytes.java
- ByteBuffer 常用方法詳解
- 優(yōu)秀程序員不得不知道的 20 個(gè)位運(yùn)算技巧
- Java 移位操作符你真的懂嗎?
日志相關(guān)
log4j 開啟 BufferedIO
參考
測(cè)試相關(guān)
參數(shù)驅(qū)動(dòng)
利用 @Parameterized.Parameters 注解可以指定多個(gè)可能的傳值,使得當(dāng)前測(cè)試類下的所有測(cè)試用例可以被多次復(fù)用。但是該注解并不能讓參數(shù)之間自行組合,所以嚴(yán)格來說,并不是參數(shù)驅(qū)動(dòng)(后續(xù)介紹的 HttpRunner 框架則是嚴(yán)格意義上的參數(shù)驅(qū)動(dòng))
import com.yuzhouwan.compression.CompressionType;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.junit.runners.Parameterized;
import java.util.Arrays;
@FixMethodOrder(MethodSorters.JVM)
@RunWith(Parameterized.class)
public class CompressionTest {
@Parameterized.Parameter()
public CompressionType compressionType4ts;
@Parameterized.Parameter(1)
public CompressionType compressionType4longValue;
@Parameterized.Parameter(2)
public CompressionType compressionType4doubleValue;
@Parameterized.Parameters
public static Iterable<Object[]> getParameters() {
return Arrays.asList(new Object[][]{
{CompressionType.NONE, CompressionType.NONE, CompressionType.NONE},
{CompressionType.SIMPLE8B, CompressionType.NONE, CompressionType.GORILLA},
{CompressionType.SIMPLE8B_WITH_RLE, CompressionType.ZIGZAG_WITH_SIMPLE8B, CompressionType.NONE},
});
}
/**
* NONE - NONE - NONE
* SIMPLE8B - NONE - GORILLA
* SIMPLE8B_WITH_RLE - ZIGZAG_WITH_SIMPLE8B - NONE
*/
@Test
public void test() {
System.out.println(compressionType4ts + " - " + compressionType4longValue + " - " + compressionType4doubleValue);
}
}
測(cè)試先行
參考
自動(dòng)生成 Test Case
參考
自動(dòng)化測(cè)試
HttpRunner
介紹
HttpRunner? 是一款面向 HTTP(S) 協(xié)議的通用測(cè)試框架,只需編寫維護(hù)一份 YAML/JSON 腳本,即可實(shí)現(xiàn)自動(dòng)化測(cè)試、性能測(cè)試、線上監(jiān)控、持續(xù)集成等多種測(cè)試需求。這里將以測(cè)試 OpenTSDB 為例,更加具象地介紹 HttpRunner
QuickStart
安裝
$ pip install httprunner==1.5.15
$ hrun -V
1.5.15
$ har2case -V
0.2.0
啟動(dòng) Flask
$ pip install flask
$ mkdir docs/data/
$ wget https://cn.httprunner.org/data/api_server.py -P docs/data/
$ export FLASK_APP=docs/data/api_server.py
$ export FLASK_ENV=development
$ flask run
$ curl localhost:5000
Hello World!
測(cè)試
$ wget https://cn.httprunner.org/data/demo-quickstart.har -P docs/data/
# 將 demo-quickstart.har 轉(zhuǎn)換為 HttpRunner 的測(cè)試用例文件
# 默認(rèn)輸出 JSON 文件,加 `-2y` 參數(shù),可以轉(zhuǎn)化為 YAML
$ har2case docs/data/demo-quickstart.har
$ hrun docs/data/demo-quickstart.json
新建測(cè)試項(xiàng)目
# 新建目錄
$ httprunner --startproject yuzhouwan
$ ls -sail
12891420763 4 -rw-r--r-- 1 benedictjin wheel 44 Feb 2 11:37 .env
12891384628 0 drwxr-xr-x 2 benedictjin wheel 64 Feb 1 16:44 api/
12891454305 4 -rw-r--r-- 1 benedictjin wheel 2389 Feb 2 14:34 debugtalk.py
12891454901 4 -rw-r--r-- 1 benedictjin wheel 1452 Feb 2 15:21 locustfile.py
12891454386 0 drwxr-xr-x 8 benedictjin wheel 256 Feb 2 15:30 reports/
12891384629 0 drwxr-xr-x 7 benedictjin wheel 224 Feb 2 14:47 testcases/
12891384630 0 drwxr-xr-x 2 benedictjin wheel 64 Feb 1 16:44 testsuites/
# .env 存放環(huán)境變量的 properties 文件
# testcases 存放所有 httprunner 的 json 測(cè)試實(shí)例
# debugtalk.py 存放所有 httprunner 測(cè)試實(shí)例中,需要用到自定義函數(shù)
# reports 生成的 html 結(jié)果頁面
- 本文作者: Benedict Jin
- 本文鏈接: https://yuzhouwan.com/posts/190413/
- 版權(quán)聲明: 本博客所有文章除特別聲明外,均采用 BY-NC-ND 許可協(xié)議。轉(zhuǎn)載請(qǐng)注明出處!