├── Dockerfile├── app.py└── integration-test├── Dockerfile├── docker-compose.yml├── test.py├── test.sh└── .env
Dockerfile:
FROM python:3.10.10-alpine3.17
RUN pip3 install tortoise-orm==0.19.3 && \ pip3 install fastapi==0.90.1 && \ pip3 install uvicorn==0.20.0 && \ pip3 install pydantic==1.10.4 && \ pip3 install asyncmy==0.2.5 && \ pip3 install aiomysql==0.1.1 && \ apk update && \ apk add curl
WORKDIR /COPY app.py /app.py
CMD ["sh", "-c", "python app.py"]app.py:
import os
import pydanticfrom fastapi import FastAPIimport uvicornfrom tortoise.contrib.fastapi import register_tortoiseimport tortoise
class ItemRequestSchema(pydantic.BaseModel): item_id: int = pydantic.Field(gt=0) item_name: str = pydantic.Field(max_length=128)
class ItemModel(tortoise.models.Model): item_id = tortoise.fields.IntField(null=False, pk=True) item_name = tortoise.fields.CharField(max_length=128)
class Meta: table = "item"
app = FastAPI()
.get("/health")async def health(): return {"success": True}
.put("/create")async def put(item: ItemRequestSchema): await ItemModel.create(item_id=item.item_id, item_name=item.item_name) return {"success": True}
def main(): register_tortoise( app, db_url=f"mysql://root:{os.getenv('MYSQL_ROOT_PASSWORD')}@mysql:3306/item", modules={"models": ["__main__"]}, generate_schemas=False, add_exception_handlers=True ) uvicorn.run(app, host="0.0.0.0", port=8080)
if __name__ == "__main__": main()integration-test/Dockerfile:
FROM python:3.10.10-alpine3.17
RUN pip3 install requests==2.28.1 && \ pip3 install PyMySQL==1.0.2
WORKDIR /COPY test.py /test.pyCMD ["sh", "-c", "python3 test.py"]integration-test/docker-compose.yml:
version"3.5"
services # 启动数据库 mysql container_namemyproject-mysql environmentMYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD imagemysql8 networksmyproject-network # 健康检查,保证 MySQL 处于就绪 healthcheck testmysql -uroot -proot -e "SELECT 1;" || exit 1 start_period5s interval5s timeout5s retries20 # 启动初始化阶段 init-stage # 依赖 mysql 服务就绪 depends_on mysql conditionservice_healthy container_namemyproject-init-stage environmentMYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD imagemysql8 networksmyproject-network # 创建数据库和表 command+ mysql -hmysql -uroot -p$MYSQL_ROOT_PASSWORD -e "create database if not exists item; use item; create table if not exists item( item_id int primary key, item_name varchar(128) );" # 启动待测试服务 myservice # 依赖初始化阶段成功完成 depends_on init-stage conditionservice_completed_successfully container_namemyproject-myservice environmentMYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD imagemyservice$TAG build context.. networksmyproject-network healthcheck testcurl -sSL -m 1 -o /dev/null http//localhost8080/health start_period2s interval2s timeout2s retries30 # 启动集成测试 integration-tester # 依赖 myservice 服务就绪 depends_on myservice conditionservice_healthy container_namemyproject-integration-tester environmentMYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD imageintegration-tester$TAG build context. networksmyproject-network
networks myproject-network driverbridge namemyproject-networkintegration-test/test.py:
import osimport unittest
import pymysqlimport requests
class TestMyService(unittest.TestCase): def setUp(self) -> None: self.db = pymysql.connect( host="mysql", port=3306, user="root", password=os.getenv("MYSQL_ROOT_PASSWORD"), database="item" ) self.base_url = "http://myservice:8080"
def tearDown(self) -> None: self.db.close()
def testCreate(self): item = { "item_id": 100, "item_name": "item_100" } # 请求 API,插入一条数据 resp = requests.put(f"{self.base_url}/create", json=item) self.assertTrue(200 <= resp.status_code < 300)
# 从 MySQL 中读数据 cursor = self.db.cursor() cursor.execute( """ SELECT `item_id`, `item_name` FROM `item` WHERE `item_id` = %s """, args=[item["item_id"]] ) result = cursor.fetchone() self.assertEqual(result, (item["item_id"], item["item_name"])) cursor.close()
if __name__ == "__main__": unittest.main()integration-test/test.sh:
source .env
# define some colors to use for outputRED='\033[0;31m'GREEN='\033[0;32m'NC='\033[0m'
# kill and remove any running containerscleanup () { docker-compose -p $PROJECT_NAME down # remove images docker image rm --force integration-tester:${TAG} myservice:${TAG}}
# catch unexpected failures, do cleanup and output an error messagetrap 'cleanup ; printf "${RED}Tests Failed For Unexpected Reasons${NC}\n"' \ HUP INT QUIT PIPE TERM
# build and run the composed servicesdocker-compose -p $PROJECT_NAME build && docker-compose -p $PROJECT_NAME up -dif [ $? -ne 0 ] ; then printf "${RED}Docker Compose Failed${NC}\n" exit -1fi
# wait for the test service to complete and grab the exit codeTEST_EXIT_CODE=`docker wait myproject-integration-tester`# output the logs for the test (for clarity)docker logs myproject-integration-tester# inspect the output of the test and display respective messageif [ -z "${TEST_EXIT_CODE}" ] || [ "$TEST_EXIT_CODE" -ne 0 ] ; then printf "${RED}Tests Failed${NC} - Exit Code: $TEST_EXIT_CODE\n"else printf "${GREEN}Tests Passed${NC}\n"fi
# call the cleanup fuctioncleanup# exit the script with the same code as the test service codeexit $TEST_EXIT_CODEintegration-test/.env:
TAG=0.0.1MYSQL_ROOT_PASSWORD=rootPROJECT_NAME=test-cixxxxxxxxxx$ cd integration-test$ chmod u+x test.sh$ ./test.sh# 检查测试脚本的退出码$ echo $?