{"id":23406,"date":"2022-11-10T07:47:21","date_gmt":"2022-11-10T15:47:21","guid":{"rendered":"https:\/\/coderpad.io\/?p=23406"},"modified":"2023-06-05T13:52:17","modified_gmt":"2023-06-05T20:52:17","slug":"a-guide-to-database-unit-testing-with-pytest-and-sqlalchemy","status":"publish","type":"post","link":"https:\/\/coderpad.io\/blog\/development\/a-guide-to-database-unit-testing-with-pytest-and-sqlalchemy\/","title":{"rendered":"A Guide To Database Unit Testing with Pytest and SQLAlchemy"},"content":{"rendered":"\n<p>Testing is a decisive phase in your <a href=\"https:\/\/en.wikipedia.org\/wiki\/Systems_development_life_cycle\" target=\"_blank\" rel=\"noopener\">systems development lifecycle<\/a>. This is an important step to make your software reliable and maintainable in the future. In this tutorial, you will have a practical guide to <a href=\"https:\/\/en.wikipedia.org\/wiki\/Unit_testing\" target=\"_blank\" rel=\"noopener\">unit testing<\/a>, one type of software testing by which individual units of your code are tested to determine if they are fit for use.<\/p>\n\n\n\n<p>We will discuss how to do unit testing using <a href=\"https:\/\/docs.pytest.org\/en\/7.2.x\/\" target=\"_blank\" rel=\"noopener\">pytest<\/a>, which is a Python testing tool. This tool has useful features to help write better programs. We will test a database created by <a href=\"https:\/\/www.sqlalchemy.org\/\" target=\"_blank\" rel=\"noopener\">SQLAlchemy<\/a> ORM.<\/p>\n\n\n\n<p>At the end of this tutorial, you will learn how to test SQLAlchemy ORM using fixtures and the classic way to implement fixtures (setup and teardown methods).<\/p>\n\n\n\n<p>As always, to grasp the ideas introduced in this tutorial, you need to experiment with your code by yourself, whether in your local machine or here in the <a href=\"https:\/\/app.coderpad.io\/sandbox\">CoderPad sandbox<\/a>. The sandbox has a ready-to-use environment to try out your experiments. <\/p>\n\n\n\n<p>You can select <strong>Python3<\/strong>, click on the three dots in the top left corner, and then select SQLAlchemy (Postgres) from the adapters. You&#8217;re free to choose a MySQL adapter, but in this tutorial, we will use the Postgres engine in SQLAlchemy.<\/p>\n\n\n\n<p>Before you start, you need to know more about <a href=\"https:\/\/coderpad.io\/blog\/development\/sqlalchemy-with-postgresql\/\">how to interact with databases using SQLAlchemy with Postgres<\/a> and revise <a href=\"https:\/\/coderpad.io\/blog\/development\/understanding-transactions-in-sqlalchemy\/\">transactions in SQLAlchemy<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Create the database models<\/strong><\/h2>\n\n\n\n<p>We will use the simple database models introduced in this <a href=\"https:\/\/coderpad.io\/blog\/development\/sqlalchemy-with-postgresql\/\">SQLAlchemy tutorial<\/a>.<\/p>\n\n\n\n<p>For this tutorial, we will dump all the code in the Pad on the left window. But for a production use case, you need to structure your files into two files, for example:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>models.py<\/code> to have the database models<\/li>\n\n\n\n<li><code>test_blog.py<\/code> to have the testing suite<\/li>\n<\/ul>\n\n\n\n<p>So the database models would have the following:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python shcb-wrap-lines\"><span class=\"hljs-keyword\">from<\/span> sqlalchemy <span class=\"hljs-keyword\">import<\/span> create_engine, Column, Integer, String, DateTime, Text, ForeignKey\n<span class=\"hljs-keyword\">from<\/span> sqlalchemy.engine <span class=\"hljs-keyword\">import<\/span> URL\n<span class=\"hljs-keyword\">from<\/span> sqlalchemy.orm <span class=\"hljs-keyword\">import<\/span> declarative_base, relationship, sessionmaker\n<span class=\"hljs-keyword\">from<\/span> datetime <span class=\"hljs-keyword\">import<\/span> datetime\n<span class=\"hljs-keyword\">import<\/span> pytest\n\nBase = declarative_base()\n\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Author<\/span><span class=\"hljs-params\">(Base)<\/span>:<\/span>\n    __tablename__ = <span class=\"hljs-string\">'authors'<\/span>\n\n    id = Column(Integer(), primary_key=<span class=\"hljs-literal\">True<\/span>)\n    firstname = Column(String(<span class=\"hljs-number\">100<\/span>))\n    lastname = Column(String(<span class=\"hljs-number\">100<\/span>))\n    email = Column(String(<span class=\"hljs-number\">255<\/span>), nullable=<span class=\"hljs-literal\">False<\/span>)\n    joined = Column(DateTime(), default=datetime.now)\n\n    articles = relationship(<span class=\"hljs-string\">'Article'<\/span>, backref=<span class=\"hljs-string\">'author'<\/span>)\n\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Article<\/span><span class=\"hljs-params\">(Base)<\/span>:<\/span>\n    __tablename__ = <span class=\"hljs-string\">'articles'<\/span>\n\n    id = Column(Integer(), primary_key=<span class=\"hljs-literal\">True<\/span>)\n    slug = Column(String(<span class=\"hljs-number\">100<\/span>), nullable=<span class=\"hljs-literal\">False<\/span>)\n    title = Column(String(<span class=\"hljs-number\">100<\/span>), nullable=<span class=\"hljs-literal\">False<\/span>)\n    created_on = Column(DateTime(), default=datetime.now)\n    updated_on = Column(DateTime(), default=datetime.now, onupdate=datetime.now)\n    content = Column(Text)\n    author_id = Column(Integer(), ForeignKey(<span class=\"hljs-string\">'authors.id'<\/span>))\n\nurl = URL.create(\n    drivername=<span class=\"hljs-string\">\"postgresql\"<\/span>,\n    username=<span class=\"hljs-string\">\"coderpad\"<\/span>,\n    host=<span class=\"hljs-string\">\"\/tmp\/postgresql\/socket\"<\/span>,\n    database=<span class=\"hljs-string\">\"coderpad\"<\/span>\n    )\n\nengine = create_engine(url)\nSession = sessionmaker(bind=engine)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>So we have two tables, authors and articles, and we want to test a few units of a transactional database in SQLAlchemy.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Testing SQLAlchemy with pytest<\/strong><\/h2>\n\n\n\n<p>In the previous section, we defined the <code>Session<\/code> class, which we will use in the testing class. You can define a class called <code>TestBlog<\/code>, which contains the test functions. Each test function should start with <code>test_<\/code> so that pytest can understand it&#8217;s a function that contains test cases.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Fixtures classic way<\/strong><\/h3>\n\n\n\n<p><a href=\"https:\/\/docs.pytest.org\/en\/7.1.x\/explanation\/fixtures.html#about-fixtures\" target=\"_blank\" rel=\"noopener\">Fixtures<\/a> is a powerful feature in pytest. While testing a database created in an SQLAlchemy ORM, you need to ensure that the session is open at each test function call with just one setup. You don&#8217;t need to instantiate it at every test function. You&#8217;d also need to close the session after all unit tests are done.<\/p>\n\n\n\n<p>The classic way of using Python database fixtures in pytest is to use setup and teardown functions. These functions are useful to avoid repeating code at every test function. This is useful because hitting the database multiple times would be discouraged, especially if you&#8217;re testing a large application.<\/p>\n\n\n\n<p>Here is how to write these methods:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python shcb-wrap-lines\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">TestBlog<\/span>:<\/span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">setup_class<\/span><span class=\"hljs-params\">(self)<\/span>:<\/span>\n        Base.metadata.create_all(engine)\n        self.session = Session()\n        self.valid_author = Author(\n            firstname=<span class=\"hljs-string\">\"Ezzeddin\"<\/span>,\n            lastname=<span class=\"hljs-string\">\"Aybak\"<\/span>,\n            email=<span class=\"hljs-string\">\"aybak_email@gmail.com\"<\/span>\n        )\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">teardown_class<\/span><span class=\"hljs-params\">(self)<\/span>:<\/span>\n        self.session.rollback()\n        self.session.close()<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <code>setup_class<\/code> is <a href=\"https:\/\/docs.pytest.org\/en\/7.1.x\/how-to\/xunit_setup.html?highlight=setup_class\" target=\"_blank\" rel=\"noopener\">called before all test methods<\/a> in the <code>TestBlog<\/code>, while the <code>teardown_class<\/code> is called after all test methods. Both are called at the class level.<\/p>\n\n\n\n<p>The setup method creates all your tables; <code>authors<\/code> and <code>articles<\/code> tables. It then defines the session, which is the <code>Session<\/code> object in SQLAlchemy in which the conversation with the database occurs. It also has the <code>valid_author<\/code> object, which we would frequently use in the following test functions.<\/p>\n\n\n\n<p>However, the teardown method is the clean-up phase after all test methods. It rolls back the changes to the database and then closes the session, so there wouldn&#8217;t be any conversation channel between the SQLAlchemy and your database anymore.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Testing a success test case<\/strong><\/h3>\n\n\n\n<p>Let&#8217;s start with testing the content retrieved by the database of the valid author:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python shcb-wrap-lines\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">TestBlog<\/span>:<\/span>\n<span class=\"hljs-comment\">#...<\/span>\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">test_author_valid<\/span><span class=\"hljs-params\">(self)<\/span>:<\/span>   \n        self.session.add(self.valid_author)\n        self.session.commit()\n        aybak = self.session.query(Author).filter_by(lastname=<span class=\"hljs-string\">\"Aybak\"<\/span>).first()\n        <span class=\"hljs-keyword\">assert<\/span> aybak.firstname == <span class=\"hljs-string\">\"Ezzeddin\"<\/span>\n        <span class=\"hljs-keyword\">assert<\/span> aybak.lastname != <span class=\"hljs-string\">\"Abdullah\"<\/span>\n        <span class=\"hljs-keyword\">assert<\/span> aybak.email == <span class=\"hljs-string\">\"aybak_email@gmail.com\"<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here, we use the session to add the valid author object (defined in the setup function). We then commit that change to the database and query that author. At the end of the test function, we typically use an assert statement to <a href=\"https:\/\/docs.pytest.org\/en\/7.1.x\/getting-started.html?highlight=assert#create-your-first-test\" target=\"_blank\" rel=\"noopener\">verify test expectations<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Testing a failure test case<\/strong><\/h3>\n\n\n\n<p>Let&#8217;s add another test method under the <code>TestBlog<\/code> class to test an integrity error, which is an exception raised when there is relational data integrity is affected:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python shcb-wrap-lines\"><span class=\"hljs-keyword\">from<\/span> sqlalchemy.exc <span class=\"hljs-keyword\">import<\/span> IntegrityError\n\n<span class=\"hljs-comment\"># ...<\/span>\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">TestBlog<\/span>:<\/span>\n    <span class=\"hljs-comment\"># ...<\/span>\n<span class=\"hljs-meta\">    @pytest.mark.xfail(raises=IntegrityError)<\/span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">test_author_no_email<\/span><span class=\"hljs-params\">(self)<\/span>:<\/span>\n        author = Author(\n            firstname=<span class=\"hljs-string\">\"James\"<\/span>,\n            lastname=<span class=\"hljs-string\">\"Clear\"<\/span>\n        )\n        self.session.add(author)\n        <span class=\"hljs-keyword\">try<\/span>:\n            self.session.commit()\n        <span class=\"hljs-keyword\">except<\/span> IntegrityError:\n            self.session.rollback()<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In this test function, we use a <code>pytest.mark.xfail<\/code> decorator to mark this <code>test_author_no_email<\/code> test function that <a href=\"https:\/\/docs.pytest.org\/en\/6.2.x\/skipping.html\" target=\"_blank\" rel=\"noopener\">can fail for some reason<\/a>. The reason to fail here is to have an <code>IntegrityError<\/code> while committing an author object with no email. That&#8217;s because when we defined the <code>authors<\/code> table, we made a restriction on the email attribute to not have a <strong>NULL<\/strong> record.<\/p>\n\n\n\n<p>In this test function, we use a <code>try\/except<\/code> statement to be able to <a href=\"https:\/\/coderpad.io\/blog\/development\/understanding-transactions-in-sqlalchemy\/\">roll back the transaction<\/a> where the <code>IntegrityError<\/code> exception occurs while committing to the DB.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Testing a referenced table<\/strong><\/h3>\n\n\n\n<p>To reference a foreign key, you need to have the referenced object defined in the same test function, or you can define that object in the setup phase, where you can import it to other test functions. In our case, we defined the <code>valid_author<\/code> object, in the setup function, which we referenced in the articles table:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python shcb-wrap-lines\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">TestBlog<\/span>:<\/span>\n    <span class=\"hljs-comment\"># ...<\/span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">test_article_valid<\/span><span class=\"hljs-params\">(self)<\/span>:<\/span>\n        valid_article = Article(\n            slug=<span class=\"hljs-string\">\"sample-slug\"<\/span>,\n            title=<span class=\"hljs-string\">\"Title of the Valid Article\"<\/span>,\n            content=<span class=\"hljs-string\">\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"<\/span>,\n            author=self.valid_author\n            )\n        self.session.add(valid_article)\n        self.session.commit()\n        sample_article = self.session.query(Article).filter_by(slug=<span class=\"hljs-string\">\"sample-slug\"<\/span>).first()\n        <span class=\"hljs-keyword\">assert<\/span> sample_article.title == <span class=\"hljs-string\">\"Title of the Valid Article\"<\/span>\n        <span class=\"hljs-keyword\">assert<\/span> len(sample_article.content.split(<span class=\"hljs-string\">\" \"<\/span>)) &gt; <span class=\"hljs-number\">50<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here, we define an object for a valid article with the referenced valid author defined in the setup method. We then add and commit it to the database.<\/p>\n\n\n\n<p>Finally, we verify our tests. For example, we verify the title and then verify the article&#8217;s length to be greater than 50 words.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Running pytest<\/strong><\/h3>\n\n\n\n<p>To invoke pytest, you have <a href=\"https:\/\/docs.pytest.org\/en\/7.1.x\/how-to\/usage.html\" target=\"_blank\" rel=\"noopener\">some options<\/a> mentioned in the pytest documentation. In this tutorial, you can use the following line in the sandbox console:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">pytest<\/span><span class=\"hljs-selector-class\">.main<\/span>(<span class=\"hljs-selector-attr\">&#91;<span class=\"hljs-string\">'-v'<\/span>]<\/span>)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We use the -v option here to increase verbosity and show each test function result.<\/p>\n\n\n<div\n\tclass=\"sandbox-embed responsive-embed \"\n\tstyle=\"padding-top: 125%\"\ndata-block-name=\"coderpad-sandbox-embed\">\n\t<iframe src=\"https:\/\/embed.coderpad.io\/sandbox?question_id=234635&#038;use_question_button\" width=\"640\" height=\"800\" loading=\"lazy\" aria-label=\"Try out the CoderPad sandbox\"><\/iframe>\n<\/div>\n\n\n\n<p>As you can see in your sandbox, there are 3 test cases for each test function. The first and third test cases succeeded with a PASSED keyword. However, the second test case succeeded with an XPASS keyword to <strong>indicate that pytest caught the expected error successfully.<\/strong><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Using fixtures<\/strong><\/h2>\n\n\n\n<p>Instead of the classic way to use a fixture, let&#8217;s look at how to define a fixture itself. But why do we need fixtures anyway?<\/p>\n\n\n\n<p>In addition to the benefits of the classic way of defining fixtures, fixtures themselves are useful because they lead to dependency inversion. Dependency inversion is a design pattern useful when a test function receives other objects it depends on. It aims to separate the concerns which lead to <a href=\"https:\/\/en.wikipedia.org\/wiki\/Dependency_injection\" target=\"_blank\" rel=\"noopener\">loosely coupled programs<\/a>.<\/p>\n\n\n\n<p>Let&#8217;s see fixtures in action and get rid of the setup_class and teardown_class methods. Now, define the following before the <code>TestBlog<\/code> class:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python shcb-wrap-lines\"><span class=\"hljs-meta\">@pytest.fixture(scope=\"module\")<\/span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">db_session<\/span><span class=\"hljs-params\">()<\/span>:<\/span>\n    Base.metadata.create_all(engine)\n    session = Session()\n    <span class=\"hljs-keyword\">yield<\/span> session\n    session.rollback()\n    session.close()\n\n<span class=\"hljs-meta\">@pytest.fixture(scope=\"module\")<\/span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">valid_author<\/span><span class=\"hljs-params\">()<\/span>:<\/span>\n    valid_author = Author(\n        firstname=<span class=\"hljs-string\">\"Ezzeddin\"<\/span>,\n        lastname=<span class=\"hljs-string\">\"Aybak\"<\/span>,\n        email=<span class=\"hljs-string\">\"aybak_email@gmail.com\"<\/span>\n    )\n    <span class=\"hljs-keyword\">return<\/span> valid_author<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>These fixtures are defined at the module level. That&#8217;s why there is a scope option inside the <code>pytest.fixture<\/code> decorator. A fixture is called based on the scope. <\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u2139\ufe0f A <a href=\"https:\/\/docs.pytest.org\/en\/7.1.x\/how-to\/fixtures.html#scope-sharing-fixtures-across-classes-modules-packages-or-session\" target=\"_blank\" rel=\"noopener\">scope<\/a> is what you can use to share fixtures across classes, modules, packages, or sessions. In our case, the <code>db_session<\/code> and <code>valid_author<\/code> fixtures can only be called across this module.<\/p>\n<\/blockquote>\n\n\n\n<p>The first fixture function creates all the tables metadata and then yields the session object whenever it&#8217;s called. Each test function calling the session will receive the same session instance, <a href=\"https:\/\/docs.pytest.org\/en\/7.1.x\/how-to\/fixtures.html#scope-sharing-fixtures-across-classes-modules-packages-or-session\" target=\"_blank\" rel=\"noopener\">thus saving time<\/a>. So that fixture function is invoked once per the test module.<\/p>\n\n\n\n<p>Using the yield statement is <a href=\"https:\/\/docs.pytest.org\/en\/7.1.x\/how-to\/fixtures.html#yield-fixtures-recommended\" target=\"_blank\" rel=\"noopener\">recommended<\/a>. After the yield line, the session is rolled back and then closed, equivalent to the teardown code.<\/p>\n\n\n\n<p>The second fixture function returns the valid author object whenever a test function calls it.<\/p>\n\n\n\n<p>Note: The separation of concerns here is clear. Instead of the classic setup function, which contained both the session and the valid author objects, we now have two separate fixture functions for each.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Calling fixtures<\/strong><\/h3>\n\n\n\n<p>To call a fixture inside a test function, you need to pass it as an argument to that function. Here is the new <code>TestBlog<\/code> class with the associated test functions:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python shcb-wrap-lines\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">TestBlog<\/span>:<\/span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">test_author_valid<\/span><span class=\"hljs-params\">(self, db_session, valid_author)<\/span>:<\/span>\n        db_session.add(valid_author)\n        db_session.commit()\n        aybak = db_session.query(Author).filter_by(lastname=<span class=\"hljs-string\">\"Aybak\"<\/span>).first()\n        <span class=\"hljs-keyword\">assert<\/span> aybak.firstname == <span class=\"hljs-string\">\"Ezzeddin\"<\/span>\n        <span class=\"hljs-keyword\">assert<\/span> aybak.lastname != <span class=\"hljs-string\">\"Abdullah\"<\/span>\n        <span class=\"hljs-keyword\">assert<\/span> aybak.email == <span class=\"hljs-string\">\"aybak_email@gmail.com\"<\/span>\n\n<span class=\"hljs-meta\">    @pytest.mark.xfail(raises=IntegrityError)<\/span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">test_author_no_email<\/span><span class=\"hljs-params\">(self, db_session)<\/span>:<\/span>\n        author = Author(\n            firstname=<span class=\"hljs-string\">\"James\"<\/span>,\n            lastname=<span class=\"hljs-string\">\"Clear\"<\/span>\n        )\n        db_session.add(author)\n        <span class=\"hljs-keyword\">try<\/span>:\n            db_session.commit()\n        <span class=\"hljs-keyword\">except<\/span> IntegrityError:\n            db_session.rollback()\n   \n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">test_article_valid<\/span><span class=\"hljs-params\">(self, db_session, valid_author)<\/span>:<\/span>\n        valid_article = Article(\n            slug=<span class=\"hljs-string\">\"sample-slug\"<\/span>,\n            title=<span class=\"hljs-string\">\"Title of the Valid Article\"<\/span>,\n            content=<span class=\"hljs-string\">\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"<\/span>,\n            author=valid_author\n            )\n        db_session.add(valid_article)\n        db_session.commit()\n        sample_article = db_session.query(Article).filter_by(slug=<span class=\"hljs-string\">\"sample-slug\"<\/span>).first()\n        <span class=\"hljs-keyword\">assert<\/span> sample_article.title == <span class=\"hljs-string\">\"Title of the Valid Article\"<\/span>\n        <span class=\"hljs-keyword\">assert<\/span> len(sample_article.content.split(<span class=\"hljs-string\">\" \"<\/span>)) &gt; <span class=\"hljs-number\">50<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>As you can see, db_session and valid_author are passed into each test function as arguments. The <code>db_session <\/code>fixture replaces each <code>self.session<\/code>, and each <code>self.valid_author<\/code> is now replaced by the <code>valid_author<\/code> fixture.<\/p>\n\n\n\n<p>Verify the tests in the sandbox below by running <code>pytest.main(['-v'])<\/code> in the console:<\/p>\n\n\n<div\n\tclass=\"sandbox-embed responsive-embed  sandbox-embed--full-width\"\n\tstyle=\"padding-top: 125%\"\ndata-block-name=\"coderpad-sandbox-embed\">\n\t<iframe src=\"https:\/\/embed.coderpad.io\/sandbox?question_id=234637&#038;use_question_button\" width=\"640\" height=\"800\" loading=\"lazy\" aria-label=\"Try out the CoderPad sandbox\"><\/iframe>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Wrapping up<\/strong><\/h2>\n\n\n\n<p>This tutorial has covered how to do unit testing for a transactional database in SQLAlchemy using pytest. You started with creating the database models and then went through the classic way of using pytest fixtures. Lastly, you learned how to use fixtures and why they are useful for writing efficient tests.<\/p>\n\n\n\n<p><em>I\u2019m Ezz. I\u2019m an AWS Certified Machine Learning Specialist and a Data Platform Engineer. I help SaaS companies rank on Google. Check out my&nbsp;<a href=\"https:\/\/ezzeddinabdullah.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">website<\/a>&nbsp;for more.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Testing is a decisive phase in your systems development lifecycle. This is an important step to make your software reliable and maintainable in the future. In this tutorial, you will have a practical guide to unit testing your database with pytest, one type of software testing by which individual units of your code are tested to determine if they are fit for use.<\/p>\n","protected":false},"author":1,"featured_media":23423,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[],"persona":[29],"blog-programming-language":[67,37],"keyword-cluster":[],"class_list":["post-23406","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development"],"acf":[],"_links":{"self":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts\/23406","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/comments?post=23406"}],"version-history":[{"count":23,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts\/23406\/revisions"}],"predecessor-version":[{"id":32633,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts\/23406\/revisions\/32633"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/media\/23423"}],"wp:attachment":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/media?parent=23406"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/categories?post=23406"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/tags?post=23406"},{"taxonomy":"persona","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/persona?post=23406"},{"taxonomy":"blog-programming-language","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/blog-programming-language?post=23406"},{"taxonomy":"keyword-cluster","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/keyword-cluster?post=23406"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}