Testing

Pytest vs. Unittest: Which Is Better?

Python, being a versatile and widely used programming language, offers several testing frameworks to facilitate the testing process. Two prominent choices are pytest and unittest, both of which come with their own sets of features and advantages.

In this article, we’ll be covering the following sections:

  • Introduction to each Python testing framework.
  • Syntax and coding examples to give you a starting point.
  • Advantages and disadvantages.
  • A thorough comparison between the two testing frameworks.
  • And finally, all the features that PyCharm provides for pytest and unittest.

This will help you determine which testing framework is the best for your needs.

Let’s get started.

Python Testing Frameworks

Python offers a variety of testing frameworks that cater to different needs and preferences. These frameworks facilitate the process of writing, organizing, and executing tests, ensuring the reliability and correctness of your code.

Two of the most popular Python testing frameworks are pytest and unittest. 

What is pytest?

Pytest is a popular and powerful testing framework for Python that simplifies the process of writing and executing tests. It is known for its simplicity, scalability, and ability to handle complex test scenarios while significantly reducing the boilerplate code required for testing.

In recent years, pytest has become one of the most popular choices for Python testing.

Features of pytest

  1. Fixture support: pytest provides a powerful fixture mechanism for setting up and tearing down resources needed for testing. This enhances test organization and readability.
  2. Parameterization: pytest allows easy parameterization of test functions, enabling the testing of multiple inputs without duplicating code. This enhances test coverage and maintains clean test code.
  3. Rich plugin architecture: pytest boasts a rich set of features and a vibrant plugin ecosystem. This extensibility allows customization and integration with other tools, making it adaptable to diverse testing needs.
  4. Concise syntax: The syntax for writing tests in pytest is concise and readable, making it easy for developers to adopt. This simplicity is conducive to rapid test development and maintenance.

How to write tests with pytest

Before writing tests with pytest, make sure you have it installed first. 

You can use Python Packages to install pytest in PyCharm:

install pytest in PyCharm

Or you can also open your PyCharm terminal and write: 

pip install pytest

Now, let’s take a simple example in which you’re creating an automated quiz.

In this program, we ask the user three questions and then return the number of answers they got correct. You can clone this exact project from this git repository.

# File name ‘quiz.py’
class Question:
  def __init__(self, prompt, answer):
      self.prompt = prompt
      self.answer = answer

# Define the list of questions
questions = [
  Question("Which is the biggest continent in the world?\n(a) Antarctica\n(b) Australia\n(c) Asia\n\n", "c"),
  Question("Which is the most spoken language in the world?\n(a) English\n(b) Spanish\n(c) French\n\n", "a"),
  Question("Who wrote 'Romeo and Juliet'?\n(a) Charles Dickens\n(b) William Shakespeare\n(c) J.K. Rowling\n\n", "b")
]

def run_quiz(questions):
  score = 0
  for question in questions:
      answer = input(question.prompt)
      if answer not in ['a', 'b', 'c']:
          raise TypeError('Input must be either a, b, c')
      if answer.lower() == question.answer:
          score += 1
  return f"You got {score} out of {len(questions)} correct."


if __name__ == '__main__':
  print(run_quiz(questions))

We’ll be using this same example to create both pytest and unittest tests so you can see the difference in how they are structured.

Here’s how to write the test in pytest:

# File name 'test_pytestexample.py'
import pytest
from unittest import mock
from quiz import run_quiz, questions
import builtins


def test_run_quiz_with_no_answers():
  # Mock user input to simulate no answers provided
  input_values = iter(['', '', ''])
  def mock_input(s):
      return next(input_values)

  with pytest.raises(TypeError):
      with mock.patch.object(builtins, 'input', mock_input):
          assert run_quiz(questions) == "You got 0 out of 3 correct."


