背(bei)景
當前DTS項目(mu)使用(yong)(yong)pytest測(ce)(ce)試(shi)框架運行自(zi)(zi)動化測(ce)(ce)試(shi)用(yong)(yong)例,但由于測(ce)(ce)試(shi)環境的資源(yuan)(yuan)(yuan)(yuan)限制,只為(wei)自(zi)(zi)動化測(ce)(ce)試(shi)項目(mu)部署(shu)了(le)一個(ge)源(yuan)(yuan)(yuan)(yuan)數(shu)據庫(ku)和一個(ge)目(mu)標數(shu)據庫(ku),這導致(zhi)有(you)一些測(ce)(ce)試(shi)用(yong)(yong)例無法開(kai)發。比(bi)如MYSQL->MYSQL的版(ban)本檢(jian)查(cha)、源(yuan)(yuan)(yuan)(yuan)庫(ku)binlog存在性(xing)檢(jian)查(cha)、源(yuan)(yuan)(yuan)(yuan)庫(ku)binlog是否(fou)開(kai)啟檢(jian)查(cha)、源(yuan)(yuan)(yuan)(yuan)庫(ku)binlog影像類型檢(jian)查(cha)、源(yuan)(yuan)(yuan)(yuan)庫(ku)用(yong)(yong)戶權限檢(jian)查(cha)、源(yuan)(yuan)(yuan)(yuan)庫(ku)連(lian)通性(xing)檢(jian)查(cha)、MySQL參數(shu)lower_case_table_names一致(zhi)性(xing)檢(jian)查(cha)等等。
上面列舉(ju)的(de)(de)測試(shi)用例(li)只是開發(fa)了正向(xiang)用例(li),因(yin)為只有一套數(shu)據(ju)庫可用,無(wu)法做反向(xiang)用例(li),如果通過更改數(shu)據(ju)庫配(pei)置來滿(man)足反向(xiang)用例(li)的(de)(de)條件:
1、手工執(zhi)行(xing)重(zhong)啟(qi)(qi)命令并指(zhi)定啟(qi)(qi)動(dong)參數(shu),繁瑣而且(qie)這也(ye)不是(shi)自動(dong)化測試了(le);
2、需(xu)要的(de)時間(jian)長,拖慢自(zi)動化測(ce)試(shi)的(de)進度;
3、更改(gai)重要(yao)的參數(shu)再改(gai)回來可能(neng)會影響其(qi)他一些測例的執行(xing)。
4、數據庫(ku)版(ban)本檢查的測例只(zhi)能(neng)通過部署(shu)多套不同版(ban)本的數據庫(ku)來完成。
綜合(he)以上的(de)(de)問題,目前DTS的(de)(de)自動化(hua)測試用例還是缺少很多,只能由測試人員(yuan)手工(gong)執行(xing),這消(xiao)耗了測試人員(yuan)的(de)(de)很多時間。
解(jie)決(jue)方案
容器(qi)(qi)以輕量、方便而著稱,但是測試環境資源有(you)限(xian),測試用例(li)多(duo),啟動多(duo)個容器(qi)(qi)實例(li)所需的資源無法滿足(zu),手(shou)工管理也(ye)很麻(ma)煩。
如果通過編(bian)程語言(yan)遠(yuan)程啟(qi)動docker容(rong)器(qi)(qi)來代替人為操(cao)作(zuo),需要時啟(qi)動容(rong)器(qi)(qi),用完及時釋放資源(yuan)(yuan),就可(ke)以大(da)大(da)節省人力和(he)硬件(jian)資源(yuan)(yuan)。直接使用python的docker依賴(lai)來進行容(rong)器(qi)(qi)操(cao)作(zuo)是一(yi)種可(ke)能的實施(shi)方案,如下啟(qi)動一(yi)個MySQL容(rong)器(qi)(qi)。
client = docker.from_env()
# 啟動MySQL容器
container: Container = client.containers.run(
'mysql:5.7',
command='--lower_case_table_names=1',
detach=True,
name='mysql-container',
ports={'3306/tcp': 13306},
environment={
'MYSQL_ROOT_PASSWORD': 'password',
'MYSQL_USER': 'user',
'MYSQL_PASSWORD': 'password',
'MYSQL_DATABASE': 'mydb'
}
)
# 刪除(chu)容器
container.remove()
不過開源(yuan)框架testcontainers已經封(feng)裝提供了更(geng)為方便的實(shi)現。
TestContainers
官網地(di)址://www.testcontainers.org
TestContainers是一個開源項目,它提(ti)供諸多可以(yi)在Docker容(rong)器中運行(xing)的組件,非(fei)常輕量、方便,需要做的只(zhi)是安裝(zhuang)docker服務,無需其(qi)他配(pei)置(zhi)。它支持Java,Python,Rust,Go,.net等多種語言,可以(yi)提(ti)供測試所需的多種環境(jing)。
testcontainers支(zhi)持(chi)眾多(duo)常用的主流組件(jian)(jian),以(yi)Java為例,支(zhi)持(chi)如下組件(jian)(jian)

