Application Factory Kullanılan Flask Uygulamalarının Test Edilmesi

Selamlar.

Flask uygulamalarında illa ki application factory kullanmışsınızdır. Kullanmadıysanız işleriniz bir parça daha sıkıntılı.

Factory pattern kullanmanın birçok avantajı bulunuyor. Bunlardan ilki yazı başlığında da belirtildiği gibi test işlemleri. Factory pattern kullanırken aslında environment set etmeniz mümkün oluyor.

Bir diğer avantajı ise tek bir uygulama için birden fazla ayara bağlı ayağa kaldırma set edebilirsiniz. Örnek vereyim. App factory ile 2 farklı environment’e bağlı ayağa kaldırma şöyle olsun;

1-) Development

export FLASK_APP=app.py
export FLASK_ENV=development
export FLASK_RUN_PORT=8080

# çalıştır
flask run

Yukarıdaki komutu çalıştırdığınızda aşağıdaki gibi bir çıktı alırsınız;

* Serving Flask app "app.py" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:8080/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 233-955-365

2-) Production

1-) Development

export FLASK_APP=app.py
export FLASK_ENV=production
export FLASK_RUN_PORT=8090

# çalıştır
flask run

Bunda da şöyle bir çıktı alırsınız;

* Serving Flask app "app.py"
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:8090/ (Press CTRL+C to quit)

Burada configlerden birisi de test oluyor tabii. Environment her zaman terminalden gelmeyebilir. Bu durumda oluşan factory ile environment set edebiliriz. Örnek verelim;

class Test(BaseConfig):

    PRESERVE_CONTEXT_ON_EXCEPTION = False
    DEBUG = True
    TESTING = True
    SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'dolphin_test.db')
    SQLALCHEMY_TRACK_MODIFICATIONS = False

Yukarıdaki bizim Test için configlerimizi içeren bir Python class’ı. Normal database’imiz dolphin.db iken Test database’imiz dolphin_test.db oldu. Böylelikle development ve test dblerimiz tamamen birbirinden bağımsız olmuş oldu.

Peki ben nasıl bi factory method oluşturuyorum? Ben şöyle kullanıyorum.

config = {
    "development": "config.Development",
    "production": "config.Production",
    "test": "config.Test"
}

db = SQLAlchemy()

def create_app(cfg: str = ''):

    app = Flask(__name__)

    env = (os.getenv('ENV') or os.getenv('FLASK_ENV')) or 'development'

    if cfg:
        env = cfg

    app.config.from_object(config.get(env))

    db.init_app(app)

    app.app_context().push()

    # burada diğer uygulama logici var, route ve login gibi.

    return app

Yukarıdaki kodda eğer parametreyi default olarak set etmezseniz flask run komutunda flask script alır doğru çalışamayabilirsiniz.

Uygulamanın dotenv ya da export üzerinden gelmesine karşın kontrol işlemi yaptım. Bunlardan hiçbirisi yoksa da default development verdim. Eğer cfg truthy bir value ise gidip onu object üzerinden çağıracak.

Normalde aşağıdaki gibi bir test kodu yazmazsınız ama bu yazı için ilk aklıma geleni yazdım şöyle bir test işlemi yapacaksınız. Ben unittest kütüphanesini kullandım.

tests/test_user.py

import unittest
import random
import string

from werkzeug.security import generate_password_hash, check_password_hash

from app import create_app, db
from app.models import User

class UsersTestCase(unittest.TestCase):

    """
    Test users table create
    """

    def setUp(self):
        self.app = create_app('test')

        db.init_app(self.app)
        db.create_all(app=self.app)

        self.client = self.app.test_client()

    def test_add_user(self):

        letters = string.ascii_lowercase
        random_letter = ''.join(random.choice(letters) for i in range(10))
        random_letter_end = ''.join(random.choice(letters) for i in range(10))

        name = "John Doe"
        email = f"{random_letter}@{random_letter_end}"
        username = random_letter
        password = random_letter

        new_user = False

        user = User.query.filter((User.email==email) | (User.username==username)).first()

        if not user:
            new_user = User(name=name, email=email, username=username, password=generate_password_hash(password, method='sha256'))
            db.session.add(new_user)
            db.session.commit()

        self.assertTrue(new_user, 'The user couldn\'t be added')

    def test_get_users(self):
        users = User.query.all()

        self.assertTrue(len(users) > 0, 'There are no users')

setUp metodu içerisinde application factory içine parametre olarak test ortamını kullanacağımı söyledim. Böylelikle dolphin_test.db dosyası oluşturulacak ve development içerisindeki databaseim hiçbir değişikliğe uğramayacak.

Testimizi de unutmadan şöyle çalıştıralım;

python -m unittest discover -v -s tests/

Başarılı 🙂

test_add_user (test_users.UsersTestCase) ... ok
test_get_users (test_users.UsersTestCase) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.158s

OK

Umarım application factory kullanmanın önemini bu yazı ile anlatabilmişimdir 🙂 Hatalı, eksik gördüğünüz şeyler var ise belirtirseniz güncellerim hatta ekstra katmak istediğiniz var ise yine güncellerim yazıyı 🙂

Okuduğunuz için teşekkürler 🙂

Kaynaklar