def test_run_quiz_with_correct_answers():
  # Mock user input to simulate correct answers provided
  input_values = iter(['c', 'a', 'b'])
  def mock_input(s):
      return next(input_values)

  with mock.patch.object(builtins, 'input', mock_input):
      assert run_quiz(questions) == "You got 3 out of 3 correct."


def test_run_quiz_with_incorrect_answers():
  # Mock user input to simulate wrong answers provided
  input_values = iter(['b', 'c', 'a'])
  def mock_input(s):
      return next(input_values)

  with mock.patch.object(builtins, 'input', mock_input):
      assert run_quiz(questions) == "You got 0 out of 3 correct."


def test_run_quiz_with_incorrect_input():
  # Mock user input to simulate the wrong type
  input_values = iter(['y', 'x', 'z'])

  def mock_input(s):
      return next(input_values)

  with pytest.raises(TypeError):
      with mock.patch.object(builtins, 'input', mock_input):
          run_quiz(questions)

# To invoke the pytest framework and run all tests
if __name__ == "__main__":
  pytest.main()

Overall, we have four test functions that test different cases. We are using mocking here to simulate different values a user can enter.

One important thing to note is that all test functions need to start with ‘test’ in pytest. If that’s not the case, then the pytest framework will fail to recognize it as a test function and won’t run it.

You can run each test separately or all of them together. 

Here’s the result:

Advantages and disadvantages of pytest

In this section, we will delve into the advantages of pytest, highlighting its strengths in areas apart from concise syntax, powerful fixtures, parameterization, and rich plugin support. Additionally, we’ll explore the potential drawbacks, acknowledging factors like a moderate learning curve for beginners, integration considerations, and nuances related to test discovery.

Advantages of pytest

1. Test discovery: pytest features automatic test discovery, eliminating the need for explicit configuration. This simplifies the testing process by identifying and executing test cases without manual intervention.

2. Skip/xfail tests: pytest provides the ability to skip or mark tests as expected failures using markers (@pytest.mark.skip and @pytest.mark.xfail). This flexibility is useful for excluding certain tests under specific conditions or marking tests that are known to fail.

3. Powerful assertion introspection: pytest provides detailed information on test failures, making it easier to diagnose and fix issues. The output includes clear and informative messages that help developers identify the exact cause of a failure.

4. Parallel test execution: pytest supports parallel test execution, allowing multiple tests to run concurrently. This feature can significantly reduce the overall test execution time, making it suitable for projects with large test suites.

5. Mocking and patching: pytest simplifies mocking and patching with built-in fixtures and libraries. This makes it easy to isolate components during testing and replace dependencies with controlled mocks.

6. Community support: pytest has a large and active community, leading to continuous development, regular updates, and a wealth of community-contributed plugins and resources. This vibrant community ensures that developers have access to extensive documentation and support.

7. Test isolation: pytest lets you run tests independently of each other, ensuring that the outcome of one test does not affect the outcome of another. This promotes cleaner, more robust, and maintainable test suites by ensuring that tests are reliable and easy to understand and debug.

These advantages collectively contribute to pytest’s popularity and effectiveness in various testing scenarios. Its flexibility, readability, and extensive feature set make it a valuable choice for both beginners and experienced developers alike.

While pytest is a powerful and widely adopted Python testing framework, like any tool, it has some potential disadvantages that users may encounter. It’s essential to consider these drawbacks when deciding whether pytest is the right choice for a particular project. 

Here are some potential disadvantages of pytest.

Disadvantages of pytest

1. Not in the Python Standard Library: Unlike unittest, which is part of the Python Standard Library, pytest is a third-party library. This means that for projects relying heavily on the Python Standard Library, there might be an additional step in installing and managing pytest.

2. Learning curve for beginners: For new developers, there might be a learning curve, especially if they are accustomed to other testing frameworks or if they are new to testing in general. Understanding and leveraging advanced features may take some time.

3. Integration with certain IDEs: While pytest integrates well with many IDEs, there can be occasional challenges in setting up the integration with some. Although this is not a widespread issue, it may require additional configuration in some cases.