其中支持的Databases:

不是所用語言的庫都支(zhi)持(chi)(chi)如(ru)此多的組件,go、node.js的testcontainers庫只支(zhi)持(chi)(chi)寥寥幾種組件。
使用介紹
使用前提:test-containers 基于 Docker,所(suo)以(yi)使用 test-container 前需要安裝(zhuang) Docker環境。
不同版(ban)本testcontainers-python的(de)(de)(de)API差異很大(da),這(zhe)里使用的(de)(de)(de)相(xiang)關(guan)依賴的(de)(de)(de)版(ban)本如下
SQLAlchemy~=1.4.46
testcontainers~=3.7.0
urllib3~=1.25.11
官方示例
testcontainers-python文檔地址://testcontainers-python.readthedocs.io/en/latest/README.html
testcontainers-python給出(chu)的(de)官方示例(li)代碼都很(hen)短(duan)小
def test_docker_run_mysql():
config = MySqlContainer('mysql:5.7.17')
with config as mysql:
engine = sqlalchemy.create_engine(mysql.get_connection_url())
with engine.begin() as connection:
result = connection.execute(sqlalchemy.text("select version()"))
for row in result:
assert row[0].startswith('5.7.17')
以上python代碼啟(qi)動(dong)了(le)5.7.17版本(ben)的容(rong)器(qi)實(shi)例,并且(qie)使(shi)用python的ORM框架sqlalchemy去連(lian)接(jie)數據庫(ku),獲取數據庫(ku)連(lian)接(jie)后執(zhi)行SQL語句并返回結(jie)果。
繼(ji)承關(guan)系
事實上,所有的特性Container都(dou)直(zhi)接或(huo)間(jian)接派生自DockerContainer。以(yi)下是繼承關(guan)系(xi):
DockerContainer
|-KafkaContainer
|-ElasticSearchContainer
|-NginxContainer
|-DbContainer
|-MySqlContainer
|-SqlServerContainer
|-OracleDbContainer
DockerContainer封裝了(le)容器的(de)通(tong)用操作,DbContainer封裝了(le)數據庫的(de)通(tong)用操作。而DockerContainer底(di)層使用python的(de)docker依賴(lai)來進行(xing)容器操作的(de)。
DockerContainer
這是(shi)最(zui)靈活(huo)也是(shi)不太(tai)方(fang)便的(de)容器類(lei)型, 此容器允許使用啟動任何Docker鏡像。
DockerContainer的構造函數
def __init__(self, image, docker_client_kw: dict = None, **kwargs):
self.env = {}
self.ports = {}
self.volumes = {}
self.image = image
self._docker = DockerClient(**(docker_client_kw or {}))
self._container = None
self._command = None
self._name = None
self._kwargs = kwargs
with_env方法用于設置容器的環境變量
def with_env(self, key: str, value: str) -> 'DockerContainer':
self.env[key] = value
return self
with_command方法指定容器啟動(dong)時的參數
def with_command(self, command: str) -> 'DockerContainer':
self._command = command
return self
with_bind_ports用于設置一對綁(bang)定的(de)端口
def with_bind_ports(self, container: int,
host: int = None) -> 'DockerContainer':
self.ports[container] = host
return self
with_exposed_ports方法用于暴露一個內部端(duan)口,綁定的(de)宿主機(ji)端(duan)口是隨機(ji)的(de)
def with_exposed_ports(self, *ports) -> 'DockerContainer':
for port in list(ports):
self.ports[port] = None
return self
get_exposed_port方法用(yong)來獲取指定(ding)的內(nei)部端(duan)口所綁定(ding)的宿主機端(duan)口
@wait_container_is_ready()
def get_exposed_port(self, port) -> str:
mapped_port = self.get_docker_client().port(self._container.id, port)
if inside_container():
gateway_ip = self.get_docker_client().gateway_ip(self._container.id)
host = self.get_docker_client().host()
?
if gateway_ip == host:
return port
return mapped_port
with_volume_mapping方法用于掛(gua)載數據卷
def with_volume_mapping(self, host: str, container: str,
mode: str = 'ro') -> 'DockerContainer':
# '/home/user1/': {'bind': '/mnt/vol2', 'mode': 'rw'}
mapping = {'bind': container, 'mode': mode}
self.volumes[host] = mapping
return self
exec方法用于在容(rong)器內(nei)部執行指(zhi)令
def exec(self, command):
if not self._container:
raise ContainerStartException("Container should be started before")
return self.get_wrapped_container().exec_run(command)
get_wrapped_container方法返回container對(dui)象(xiang),這才(cai)真(zhen)正對(dui)應著一(yi)個(ge)里面有所有的(de)容器信息
def get_wrapped_container(self) -> Container:
return self._container
可以使用DockerContainer啟(qi)動(dong)任何類型的容(rong)器,比如(ru)啟(qi)動(dong)一個MySQL容(rong)器
container = DockerContainer("mysql:5.7")
container.with_env("MYSQL_ROOT_PASSWORD", "afcer554KCJ5")
container.with_command("--lower_case_table_names=1")
container.with_exposed_ports(3306)
container.start()
事實上,MysqlContainer與DockerContainer差(cha)異(yi)很小,MysqlContainer只是(shi)(shi)多做了一點事情(設置環境變量(liang)、檢查容器(qi)是(shi)(shi)否啟動(dong)就(jiu)緒),自動(dong)化測試直接使用DockerContainer也能滿足需求,當然使用MysqlContainer會更加方便一些。
MysqlContainer
MySqlContainer繼承自DbContainer,DbContainer繼承自DockerContainer。
MysqlContainer構造函數(shu)
def __init__(self,
image="mysql:latest",
MYSQL_USER=None,
MYSQL_ROOT_PASSWORD=None,
MYSQL_PASSWORD=None,
MYSQL_DATABASE=None,
**kwargs):
super(MySqlContainer, self).__init__(image, **kwargs)
self.port_to_expose = 3306
self.with_exposed_ports(self.port_to_expose)
self.MYSQL_USER = MYSQL_USER or environ.get('MYSQL_USER', 'test')
self.MYSQL_ROOT_PASSWORD = MYSQL_ROOT_PASSWORD or environ.get('MYSQL_ROOT_PASSWORD', 'test')
self.MYSQL_PASSWORD = MYSQL_PASSWORD or environ.get('MYSQL_PASSWORD', 'test')
self.MYSQL_DATABASE = MYSQL_DATABASE or environ.get('MYSQL_DATABASE', 'test')
?
if self.MYSQL_USER == 'root':
self.MYSQL_ROOT_PASSWORD = self.MYSQL_PASSWORD
鏡像默認是latest,最新版。
啟動容器(qi)
創建一(yi)個MysqlContainer對(dui)象后,只(zhi)需簡單調用(yong)start方法即可啟(qi)動容器(qi)
# 啟(qi)動容(rong)器
mysqlContainer.start()
start方(fang)法(fa)的(de)定義(yi)
class DbContainer(DockerContainer):
?
@wait_container_is_ready(*ADDITIONAL_TRANSIENT_ERRORS)
def _connect(self):
import sqlalchemy
engine = sqlalchemy.create_engine(self.get_connection_url())
engine.connect()
?
def start(self):
# 配(pei)置環境變(bian)量
self._configure()
# 這一步就是把(ba)容(rong)器啟(qi)動起(qi)來
super().start()
# 驗證容器服務是(shi)否可用
self._connect()
return self
?
在start方法里的(de)最后調用_connect()方法是為了驗(yan)證容器服務是否可(ke)用。_connect方(fang)法使用SQLAlchemy來(lai)連接數據庫(ku),SQLAlchemy通(tong)過(guo)容器(qi)的get_connection_url()方(fang)法獲取數據庫(ku)地址進(jin)行連接。_connect方法添(tian)加(jia)了@wait_container_is_ready注解,會一直等待直到數(shu)據(ju)庫連接成功(gong),超(chao)時時間(jian)120s。
SQLAlchemy是一個開源的Python ORM(對(dui)象(xiang)關系映射)框架。
在(zai)windows上(shang)進行(xing)測試時(shi),MysqlContainer的get_connection_url方法返回如下的URL對(dui)象
mysql+pymysql://test_asd:afrvte54657hnngf@localnpipe:65303/test
PostgresContainer的get_connection_url方(fang)法返回如下的URL對象(xiang)
postgresql+psycopg2://postgres:***@localnpipe:64211/postgres
這(zhe)里顯(xian)然是通(tong)過named pipe來連接數(shu)據庫,然而連接失敗次(ci)數(shu)達到指(zhi)定(ding)值(zhi),容器啟動失敗。
Windows命名管(guan)道只(zhi)能用于Windows主機(ji)上(shang)的(de)進程(cheng)間通信,WSL上(shang)運(yun)行(xing)(xing)的(de)Docker容器被視為獨立(li)的(de)進程(cheng)空間,因此無法通過命名管(guan)道進行(xing)(xing)通信。與WSL2中(zhong)運(yun)行(xing)(xing)的(de)Docker容器進行(xing)(xing)通信,需要使用網絡(luo)通信協議,如TCP協議。
即使(shi)我在Windows本地啟動MySQL服務,使(shi)用SQLAlchemy連接以上的(de)URL對(dui)象也是失敗。
閱讀了(le)testcontainers-python源碼,通(tong)過繼(ji)承MySqlContainer重寫其get_connection_url()來解決。
from testcontainers.mysql import MySqlContainer
?
?
class CustomMysqlContainer(MySqlContainer):
def get_connection_url(self):
return 'mysql+pymysql://{0}:{1}@127.0.0.1:{2}/{3}'.format('root',
self.MYSQL_ROOT_PASSWORD,
self.get_exposed_port(3306),
self.MYSQL_DATABASE)
將host寫死為(wei)127.0.0.1,這樣即(ji)可解(jie)決。
而(er)testcontainers-java則是使用localhost連接的:
19:09:15.663 [main] INFO ?? [mysql:5.7.34] - Container mysql:5.7.34 is starting: 18f8abf99e470a467d86d5b37c09be2b7cfd94e5b270d7e633b001fbb3db2ae2
19:09:16.095 [main] INFO ?? [mysql:5.7.34] - Waiting for database connection to become available at jdbc:mysql://localhost:64513/test using query 'SELECT 1'
19:09:24.280 [main] INFO ?? [mysql:5.7.34] - Container is started (JDBC URL: jdbc:mysql://localhost:64513/test)
19:09:24.281 [main] INFO ?? [mysql:5.7.34] - Container mysql:5.7.34 started in PT8.7804708S
創建(jian)mysql賬號
MysqlContainer構造函(han)數(shu)中,可以指定(ding)參數(shu)MYSQL_USER、MYSQL_PASSWORD、MYSQL_ROOT_PASSWORD、MYSQL_DATABASE來創建(jian)賬戶相(xiang)關信息。
mysqlContainer = MysqlContainer(image='mysql:5.7',
MYSQL_USER='test_asd',
MYSQL_ROOT_PASSWORD='afrvte54657hnngf',
MYSQL_PASSWORD='afrvte54657hnngf',
MYSQL_DATABASE='test')
在(zai)容(rong)器(qi)啟動過(guo)程中看到如下日志
[Warning] root@localhost is created with an empty password ! Please consider switching off the --initialize-insecure option.
MySQL初始化(hua)時root@localhost用(yong)(yong)(yong)戶(hu)(hu)是空(kong)密(mi)(mi)碼。如果設(she)置了(le)MYSQL_ROOT_PASSWORD,容器會在MySQL服務運行后立即設(she)置root@localhost用(yong)(yong)(yong)戶(hu)(hu)密(mi)(mi)碼,并創建一個新用(yong)(yong)(yong)戶(hu)(hu)root@%,兩者密(mi)(mi)碼一致。
指定端口
MySQL容器內的(de)MySQL服務默認運(yun)行在3306號端口,綁定的(de)宿主機(ji)端口是隨機(ji)的(de)。
可以(yi)在(zai)容器啟動前將其綁定到指定的(de)宿主(zhu)機端口(kou),例如
mysqlContainer = MysqlContainer(image='mysql:5.7',
MYSQL_USER='test_asd',
MYSQL_ROOT_PASSWORD='afrvte54657hnngf',
MYSQL_PASSWORD='afrvte54657hnngf',
MYSQL_DATABASE='test')
mysqlContainer.with_bind_ports(3306, 26788)
而with_exposed_ports方法也用于暴露指定(ding)的(de)內部端口,但綁(bang)定(ding)的(de)宿主機端口是隨機的(de)。
使用(yong)with_bind_ports方法綁定(ding)指定(ding)的宿主機(ji)(ji)端(duan)口(kou)需要確保宿主機(ji)(ji)該端(duan)口(kou)空閑,而with_exposed_ports會自動尋找宿主機(ji)(ji)上空閑的一(yi)個可用(yong)端(duan)口(kou)。
指定啟(qi)動參數(shu)
如果(guo)想(xiang)要指(zhi)定(ding)MySQL的運行參數(shu),可以在容(rong)器啟動前使用with_command方法來指(zhi)定(ding)MySQL參數(shu),方法的參數(shu)格式如下
mysqlContainer = MysqlContainer(image='mysql:5.7',
MYSQL_USER='test_asd',
MYSQL_ROOT_PASSWORD='afrvte54657hnngf',
MYSQL_PASSWORD='afrvte54657hnngf',
MYSQL_DATABASE='test')
mysqlContainer.with_command('--lower-case-table-names=1')
# 將把上面設置的command覆蓋
mysqlContainer.with_command('--character_set_server=utf8mb4')
# 想要同時(shi)設置多(duo)個MySQL服務啟動參數,按如(ru)下格式傳遞參數
mysqlContainer.with_command('--lower-case-table-names=1 --character_set_server=utf8mb4')
進入容(rong)器查(cha)看

容器操作
DockerContainer提供了start和stop方法(fa)(fa)(fa),每(mei)次調用start方法(fa)(fa)(fa)就會啟動一個(ge)新容器實(shi)例并返回,stop方法(fa)(fa)(fa)不是停止(zhi)容器而是刪除容器。
def start(self):
logger.info("Pulling image %s", self.image)
docker_client = self.get_docker_client()
self._container = docker_client.run(self.image,
command=self._command,
detach=True,
environment=self.env,
ports=self.ports,
name=self._name,
volumes=self.volumes,
**self._kwargs
)
logger.info("Container started: %s", self._container.short_id)
return self
?
def stop(self, force=True, delete_volume=True):
self.get_wrapped_container().remove(force=force, v=delete_volume)
為(wei)了避免容器一(yi)直運行,容器使(shi)用完后一(yi)定要調用DockerContainer的stop方法刪(shan)除容器實例。
DockerContainer封裝了具體(ti)的容(rong)器實例,提(ti)供了一些工(gong)具方法(fa)如(ru)with_env、with_command,它的實例變量_container才對(dui)應著一個具體(ti)的容器實例(li)。
_container是Container類型的對象(xiang),是python的docker依賴(lai)提供的。
class Container(Model):
""" Local representation of a container object. Detailed configuration may
be accessed through the :py:attr:`attrs` attribute. Note that local
attributes are cached; users may call :py:meth:`reload` to
query the Docker daemon for the current properties, causing
:py:attr:`attrs` to be refreshed.
"""
//...
def pause(self):
"""
Pauses all processes within this container..
"""
return self.client.api.pause(self.id)
?
def remove(self, **kwargs):
"""
Remove this container. Similar to the ``docker rm`` command.
"""
return self.client.api.remove_container(self.id, **kwargs)
def start(self, **kwargs):
"""
Start this container. Similar to the ``docker start`` command, but
"""
return self.client.api.start(self.id, **kwargs)
def stop(self, **kwargs):
"""
Stops a container. Similar to the ``docker stop`` command.
"""
return self.client.api.stop(self.id, **kwargs)
def unpause(self):
"""
Unpause all processes within the container.
"""
return self.client.api.unpause(self.id)
想要(yao)對容器實例進行(xing)其(qi)他操(cao)作,可以通(tong)過DockerContainer的(de)get_wrapped_container方(fang)法獲取_container實例(li)變量。
container = mysqlContainer.get_wrapped_container()
#停止活動
container.pause()
#繼(ji)續活動(dong)
container.unpause()
我們可(ke)以調用(yong)pause方法來模擬數據庫不可(ke)用(yong)狀態。
性能表現和資源占(zhan)用
測試(shi)環(huan)境:Windows WSL2+Windows docker desktop
啟動(dong)容器(qi)(qi)的(de)(de)start方法是同(tong)步的(de)(de),在等(deng)待容器(qi)(qi)完全(quan)啟動(dong)后才會返回,這里的(de)(de)容器(qi)(qi)啟動(dong)時間7.7s

內存占用

使用(yong)testcontainers-python創建(jian)容器時,推(tui)薦使用(yong)官方的Docker鏡像(xiang),是經過優化(hua)和(he)配置的,因(yin)此(ci)占用(yong)的內存資源是相對較少的。
第一(yi)次啟動容器需要拉取鏡像(xiang)耗時會比(bi)較(jiao)久(jiu)。
控制容器啟動超(chao)時(shi)時(shi)間
在start方法里,testcontainers通(tong)過連接(jie)數據庫來(lai)判斷服務是否可用。testcontainers默(mo)認設置的最大連接(jie)重試次數為120次,間隔1s。
os.environ.setdefault("TC_MAX_TRIES", '120')
調(diao)試階段可以(yi)適當調(diao)小此值(zhi)。
雖然單個(ge)容(rong)器(qi)啟動速度(du)很快,測試發現(xian),如果連(lian)續(xu)啟動4個(ge)MySQL容(rong)器(qi),容(rong)器(qi)的啟動時間超(chao)過甚至會超(chao)過120s導致(zhi)測試失(shi)敗(偶爾)。
PostgresContainer
指定參(can)數
postgresContainer.with_command("-c wal_level=logical -c max_connections=134 -c shared_buffers=2GB")
資源占用

docker-compose
testcontainers也支(zhi)持(chi)使用docker-compose。
創建docker-compose.yml文件,啟(qi)動(dong)一(yi)個MySQL和一(yi)個postgres。
version: '3'
services:
db:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test_db
MYSQL_USER: test_user
MYSQL_PASSWORD: test_password
ports:
- "13306:3306"
postgres:
image: postgres:13
environment:
POSTGRES_DB: test_db
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_password
ports:
- "15432:5432"
創建(jian)DockerCompose,指定docker-compose文件在當前(qian)目錄下,啟動DockerCompose
from testcontainers.compose import DockerCompose
?
?
def test_compose():
compose = DockerCompose('.')
compose.start()
?
mysql_host = compose.get_service_host('db', 3306)
print(mysql_host)
mysql_port = compose.get_service_port('db', 3306)
print(mysql_port)
?
'''
Returns tuple[str, str, int] stdout, stderr, return code
'''
return_tuple = compose.exec_in_container('db', ['ls'])
print(return_tuple)
?
time.sleep(1)
?
compose.stop()

testcontainers-java
@Testcontainers: 用于啟用 Testcontainers 支持。它可以用在(zai)類或方法級別,讓 JUnit 在(zai)測試執(zhi)行(xing)前啟動 Docker 容器。例(li)如:
@Testcontainers
public class MyTestClass {
// ...
}
@Container: 用于將 Docker 容器作為測(ce)試(shi)類的(de)靜態字段啟動。這個注(zhu)解可以與 DockerComposeContainer、GenericContainer 和(he)其他擴(kuo)展類一起使用(yong)。例如(ru):
@Container
public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer();
@Testcontainers
public class MyTest {
@Container
private static final MySQLContainer mySQLContainer = (MySQLContainer) new MySQLContainer("mysql:5.7.34")
.withDatabaseName("test")
.withUsername("dds")
.withPassword("asfrer54765dsc")
.withInitScript("mysqlScript.sql")
.withEnv("name","value")
.withCommand("--lower-case-table-names=1 --character_set_server=utf8mb4");
@Test
public void testMysql() throws SQLException {
System.out.println(mySQLContainer.getMappedPort(3306));
try (Connection conn = DriverManager.getConnection(mySQLContainer.getJdbcUrl(), mySQLContainer.getUsername(), mySQLContainer.getPassword())) {
String sql = "SELECT 2 + 2";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
try (ResultSet rs = stmt.executeQuery()) {
rs.next();
int result = rs.getInt(1);
Assertions.assertEquals(4, result);
}
}
}
}
withPassword方法設置的(de)密碼也是root用戶的(de)密碼;
withInitScript可指定MySQL的初(chu)始化(hua)腳本;
withCommand可指定(ding)MySQL參數(shu);
withEnv設(she)置容器環境(jing)變量;
getJdbcUrl返(fan)回JDBC連接urljdbc:mysql://localhost:51319/test;
getMappedPort方法返回容(rong)器內(nei)部(bu)端(duan)口(kou)所綁定的宿主機端(duan)口(kou)。