0%

记录一些 Java 反射基础知识

准备测试 Bean

1
2
3
4
5
6
7
8
9
package reflectiontest.bean;

public class TestUser {
private String name;
private int age;
public String gender;

// Getter and Setter
}

getFields VS getDeclaredFields

getFields 只会返回 public 类型的 fields, getDeclaredFields 会返回所有类型的 fieds

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void get_class_field() {
Field[] fields = TestUser.class.getFields();
System.out.println("Output of getFields...");
for (Field f : fields) {
System.out.println(f);
}
System.out.println("\n");

Field[] declareFields = TestUser.class.getDeclaredFields();
System.out.println("Output of getDeclaredFields...");
for (Field f : declareFields) {
System.out.println(f);
}
}

// Output of getFields...
// public java.lang.String reflectiontest.bean.TestUser.gender


// Output of getDeclaredFields...
// private java.lang.String reflectiontest.bean.TestUser.name
// private int reflectiontest.bean.TestUser.age
// public java.lang.String reflectiontest.bean.TestUser.gender

poetry 推荐使用 pyenv 进行本地 python 的多版本管理,以前用过,但是也没什么特别的印象了,特此记录一下使用情况

安装

Win 平台不支持这个工具,残念。。。

通过 brew 安装, brew 加速的教程在另一篇教程里有提到

1
2
brew update
brew install pyenv

在 profile 中添加配置使能,我本地用的 zsh, 各版本的 shell 稍有区别,指定的文件不一样

1
echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n  eval "$(pyenv init -)"\nfi' >> ~/.zshrc

安装推荐的工具机,各种系统不一样

For MacOS, install Xcode Command Line Tools (xcode-select –install) and HomeBrew, then optional but best install

1
brew install openssl readline sqlite3 xz zlib

在系统中可以通过输入 echo $(pyenv root) 拿到目录地址

常用命令

直接输入 pyenv 查看所有的 cmd 信息

安装某个版本的 python

1
pyenv install 3.7.8

如果没打全,他会给提示可用的版本,很人性化。安装的 python 版本会被放到 ~/.pyenv/versions/ 管理

删除对应版本

pyenv uninstall 3.7.8 或直接去 versions 文件夹下删除

显示可用版本

pyenv versions

1
2
3
4
pyenv versions
* system (set by /Users/jack/.python-version)
3.6.5
3.7.8

切换版本

多用 pyenv version 查看当前的环境版本信息

使用前的情况:系统自带 python 版本 2.7.16, pyenv 可用版本 3.6.5 和 3.7.8。此时 cmd 输入 python -V 给出版本 2.7.16

全局切换版本 pyenv gloabl 3.7.8,他会将这个版本存放到 .pyenv/version 文件中,再打开终端查看版本,变为 3.7.8

pyenv local 3.6.5 可以指定 folder 下的 python 版本,他会将版本信息写入当前目录下的 .python-version 文件中

如果想要指定终端的 python 版本,可以用 pyenv shell xxx, 这个我没有需求,未亲测

作用范围和其编程语言一样,范围最精确的那个生效 shell > local > gloabl

查看 python 路径

pyenv which python

更新

每次新安装版本,记得跑一下 pyenv rehash 更新信息

Issues

pyenv install 下载失败, 报错

1
2
3
4
5
6
7
8
 Jack > ~ > pyenv install 3.7.3
python-build: use openssl@1.1 from homebrew
python-build: use readline from homebrew
Downloading Python-3.7.3.tar.xz...
-> https://www.python.org/ftp/python/3.7.3/Python-3.7.3.tar.xz
error: failed to download Python-3.7.3.tar.xz

BUILD FAILED (OS X 10.15.6 using python-build 20180424)

可以自行下载对应的 tar.xz 文件然后放到 pyenv 的 cache 文件夹下,pyenv install 的时候会取对应的安装包进行安装

1
wget -P $(pyenv root)/cache https://www.python.org/ftp/python/3.7.3/Python-3.7.3.tar.xz

Refer

公司日常维护过程中,同事自己写的一个 Chrome 小插件很精巧,符合我小而美的审美,很适合处理某些需求,特此记录一下 Chrome 插件的小知识和一个阅读源码的收获