4. Parallel test execution configuration: While pytest supports parallel test execution, configuring it for optimal performance might require additional setup and consideration. Developers need to understand and configure parallel execution settings correctly to avoid unexpected behaviors.

5. Slower test execution in some cases: In certain scenarios, pytest may exhibit slightly slower test execution compared to some other testing frameworks. This can be attributed to the additional features and flexibility provided by pytest, which may introduce some overhead.

6. Limited built-in fixtures for unittest-style setup/teardown: For developers accustomed to unittest-style setup and teardown using the setUp and tearDown methods, pytest might seem different. pytest encourages the use of fixtures, and while powerful, it might feel less intuitive for those transitioning from unittest.

7. Strictness in test discovery: pytest’s test discovery can be less strict compared to unittest in some cases. While this flexibility is an advantage for many, it might lead to unintended consequences if not carefully managed, especially in large and complex projects.

It’s important to note that many of these disadvantages are context-dependent, and what might be a drawback in one scenario could be an advantage in another.

Understanding the specific needs of a project and the preferences of the development team is crucial when evaluating whether pytest is the right testing framework for a given situation.

What Is unittest?

Unittest is a built-in testing framework in Python that follows the xUnit style and is inspired by Java’s JUnit. Since it’s part of the Python Standard Library, unittest provides a solid foundation for writing and organizing tests.

Features of unittest

  1. Test discovery: unittest automatically discovers and runs test cases without the need for explicit configuration. This simplifies test management and ensures all relevant tests are executed.
  2. Fixture support: unittest supports setup and teardown methods for test fixtures. While not as flexible as pytest’s fixtures, unittest’s fixtures provide the necessary functionality for resource management.
  3. Assertions: unittest comes with a variety of assertion methods for verifying expected outcomes. This includes methods like ‘assertEqual’, ‘assertTrue’, and more, ensuring comprehensive testing.
  4. Test suites: unittest allows grouping tests into test suites for better organization. This is beneficial for managing large codebases with numerous test cases.

How to write tests with unittest

We’ll be using the same quiz program example as before. This time, let’s test it with unittest.

# File name 'test_unittestexample.py'
import unittest
from unittest import mock
import builtins
from quiz import run_quiz, questions

#Define test class that's derived from unittest.TestCase
class Test(unittest.TestCase):
  def test_run_quiz_with_no_answers(self):
      # Simulate no answers provided by the user
      input_values = iter(['', '', ''])

      def mock_input(s):
          return next(input_values)

      with mock.patch.object(builtins, 'input', mock_input):
          self.assertRaises(TypeError, run_quiz, questions)


  def test_run_quiz_with_all_correct_answers(self):
      # Simulate correct answers provided by the user
      input_values = iter(['c', 'a', 'b'])

      def mock_input(s):
          return next(input_values)

      with mock.patch.object(builtins, 'input', mock_input):
          output = run_quiz(questions)
          self.assertEqual(output, "You got 3 out of 3 correct.")

  def test_run_quiz_with_all_incorrect_answers(self):
      # Simulate all incorrect answers provided by the user
      input_values = iter(['b', 'c', 'a'])

      def mock_input(s):
          return next(input_values)

      with mock.patch.object(builtins, 'input', mock_input):
          output = run_quiz(questions)
          self.assertEqual(output, "You got 0 out of 3 correct.")

#invoke the unittest framework and run tests
if __name__ == "__main__":
  unittest.main()

Notice that in unittest, we don’t directly define our test functions. You need to define a test class that will contain all your test functions. In this case, it’s  class Test (unittest.TestCase). All unittest test classes need to be derived from unittest.TestCase, as this is the only way that the test functions will be invoked by the unittest framework.

Just like pytest, test functions in unittest also needs to start with ‘test’ in order for the framework to recognize and run it.

Here’s how to run the unittests individually or altogether in PyCharm:

