Thinking about use case before code
Thinking about use case before code
Test Driven Development (TDD) is software development approach in which test cases are developed to specify and validate what the code will do. In simple terms, test cases for each functionality are created and tested first and if the test fails then the new code is written in order to pass the test and making code simple and bug-free.
TDD usually follows the "Red-Green-Refactor" cycle:
The pytest framework makes it easy to write small, readable tests, and can scale to support complex functional testing for applications and libraries
A unit test is a way of testing a unit - the smallest piece of code that can be logically isolated in a system. In most programming languages, that is a function, a subroutine, a method or property.
A unit test is a way of testing a unit - the smallest piece of code that can be logically isolated in a system
We need to add the user_serviceโs URL to setting , letโs write the test case first create a file in tests/unit/test_settings.py
from app.settings import settings def test_user_service_url_in_settings(): assert settings.USER_SERVICES_URL
from app.settings import settings def test_user_service_url_in_settings(): assert settings.USER_SERVICES_URL
Add the missing settings in app/settings.py
from horizons.settings.base_settings import EnvBaseSettings from functools import lru_cache from pydantic import HttpUrl class Settings(EnvBaseSettings): PROJECT_NAME: str = "Sample Service" SECRET_KEY: str USER_SERVICES_URL: HttpUrl = None APPLICATION_MODULES: list[str] = [ "app.auth.models", "app.item.models", ]
from horizons.settings.base_settings import EnvBaseSettings from functools import lru_cache from pydantic import HttpUrl class Settings(EnvBaseSettings): PROJECT_NAME: str = "Sample Service" SECRET_KEY: str USER_SERVICES_URL: HttpUrl = None APPLICATION_MODULES: list[str] = [ "app.auth.models", "app.item.models", ]
Add the missing config in app/.env
SERVER_NAME="sample" SERVER_HOST="0.0.0.0" SERVER_PORT=8000 SECRET_KEY="09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" BACKEND_CORS_ORIGINS=["http://localhost:8000"] LOG_LEVEL="INFO" DATABASE_URL="mysql://horizons:Horizons@2022@localhost:3306/sample" TEST_DATABASE_URL="mysql://horizons:Horizons@2022@localhost:3306/sample_test" USER_SERVICES_URL="http://api.user.joinhorizons.com"
SERVER_NAME="sample" SERVER_HOST="0.0.0.0" SERVER_PORT=8000 SECRET_KEY="09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" BACKEND_CORS_ORIGINS=["http://localhost:8000"] LOG_LEVEL="INFO" DATABASE_URL="mysql://horizons:Horizons@2022@localhost:3306/sample" TEST_DATABASE_URL="mysql://horizons:Horizons@2022@localhost:3306/sample_test" USER_SERVICES_URL="http://api.user.joinhorizons.com"
Run the test again
poetry run pytest tests/unit/test_settings.py -k 'test_user_service_url_in_settings'
poetry run pytest tests/unit/test_settings.py -k 'test_user_service_url_in_settings'
we are great to see itโs success
Integration testing โ also known as integration and testing (I&T) โ is a type of software testing in which the different units, modules or components of a software application are tested as a combined entity
router
, schema
firstschemas.py
Test add Item API endpoint, what we excepted:
POST /api/v1/items/
endpointGet /api/v1/items/:id
endpointDefine our api and data model first
app/item/router.py
@router.post("/", response_model=Response[ItemResponse]) async def create_item( request: ItemRequest, user_id: int = Depends(get_user_id) ): pass @router.get("/{item_id}", response_model=Response[ItemResponse]) async def get_item(item_id: int, user_id: int = Depends(get_user_id)): pass
@router.post("/", response_model=Response[ItemResponse]) async def create_item( request: ItemRequest, user_id: int = Depends(get_user_id) ): pass @router.get("/{item_id}", response_model=Response[ItemResponse]) async def get_item(item_id: int, user_id: int = Depends(get_user_id)): pass
Define our api and data model first
app/item/schemas.py
from .models import Item from horizons.schema_plus.models import BaseModel from tortoise.contrib.pydantic import ( pydantic_model_creator, pydantic_queryset_creator, ) class ItemRequest(BaseModel): name: str description: str ItemResponse = pydantic_model_creator(Item) ItemsResponse = pydantic_queryset_creator(Item)
from .models import Item from horizons.schema_plus.models import BaseModel from tortoise.contrib.pydantic import ( pydantic_model_creator, pydantic_queryset_creator, ) class ItemRequest(BaseModel): name: str description: str ItemResponse = pydantic_model_creator(Item) ItemsResponse = pydantic_queryset_creator(Item)
check your api
Write the test case first in tests/integration/item/test_main.py
@pytest.mark.anyio async def test_add_and_get_item(client_with_token): name = "item1" description = "description1" # use the request data model to post data item = ItemRequest(name=name, description=description) response = await client_with_token.post("/api/v1/items/", json=item.dict()) assert response.status_code == 200 # use the response data model to serialize data response = Response[ItemResponse].parse_raw(response.content) # the return code must be success assert response assert response.code == BusinessCode.NO_ERROR assert response.message == HTTPMessage.SUCCESS.value # The output data must exactly match the input data id = response.data.id assert id assert response.data.name == name assert response.data.description == description ...
@pytest.mark.anyio async def test_add_and_get_item(client_with_token): name = "item1" description = "description1" # use the request data model to post data item = ItemRequest(name=name, description=description) response = await client_with_token.post("/api/v1/items/", json=item.dict()) assert response.status_code == 200 # use the response data model to serialize data response = Response[ItemResponse].parse_raw(response.content) # the return code must be success assert response assert response.code == BusinessCode.NO_ERROR assert response.message == HTTPMessage.SUCCESS.value # The output data must exactly match the input data id = response.data.id assert id assert response.data.name == name assert response.data.description == description ...
Write the test case first in tests/integration/item/test_main.py
@pytest.mark.anyio async def test_add_and_get_item(client_with_token): name = "item1" description = "description1" ... response = await client_with_token.get(f"/api/v1/items/{id}") assert response.status_code == 200 # use the response data model to serialize data response = Response[ItemResponse].parse_raw(response.content) # the return code must be success assert response assert response.code == BusinessCode.NO_ERROR assert response.message == HTTPMessage.SUCCESS.value # The output data must exactly match the input data assert id == response.data.id assert response.data.name == name assert response.data.description == description
@pytest.mark.anyio async def test_add_and_get_item(client_with_token): name = "item1" description = "description1" ... response = await client_with_token.get(f"/api/v1/items/{id}") assert response.status_code == 200 # use the response data model to serialize data response = Response[ItemResponse].parse_raw(response.content) # the return code must be success assert response assert response.code == BusinessCode.NO_ERROR assert response.message == HTTPMessage.SUCCESS.value # The output data must exactly match the input data assert id == response.data.id assert response.data.name == name assert response.data.description == description
./run_test.sh
./run_test.sh
the script will create a test database with seed data
class ItemService: async def add_item( self, user_id: int, request: ItemRequest ) -> ItemResponse: item = Item(**request.dict()) item.user_id = user_id await Item.save(item) return await ItemResponse.from_tortoise_orm(item) async def get_item(self, user_id: int, item_id: int) -> ItemResponse: queryset = Item.filter(user_id=user_id, id=item_id).first() return await ItemResponse.from_queryset_single(queryset)
class ItemService: async def add_item( self, user_id: int, request: ItemRequest ) -> ItemResponse: item = Item(**request.dict()) item.user_id = user_id await Item.save(item) return await ItemResponse.from_tortoise_orm(item) async def get_item(self, user_id: int, item_id: int) -> ItemResponse: queryset = Item.filter(user_id=user_id, id=item_id).first() return await ItemResponse.from_queryset_single(queryset)
@router.post("/", response_model=Response[ItemResponse]) async def create_item( request: ItemRequest, user_id: int = Depends(get_user_id) ): data = await item_service.add_item(user_id, request) return json_response(data=data) @router.get("/{item_id}", response_model=Response[ItemResponse]) async def get_item(item_id: int, user_id: int = Depends(get_user_id)): data = await item_service.get_item(user_id, item_id) return json_response(data=data)
@router.post("/", response_model=Response[ItemResponse]) async def create_item( request: ItemRequest, user_id: int = Depends(get_user_id) ): data = await item_service.add_item(user_id, request) return json_response(data=data) @router.get("/{item_id}", response_model=Response[ItemResponse]) async def get_item(item_id: int, user_id: int = Depends(get_user_id)): data = await item_service.get_item(user_id, item_id) return json_response(data=data)
Run the test again
./run_test.sh
./run_test.sh
we are great to see itโs success
Fire a pull request to the sample project with unit test and integration test codes
test_add_and_get_item
/api/v1/items
api/va/items/{item_id}