插件目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Root
├── README.md
├── background.js <- 定义一些 js 脚本
├── content.js
├── doc
│ └── images
│ ├── extend_all.png
│ ├── extend_status.png
│ ├── extension_icon.png
│ ├── extension_loaded.png
│ └── load_unpacked_extension.png
├── icon.png <- icon 定义
├── images
│ ├── icon128.png
│ ├── icon16.png
│ └── icon48.png
├── jquery-3.0.0.min.js
├── manifest.json <- 定义了 extension 的基本信息,权限等,可以概览整个应用
├── options.html <- 为客户提供可选项
├── options.js
├── popup.html <- 点击弹出页面,用于交互
├── popup.js
├── style.css
└── test.js

JS 的一些知识点

  • .aspx 页面,是基于微软 .Net 开发的站点
  • html 页面中可以直接在 onclick 里面写 logic,简直是随心所欲
  • 通过 ajax 可以实现表单提交
1
2
3
4
5
6
7
<!-- click 中设置 confirm 内容 -->
<!DOCTYPE html>
<html>
<body>
<input type="button" name="ctl00$ContentPlaceHolder$GridViewLive$ctl02$Deletion" value="Delete" onclick="if (!confirm(&#39;Are you sure you want to delete the company?&#39;)) return false; console.log('Click Confirmed')" />
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$.ajax({
type: "POST",
url: url,
data: data,
success: success,
dataType: dataType
});

// 简写形式
$.post( "ajax/test.html", function( data ) {
$( ".result" ).html( data );
});

// form.serialize() 可以方便的实现数据提取
$.post( "test.php", $( "#testform" ).serialize() );

// 如果想要成功提示,还可以
$.post(url, $("#ctl00").serialize()).done(
function( data ) {
alert( "extends success" );
}
);

调试脚本

由于这次只是查看代码,而且验证一些函数的功能,调试还是挺顺利的,直接通过 Chrome console 就完成了,各种变量自动装载完成,美滋滋儿。

Poetry 类 pipenv 工具,据说 lock 什么的速度更快,而且有集成发布功能,刚好 rich 这个项目有用这个,刚好在看源码的时候体验一把

安装