Here’s the result:

Notice the difference between how unittest displays the test result vs how pytest does it. As you can see, unittest’s output is more summarized, whereas pytest displays test results for every individual test. 

Advantages and disadvantages of unittest

Unittest, the built-in testing framework in Python, offers a range of advantages that make it a solid choice for many developers and projects.

Here are some key advantages of unittest:

Advantages of unittest

  1. Community and industry standard: As a part of the xUnit family, unittest adheres to industry standards for unit testing frameworks. Developers familiar with xUnit-style frameworks in other languages will find unittest conventions familiar and consistent.
  2. Part of the Python Standard Library: unittest comes bundled with the Python Standard Library, ensuring its availability without the need for additional installations. This standardization promotes consistency across Python projects.
  3. Widely adopted and well-documented: As part of the Python Standard Library, unittest is widely adopted and well-documented. This makes it a familiar choice for developers transitioning from other languages with xUnit-style testing frameworks.
  4. Integration with IDEs: unittest integrates seamlessly with many integrated development environments (IDEs), providing features like test discovery, execution, and result visualization. This integration streamlines the testing workflow for developers using these tools.
  5. Standardized test output: unittest produces standardized test output, making it easy to integrate with other tools or continuous integration systems. The consistent output format enhances interoperability and ease of use.
  6. Consistent test isolation: unittest ensures consistent test isolation by creating a new instance of the test class for each test method. This prevents unintended side effects between different test cases.

While unittest may not have some of the advanced features found in third-party testing frameworks like pytest, its simplicity, standardization, and widespread adoption make it a reliable choice for many Python projects, particularly those where compatibility with the Python Standard Library is a priority.

However, there are some potential disadvantages to it, as well. It’s important to consider these limitations when deciding whether unittest is the most suitable choice for a particular project.

Disadvantages of unittest

1. Verbose syntax: One of the commonly mentioned criticisms of unittest is its verbose syntax. Compared to some other testing frameworks like pytest, writing test cases with unittest can require more lines of code, leading to a potential increase in boilerplate.

2. Limited parameterization support: unittest does not have native support for parameterized tests. 

3. Discovery requires rest prefix: By default, unittest requires test methods to be named with the prefix “test” for automatic discovery. While this convention is widely adopted, it can be restrictive if you want more flexibility in naming your test methods.

4. Learning curve for advanced features: While the basics of unittest are relatively easy to grasp, some of the more advanced features, such as creating custom test loaders or test runners, may have a more challenging learning curve.

5. Limited support for parallel test execution: unittest has limited built-in support for parallel test execution. Achieving parallelization may require additional effort and the use of external tools or libraries.

Despite these disadvantages, unittest remains a robust and reliable testing framework, particularly for projects that prioritize compatibility with the Python Standard Library and maintainability over advanced features provided by third-party frameworks.

Differences between pytest and unittest

The following table provides an overview of some key features and characteristics of both pytest and unittest.

Featurepytestunittest
Fixture supportYesNo built-in fixture support
Plugin architectureRich set of plugins availableLimited compared to pytest
Test discoveryAutomaticAutomatic (but less flexible)
Speed of executionFaster – Runs tests concurrently and has strong test parallelization capabilitySlower – Runs tests sequentially by default
AssertionsBuilt-in assertion methods (assert, assert … == …)Built-in assertion methods (assertEqual, assertTrue, etc.)
Fixture scopeFlexible (function, module, session)Limited (setUp and tearDown per test)
Marking testsYesLimited
Parameterized testsYes (via fixtures or @pytest.mark.parametrize)No (requires custom implementation)
InstallationThird-party package, needs to be installed separatelyPart of the Python Standard Library
Test output captureYes (detailed and customizable)Yes (limited customization)
Parallel test executionYesLimited support (requires custom setup)
Mocking and patchingCompatible with unittest.mock and pytest-mockYes, using unittest.mock
Community supportLarge and activeStandard (part of the Python Standard Library)
Documentation qualityWell-documented and extensiveWell-documented
Learning curveModerateEasy

Keep in mind that the suitability of a testing framework often depends on specific project requirements and team preferences.

Each framework has its strengths, and the choice between pytest and unittest should align with the testing needs and development philosophy of your project.

That said, for new projects with modern Python, pytest has become the default choice.

Pytest and unittest with PyCharm

Pytest in PyCharm

1. Intelligent code completion

PyCharm enhances the pytest experience with its intelligent code completion feature. As you write your test cases, PyCharm provides context-aware suggestions, making it easier to explore available fixtures, functions, and other elements related to pytest. This not only improves development speed but also helps you avoid typos and potential errors.

2. Test discovery and navigation

One of the key features of PyCharm is its robust support for test discovery. PyCharm automatically identifies and lists all your pytest tests, making it effortless to navigate through your test suite. You can quickly jump to the test definition, explore test fixtures, and view test outcomes directly from the editor. This streamlined workflow is particularly beneficial in large projects with extensive test suites. PyCharm also makes it very easy to run just one test, one file, or one directory, without knowing command line flags.

3. Integrated debugging

PyCharm provides seamless integration with pytest’s debugging capabilities. You can set breakpoints within your test code, run tests in debug mode, and step through the code to identify issues. The debugger interface in PyCharm offers a visual representation of the call stack, variable values, and breakpoints, facilitating efficient debugging and issue resolution.

4. Test result visualization

PyCharm presents test results in a clear and visually appealing manner. After running your pytest suite, you can easily navigate through the results, identify passed and failed tests, and view detailed information about each test case. This visual feedback helps developers quickly assess the health of their codebase and address any failures promptly.

5. Integration with version control systems

PyCharm seamlessly integrates with version control systems, ensuring that changes to both code and pytest tests are tracked consistently. This integration facilitates collaboration within development teams and helps maintain a unified versioning approach.

Unittest in PyCharm

1. Test discovery and execution

PyCharm provides robust support for discovering and executing unittest test cases. The IDE automatically identifies and lists all unittest tests in your project, simplifying test management. With just a few clicks, you can run individual test cases, entire test modules, or the entire test suite, ensuring a smooth and efficient testing process.

2. Code navigation and refactoring

PyCharm enhances the navigation and refactoring capabilities for unittest. Developers can easily navigate between test cases and corresponding code, making it straightforward to understand the relationship between tests and the tested code. Additionally, PyCharm supports various refactoring operations, allowing developers to modify their codebase confidently while maintaining the integrity of their test suites.

3. Code coverage analysis

PyCharm features a code coverage analysis tool that helps developers assess the extent to which their code is covered by unittests. This feature is valuable for identifying areas that may require additional testing, ensuring comprehensive test coverage.

4. Test configuration

PyCharm simplifies the configuration of unittest by providing an intuitive interface for adjusting testing parameters. Developers can customize test run configurations, specify test environments, and control other testing-related settings. This flexibility ensures that developers can adapt unittest to meet the specific requirements of their projects.

By using PyCharm, developers can seamlessly integrate both pytest and unittest into their workflow. PyCharm testing provides features like test discovery, execution, and result visualization for both frameworks.

How to choose between pytest vs. unittest?

Choosing between pytest and unittest depends on various factors, including project requirements, team preferences, and the complexity of testing scenarios. pytest is favored for its concise syntax, rich feature set, and flexibility, making it suitable for projects with diverse testing needs. 

On the other hand, unittest, being part of the standard library, provides simplicity and a consistent testing structure, making it a solid choice for projects that prioritize standardization and simplicity.

Ultimately, both pytest and unittest are capable testing frameworks, and the choice between them should align with your specific needs and development philosophy. Remember that the right testing framework can significantly contribute to the success of your project by increasing code quality and reliability.

image description