Win

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# powershell 输入
(Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python

# 提示 error, 原因是 DNS 污染
Invoke-WebRequest : 未能解析此远程名称: 'raw.githubusercontent.com'
所在位置 行:1 字符: 2
+ (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poet ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest],WebExce
ption
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

# 解决方案:修改 host 文件
# 目录:C:/Windows/System32/drivers/etc/
# 管理员模式打开,添加文本: 151.101.0.133 raw.githubusercontent.com
# 刷新DNS
ipconfig /flushdns

# 链接成功,但是报其他错误
Invoke-WebRequest : 基础连接已经关闭: 发送时发生错误。
所在位置 行:1 字符: 2
+ (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poet ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest],WebExce
ption
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

# 改完之后各种报错,烦躁。这个命令就是下载一个 get-poetry.py 的 raw 文件,然后使用 python get-poetry.py 安装。我直接下载这个文件然后安装了。。。

# 尼玛,被墙了安装超级慢 (╬▔皿▔)╯ 最后用小飞机开启全局代理, 再 CMD 窗口 python get-poetry.py 安装成功

Retrieving Poetry metadata

Before we start, please answer the following questions.
You may simply press the Enter key to leave unchanged.
Modify PATH variable? ([y]/n)

# Welcome to Poetry!
This will download and install the latest version of Poetry,
a dependency and package manager for Python.
It will add the `poetry` command to Poetry's bin directory, located at:
%USERPROFILE%\.poetry\bin
This path will then be added to your `PATH` environment variable by
modifying the `HKEY_CURRENT_USER/Environment/PATH` registry key.
You can uninstall at any time by executing this script with the --uninstall option,
and these changes will be reverted.

Installing version: 1.0.10
- Downloading poetry-1.0.10-win32.tar.gz (11.96MB)

Poetry (1.0.10) is installed now. Great!
To get started you need Poetry's bin directory (%USERPROFILE%\.poetry\bin) in your `PATH`
environment variable. Future applications will automatically have the
correct environment, but you may need to restart your current shell.

# 重启一下终端,输入命令检测安装
poetry --version

# 但是在 vscode 的终端中还是不能识别,手动将 user\.poetry\bin 添加到系统 path 中重启 vscode, 识别成功

PS: 国内安装各种软件有助于增长火气!!!

常用 Command

poetry new project-name

初始化项目, 创建必要文件。你可以在 git 上先建一个空的仓库然后,本地做完 poetry init 和 git init 之后 match 一下

初始化后目录为

1
2
3
4
5
6
7
8
job-spider
├── pyproject.toml
├── README.rst
├── job_spider
│ └── __init__.py
└── tests
├── __init__.py
└── test_job_spider.py

通过配置 toml 文件指定国内源加速

1
2
3
[[tool.poetry.source]]
name = "douban"
url = "https://pypi.doubanio.com/simple/"

poetry config –list

查看配置,比如 virtualenv 会创建在哪里之类的。这个 cmd 还是很有帮助的,可以通过它知道你的虚拟环境创建在哪里,是不是要在 project 创建 venv 等

1
2
3
4
5
$ poetry config --list
cache-dir = "/Users/jack/Library/Caches/pypoetry"
virtualenvs.create = true
virtualenvs.in-project = false
virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/jack/Library/Caches/pypoetry/virtualenvs

通过指定 poetry config virtualenvs.in-project true 可以指定将虚拟环境创建到 project 目录下面,方便管理

poetry shell

激活环境, 如果还没有创建过虚拟环境,他还会根据 toml 文件新建一个

poetry install

并不是安装依赖,而是根据 toml 文件安装项目依赖,对标 pipenv sync

poetry add

对标 pipenv 中的 pipenv install, 使用 add --dev/-D flask 安装 dev 相关的包

poetry env info

poetry env info: 显示运行环境信息,包括本地 OS 和虚拟环境

1
2
3
4
5
6
7
8
9
10
Virtualenv
Python: 3.7.5
Implementation: CPython
Path: /Users/jack/gitStore/mycommands/.venv
Valid: True

System
Platform: darwin
OS: posix
Python: /Library/Frameworks/Python.framework/Versions/3.7

poetry env list: 显示可用的 env 列表

官方推荐 poetry 结合 pyenv 管理各种版本的虚拟环境

poetry show

显示已安装的依赖

1
2
3
4
5
6
poetry show
atomicwrites 1.4.0 Atomic file writes.
attrs 19.3.0 Classes Without Boilerplate
click 7.1.2 Composable command line interface toolkit
flask 1.1.2 A simple framework for building complex web applications.
...

遇到的问题

Resolving dependency 挺慢

在安装更新的时候 resolving dependency 挺慢的,等了好一会儿,一度认为进程死了。但是第二次就快多了,可能是有 cache

1
2
3
C:\Users\jack\gitStore\job-spider\job_spider>poetry install --verbose
Updating dependencies
Resolving dependencies...

编译器识别有问题

观察 VSCode 的左下角,python 编译器经常选择有问题,会找不到自己创建的虚拟环境路径。可以点击它,然后根据 poetry shell 的提示手动设置,路径如 C:\Users\jack\AppData\Local\pypoetry\Cache\virtualenvs\job-spider-UlnXzhyt-py3.7 做完后他会自动保存到 .vscode 的工程文件夹下。但是我默认这个文件是不 check in 的,所以然并卵 ┑( ̄Д  ̄)┍

Win 启动 flask 失败

新建了一个 flask demo,启动的时候报错

1
2
3
4
PS C:\Users\jack\gitStore\job-spider> poetry run .\job_spider\main.py

[OSError]
[WinError 193] %1 不是有效的 Win32 应用程序。

据说是 windows 上安装了 64 位的 python, 调用了 32 位的 dll 会报这个错,换个 32 位的 python 就能解决。将原有的 64 位卸载,删除各种环境变量,重新安装 32 位 python,然并卵,要自闭了 ( ̄ε(# ̄)

暂时没有什么其他更好的解决方案,打算用虚拟机或者在 MacOS 上完成开发以节省时间

今天在 Mac 上用 3.7.8 的版本也会抛同样的错误!!!难道是版本有问题?果断用 3.6.6, 3.7.3 试试,可行。。。。回去再到 Windows 的机子上试试这个版本。

在 Win 上换 3.7.3 之后一切正常 ╰(艹皿艹 )

MacOS poetry install 报错

切换到 3.6.5 之后 poetry install 报错

1
2
3
4
5
[EnvCommandError]
Command ['/Users/jack/gitStore/splunk-collector/.venv/bin/pip', 'install', '--no-deps', 'zipp==3.1.0'] errored with the following return code 1, and output:
pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available.
...
...

是 OpenSSL 包缺失导致的

1
2
3
4
5
# 修复,第一行可以不运行,下载包经常卡住
brew update && brew upgrade
brew uninstall --ignore-dependencies openssl; brew install https://github.com/tebelorg/Tump/releases/download/v1.0.0/openssl.rb

brew reinstall python

这之后还重新将 pyenv 管理的 python 重新卸载安装了一下,问题解决

MacOS poetry run

1
2
3
4
 poetry run splunk_collector/main.py

[PermissionError]
[Errno 13] Permission denied

运行 flask demo, permission 报错。完全搞错了。。。。flask 并不是那样运行的。保存完文件之后, 通过如下方式运行,而不是直接用 poetry 或者 python 运行,我 凸^-^凸

1
2
3
$ export FLASK_APP=hello.py
$ flask run
* Running on http://127.0.0.1:5000/

常用 Linux command 备忘录

sh Vs bash

sh 是一种协议 shell command language. 而 /bin/sh/bin/bash 是对他的两种不同的实现, 早起他们基本是一致的,但是随着 bash 的发展,他们变得不兼容起来。/bin/sh 还是标准,/bin/bash 则效率更高

快捷键

  • ctr + w 光标处开始删除一个 word
  • ctr + /, ctr + _ 撤销删除,具体细节有所不同,但是都能达到目的

查看当前目录下文件最近修改时间

两种方式,一种是通过 ls --full-time 显示

1
2
3
4
5
6
ls --full-time
total 60
drwxr-xr-x 2 root root 4096 2021-04-14 10:24:04 +0000 srv
dr-xr-xr-x 13 root root 0 2021-07-12 02:52:11 +0000 sys
drwxr-xr-x 2 root root 4096 2021-07-12 02:53:15 +0000 test
drwxrwxrwt 1 root root 4096 2021-05-04 17:21:05 +0000 tmp

另一种是 stat file_name

1
2
3
4
5
6
7
8
stat test
File: test
Size: 4096 Blocks: 8 IO Block: 4096 directory
Device: bbh/187d Inode: 1055968 Links: 2
Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2021-07-12 02:53:32.000000000
Modify: 2021-07-12 02:53:15.000000000
Change: 2021-07-12 02:53:15.000000000

tee

查看信息的同时做写入操作

1
2
3
4
5
6
7
8
9
10
11
12
ps  | tee info.log      # ps 输出进程信息的同时,将结果导入 info.log 中
# PID TTY TIME CMD
# 23438 ttys000 0:59.10 /bin/zsh -l
# 48670 ttys002 0:01.91 /bin/zsh --login -i
# 71565 ttys003 0:02.87 -zsh
# 72395 ttys003 0:00.00 tee info.log
cat info.log # 查看文本信息
# PID TTY TIME CMD
# 23438 ttys000 0:59.10 /bin/zsh -l
# 48670 ttys002 0:01.91 /bin/zsh --login -i
# 71565 ttys003 0:02.87 -zsh
# 72395 ttys003 0:00.00 tee info.log

重定向

1
2
3
4
5
<: 输入重定向
>: 输出重定向
<<: 截取标准输入
>>: 输出重定向,追加,不覆盖
EOF: 自定义终止符

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 只能在一条命令中完成,文本过长会很累赘
echo "eeeeecho" >> echo.txt

# 将 aaa 写入 a.txt
cat << EOF > a.txt
aaa
EOF

# a.txt 中追加 bbb
cat << EOF >> a.txt
bbb
EOF

# a.txt 拷贝到 b.txt
cat a.txt > b.txt

# 和 cat 类似不过它还有附带显示内容的效果
# tee << EOF > d.txt 会将显示部分吞掉,文件倒是还是生产
tee c.txt << EOF
ccc
EOF

curl

终端获取资源,Sample: curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python

-s: 静默模式,去掉显示进度等信息
-S: 显示错误信息
-L: 自动站点跳转

将 query 结果存到本地文件

1
curl url >> ret.json

ping

ping 命令不需要带 protocal,如果要指定端口可以加 -p

1
ping -p 8089 cloudsearch-dc8.cld.ondemand.com

容量查询

1
2
3
4
5
6
7
8
# 显示系统容量
df -hl

# 显示当前目录下个文件夹大小
du -sh *

# 显示文件大小并倒序排列
du -sh * | sort -hr

ps 命令保留表头

1
2
3
# 这个命令不是很好,比较繁琐,效率也不高。
# 实现方式是先 ps 一下拿到 head 打印出来,再 ps 一次拿到我们想要的结果
ps | head -1; ps | grep java

查看文件/夹大小

1
2
# du: disk usage
du -sh *

链接 SFTP

建立联接

1
$ sfpt username@1.1.1.1 # 回车输入密码

获取文件下载到指定路径

1
2
3
sftp> get /export/sftp/test.csv /Users/my/Downloads
Fetching /export/sftp/test.csv to /Users/my/Downloads/test.csv
/export/sftp/test.csv 100% 133 0.3KB/s 00:00

上传本地文件到服务器指定路径

1
2
3
sftp> put /Users/my/Downloads/re-produce.gif /export/sftp
Uploading /Users/my/Downloads/re-produce.gif to /export/sftp/re-produce.gif
/Users/my/Downloads/re-produce.gif 100% 257KB 86.6KB/s 00:02

统计文件

  • 当前目录下的文件个数,不包含文件夹 ls -l | grep '^-' | wc -l
  • 当前目录下的文件个数,递归 ls -lR | grep '^-' | wc -l
  • 当前目录下的文件夹个数 ls -l | grep '^d' | wc -l

解释:

  • ls -l: 显示当前目录下所有文件,文件+文件夹
  • grep '^-': 删选文件,grep '^-' 筛选文件夹。 示例 -rw-r--r-- 1 jack staff 1061 Aug 3 16:53 LICENSE
  • wc -l: 统计行数

记录一下查了无数遍的换行方法备用,总结一下就是使用 ‘’ + \ + ‘’ 类似的语法做链接,只可使用 ‘xxxx\xxx’ 的话会出现空格

无缝连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
a = '1111111'\
'2222222'\
'3333333'

print(a)
# '111111122222223333333'

print('aaaaaaaaa'
'bbbbbbbbb'
'ccccccccc')
# aaaaaaaaabbbbbbbbbccccccccc

print('aaaaaaaaa'\
'bbbbbbbbb'\
'ccccccccc')
# aaaaaaaaabbbbbbbbbccccccccc

有缝连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
a = '''11111111
22222222
33333333'''
print(a)
# '11111111\n 22222222\n 33333333'

a = '11111111\
22222222\
33333333'
print(a)
# '11111111 22222222 33333333'

print('''55555555555
66666666666
77777777777''')
# 55555555555
# 66666666666
# 77777777777

记录一下工作中常用到的 TestNG, Jmockit 使用案例

DataProvider

单个参数

1
2
3
4
5
6
7
8
9
10
11
12
@DataProvider(name = "singleParam")
public Object[][] singleParam() {
return new Object[][]{
{"Jerry"},
{"Tom"},
};
}

@Test(dataProvider = "singleParam")
public void single_data(String username) {
System.out.println("Get username: " + username);
}

idea 中通过设置 live template 简化操作

  • cmd + , 调出设置界面,搜索 live template
  • 点击 +, 添加 group, 命名为 Unit Test
  • 选中 group,点击 + 添加新规则
  • 模版中输入下面的模版案例
  • 点击 Context 的 define, 选中 java -> declear
  • 点击 Edit variables, Expression 中输入默认值,比如 “methodName”, 这里的规则比较绕,试了好久,至少能 work
1
2
3
4
5
6
@org.testng.annotations.DataProvider(name = "$DATA_PROVIDER_NAME$")
public Object[][] $METHOD_NAME$() {
return new Object[][]{
{$OBJECT$}
};
}

多个参数

1
2
3
4
5
6
7
8
9
10
11
12
@DataProvider(name = "multiParam")
public Object[][] multiParam() {
return new Object[][]{
{"Jerry", 12},
{"Tom", 11},
};
}

@Test(dataProvider = "multiParam")
public void single_data(String username, int age) {
System.out.println("Get username: " + username + ", age: " + age);
}

Mock 类的静态代码块

测试类结构如下

1
2
3
4
5
6
7
public class ClientIPUtils {
private static String token = null;

static {
token = someService.getToken();
}
}

这种类型的测试中,可以通过以下方式绕过 静态代码块 中的逻辑。

1
2
3
4
5
6
7
8
@BeforeClass
public static void before() {
new MockUp<VaultUtil>() {
@Mock
void $clinit() {
}
};
}

如果你的测试逻辑需要不同的 token,你不应该在 case level mock 他,因为它是类级别的代码,jvm 启动的时候只执行一次,之前我像下面这样写测试,导致第二个测试一直失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void test1() {
new Expectations() {
{
someService.getToken();
result = "fake";
}
};
}

@Test
public void test2() {
new Expectations() {
{
someService.getToken();
result = "fake";
}
};
}

解决办法是,通过 MockUp 绕过静态代码块的初始化,当需要改变值的时候,通过 Deencapsulation.setField(Class, field_name, field_value); 实现

Mocked 作用域

如果是 global 参数,那么所有 class 内的 case 都会有影响,如果是 method level 的那只有对应的 case 有影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Teacher{
public String name = "unnamed";

public Teacher(String name) {
this.name = name;
}
}

@Mocked Teacher teacher;
@Test
public void test() { System.out.println(teacher.name); } //output: null

@Test
public void test02 () { System.out.println(new Teacher("Jack").name); } //output: null

如果做 method level 的 mock, 只作用 case 本身

1
2
3
4
5
@Test
public void test(@Mocked Teacher teacher) { System.out.println(teacher.name); } // null

@Test
public void test02 () { System.out.println(new Teacher("Jack").name); } // Jack

Jmockit 和 TestNG 兼容性问题

TestNG 6.9.11+ 和 Jmockit 有兼容性问题,将 @Mocked 通过参数方式传入会抛 Exception

1
2
3
4
5
6
7
8
9
10
public class CompatibleTest {
@Test
public void test(@Mocked UserBean userBean) {}
}

//output:
// org.testng.internal.reflect.MethodMatcherException:
// Data provider mismatch
// Method: test([Parameter{index=0, type=com.objects.UserBean, declaredAnnotations=[@mockit.Mocked(stubOutClassInitialization=false)]}])
// Arguments: []

修复方法:将 @Mocked 部分提取改为 global 的变量即可

1
2
3
4
5
6
public class CompatibleTest {
@Mocked UserBean userBean;

@Test
public void test() {}
}

如果我还想保留这种 case level 的使用,需要做点什么?这种 case level 的使用在作用域控制上更好

TODO

Mock 不带默认构造函数的对象

构建一个测试对象时,如果他没有默认构造函数的话需要为参数声明 @Injectable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 测试对象
class Dog{
private String name;

public Dog(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

public class CompatibleTest {
@Tested Dog dog;
@Injectable String name;

@Test
public void test() { dog.getName(); }
}

// 如果没加的话抛出异常
// java.lang.IllegalArgumentException: No constructor in tested class that can be satisfied by available injectables
// public com.successfactors.legacy.service.provisioning.impl.Dog(String)
// disregarded because no injectable was found for parameter "name"

Mockup 工厂方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* new object + mockup, new object 发生在 mock 之后,所以 mock 生效
*/
@Test
public void mock_factory_using_mockup() {
new MockUp<NPCFactory>() {
@Mock
public Person getNPC() {
return new Person("mock", 1);
}
};

ClassRoom classRoom = new ClassRoom();
assertEquals("mock", classRoom.getNPCName());
}

public class ClassRoom {
private Person npc = NPCFactory.getNPC();
public String getNPCName() { return npc.getName(); }
}

使用 Deencapsulation 设置私有变量,高版本已经 deprecated

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* new object + expectations, new object 发生在 mock 之后,所以 mock 生效
*/
@Test
public void mock_factory_using_deencapsulation(@Mocked final Person person) {
new Expectations() {{
person.getName();
result = "deenMock";
}};

Deencapsulation.setField(room, "npc", person);
assertEquals("deenMock", room.getNPCName());
}

通过 Expectations case level mock 静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* new object + expectations, new object 发生在 mock 之后,所以 mock 生效
*/
@Test
public void mock_factory_using_expectations() {
new Expectations(NPCFactory.class) {{
NPCFactory.getNPC();
result = new Person("expMock", 2);
}};

ClassRoom classRoom = new ClassRoom();
assertEquals("expMock", classRoom.getNPCName());
}

部分 mock/PartialMock

1
2
3
4
5
6
7
8
9
10
11
12
@Tested Person person;

@Test
public void person_name_jack() {
new Expectations(person) {{
person.getName();
result = "jack";
}};

assertEquals("jack", person.getName());
assertEquals(0, person.getAge());
}

partial 对非修饰类型有效吗?有效

new Expectations(ClassA.class) 会对这个 class 的所有实例生效,new Expectations(instance) 则只会对当前这个 instance 起作用,范围更精确

获取 Logger 引用做验证

如果你在 UT 中想要验证某条 log 有没有打印出来,你可以使用 @Capturing annotation。

相比于 @Mocked 而言,@Capturing 最大的特点是,他用于修饰 父类或者接口,那么他的所有实现类都会被 mocked 掉。对 log 的案例来说,我们为 Logger 这个 interface 加上这个注释之后,后续所有的实现都被 mock 掉,然后我们再做验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Tested Class
public class MySubscriber {

private static final Logger LOGGER = LogManager.getLogger(MySubscriber.class);

@Override
public void onEvent(Event event) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Start Process MySubscriber...");
LOGGER.info("End...");
}
}
}

// In UT
@Capturing
private Logger logger;

@Test
public void test_capturing_anno() {
new Expectations(ReadAuditSwitchHelper.class) {{
logger.isInfoEnabled();
result = true;
}};

subscriber.onEvent(context, event);

new Verifications() {{
logger.isInfoEnabled(); times=1;
List<String> capturedInfos = new ArrayList<>();
logger.info(withCapture(capturedInfos));

capturedInfos.stream().forEach(System.out::println);
}};
}

获取方法参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 如果是单个参数
new Verifications() {{
double d;
String s;
mock.doSomething(d = withCapture(), null, s = withCapture());

assertTrue(d > 0.0);
assertTrue(s.length() > 1);
}};

// 如果是多个参数
new Verifications() {{
List<DataObject> dataObjects = new ArrayList<>();
mock.doSomething(withCapture(dataObjects));

assertEquals(2, dataObjects.size());
DataObject data1 = dataObjects.get(0);
DataObject data2 = dataObjects.get(1);
// Perform arbitrary assertions on data1 and data2.
}};

@Mocked 导致 equals 方法失效

今天写 UT 的时候遇到一个问题,当我使用 @Mocked 修饰一个类时,这个类的所有引用都会被 mock 掉,虽然知道有这种特性,但是以前都没有碰到问题,忽视了,debug 花了好久。

示例如下:

准别两个简单的 MyBean 和 MyField, MyField 是 MyBean 的一个属性,并在声明时就做了初始化。

对应的 UT 可以 work,但当我对 MyField 添加 @Mocked 注解时,对应的 equals 方法会被抹去,UT 就挂了。

解决方案有两种:1. 不用 @Mocked; 2. 只做方法层面的 mock

对于第二种方法,testng 升级到 6.1 之后需要配合 @DataProvider 使用,变得麻烦了,也不知道后面的版本会不会修复这个问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class MyBean {
private MyField field = new MyField();

// getter/setter

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyBean myBean = (MyBean) o;
return Objects.equals(field, myBean.field);
}

@Override
public int hashCode() {
return Objects.hash(field);
}
}

public class MyField {
private String name;

// getter/setter

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyField myField = (MyField) o;
return Objects.equals(name, myField.name);
}

@Override
public int hashCode() {
return Objects.hash(name);
}
}

public class TestMockedAnno01 {
// @Mocked MyField field;

@Test
public void test() {
MyBean bean1 = new MyBean();
MyBean bean2 = new MyBean();

Assert.assertEquals(bean1, bean2);

}
}

想要解决的问题:

  1. Apache, Tomcat 和 Nginx 的定义/区别
  2. Server 搭配拓扑图

Apache, Tomcat 和 Nginx 的定义/区别

名词解释:

  • 静态服务器,就是每次访问同一个地址只能返回同样的内容,不会改变

Apache

这里说的 Apache 指的是 Apache Http Server。静态服务器的一种,老牌(始于1995),曾经的王者,近年来市场占有率下降。
模块多,性能稳定,rewrite 性能搞,配置相对复杂

Nginx

毛子出品,2004年首发,声势迅猛。如今是三巨头之一(另两个是Microsoft, Apache),和 Apache 是同类产品。
支持反向代理,轻量级,非阻塞,高并发,社区活跃,bug 多

Tomcat

全名是 Apache Tomcat,Application Server 的一种,用来提供动态支持,和前面的不是一种类型。

Server 搭配拓扑图

1
2
3
4
5
6
7
8
9
10
11
12
                                                       +-----------+
-------->| Tomcat01 |
| | |
+--------------+ +------------+ | +-----------+
| Client | |Apache/Nginx| |
| |--------> | |------|
| | | | |
+--------------+ +------------+ |
| +-----------+
-------->| Tomcat02 |
| |
+-----------+

记录 Pandas 常用方法作为快速入门导航

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
path = '/Users/i306454/Downloads/dump.json'
dump = pandas.read_json(path)
# 输出一个二维数组

# 显示每一列的基本信息,包括类型,是否空等
dump.info()
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 18846 entries, 0 to 18845
# Data columns (total 6 columns):
# # Column Non-Null Count Dtype
# --- ------ -------------- -----
# 0 subject 18846 non-null object
# 1 receive_from 18846 non-null object
# ...

# 显示可计算列的统计信息,最值,方差,分布等
dump.describe()
# size
# count 1.884600e+04
# mean 5.790807e+04
# ...

# 显示前三条,用作预览
dump.head(3)

# 取值 loc/iloc, loc 通过名字,iloc 通过数字标签
# 取1,2 行, size 到 receive_data 矩阵, 和 python 的语法不一样的是这个表达式会包含第二行
dump.loc[1:2, 'size':]
# size receive_date
# 1 11593 2015-08-06T08:36:19
# 2 15863 2017-08-06T08:09:36

# 直接到方括号可以选择列
dump['size']

# 查看矩阵大小
dump.shape
# (15, 6)

# 查看 size > 3M 的行
dump[dump['size'] > 2000000]

# 画直方图, bins 如果是数字的话表示你想分成几个 bar
dump['size'].hist(bins=20)

# pandas 的直方图可选项比较少,画图可以用 matplotlib
import matplotlib.pyplot as plt

x = [2500*sub for sub in range(0, 22)]
plt.figure(figsize=(20, 8), dpi=80)
plt.hist(main['size'].values, bins=range(0, 50000+1, 2500))
plt.grid()
plt.xticks(x, ['{:.1f}KB'.format(sub/1000) for sub in x])
plt.show()

# 生产次云, 通过 scale 控制清晰度
titles = dump['subject']
titletxt = ' '.join(titles)
wordcloud = WordCloud(scale=10).generate(titletxt)
plt.figure(figsize=(20, 8), dpi=80)
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis("off")
plt.show()

修改 pandas describe 格式

1
2
3
4
5
6
7
8
9
10
11
pd.set_option('display.float_format', lambda x: '{:.2f}KB'.format(x/1000))
dump.describe()
# size
# count 18.85KB
# mean 57.91KB
# std 143.28KB
# min 3.21KB
# 25% 11.09KB
# 50% 23.72KB
# 75% 42.62KB
# max 5235.50KB

使用 python 解析 PDF 文件,提取文件中表格的数据。随便在网上找了一个 PDF 文件做样本。使用 filetype:pdf 价格表格 的到样本文件。

稍微检索了一下,当下貌似名为 camelot 的 python lib 很火,就用这个做实验吧

安装

这一步还挺复杂,需要安装挺多依赖,具体参考官方文档,这里只记录我本地环境的安装步骤

MacOS:

  1. brew install tcl-tk ghostscript, 然后终端输入 gs -version, 在 python 命令行中输入 import tkinter 验证依赖是否安装成功
  2. pip3 install camelot-py[cv] --user 安装报错,是 zsh 的锅,切换回 bash 安装即可

测试

运行了一下官方给的例子,成功。但是我自己下载的中文 pdf 有问题,查了下,是说 camelot 基于 PyPDF2,然后这个 lib 是不支持处理中文字符的,不过可以通过修改对应 lib 的源码实现支持,网上有教程。不过我暂时只处理英文文档,就不纠结了。

1
2
3
4
import camelot
tables = camelot.read_pdf('foo.pdf')
tables[0].df
# 输出表格,foo.pdf 在官方教程中有给下载链接