Solution Development in Python, Part 2

Continuing on frompart 1, we now have a nice little package that we wrote. Let’s refine this package to be a little more in line with Python practices, add some tests (well, a
test), and provide some console execution.

You’re probably not going to just have one file in your solution and even if you did, that file would not be __init__.py
, in terms of where you store all your logic. That was enough to get us started but we can do better than that.

Let’s add a file called saying.py
. This file should go in the inner
proverb
directory.

Take the logic that was in __init__.py
and move it to this new file, so it looks like this:

def saying():
    return (
        u'Let not mercy and truth forsake thee:n'
        u'Bind them about thy neck;n'
        u'write them upon the table of thine heart.n'
        u'n'
        u'Proverbs 3:3'
    )

This leaves the init file temporarily empty. Very temporary as it turns out since you can now add this to your __init__.py
file:

from .saying import saying

Here I’m just making the saying() function available on the module.

(Re)Install It

If you wanted to install your package again to see if it works, you can try this:

pip install .

This is the same thing we did last time. However, you are likely to encounter a message saying something like: “Requirement already satisfied (use –upgrade to upgrade).” What this is saying is that some package called “proverb” at version “0.1” (assuming you used the same version I did) is already in place. It’s basically telling you what to do:

pip install . --upgrade

You could also, of course, change the version and, in fact, that is what you are likely to do when you make changes like we did. Logically, we haven’t changed how our package works so it would run the same way as before:

>>> import proverb
>>> print(proverb.saying())

Add a Dependency

Let’s add another package that our package will depend on. If you’re writing a test solution, you will likely be relying on other packages. So let’s see how this is done. The common contrivance in these kind of simple tutorials is to use a formatting language. So let’s use Markdown.

Specifically, our package will depend on the markdown package. We have to add this dependency into our setup.py
file.

setup(
    name='proverb',
    version='0.1',
    description='Statement to Live By',
    long_description=readme(),
    classifiers=[
        'Development Status :: 3 - Alpha',
        'License :: OSI Approved :: MIT License',
        'Programming Language :: Python :: 3',
        'Topic :: Text Processing :: Linguistic',
      ],
    keywords='bible proverbs',
    url='http://github.com/testerstories/proverb',
    author='Jeff Nyman',
    author_email=jeff@gmail.com',
    license='MIT',
    packages=['proverb'],
    install_requires=['markdown'],
    include_package_data=True,
    zip_safe=False
)

Note that if you were writing a test solution for automating browsers, this is how you would include the selenium package.

Now let’s change our saying.py
file to import this package and then use it. Change the code in that file as such:

from markdown import markdown
 
def saying():
    return markdown (
        u'Let not _mercy_ and _truth_ forsake thee:n'
        u'Bind them about thy neck;n'
        u'write them upon the table of thine heart.n'
        u'n'
        u'**Proverbs 3:3**'
    )

Notice that the return statement now calls markdown(). Further, some lines of the saying have markdown characters. You obviously won’t see this formatting if running from certain command lines but you will see the tags that were generated from the markdown. The goal here, however, was just to show you how to incorporate another package into your own.

This notion of dependencies does bring up something you’ll hear about and see quite often regarding Python packages: a requirements.txt file.

setup.py or requirements.txt?

There can be confusion regarding these two files.

What’s important to know is that you’ll have what are called “abstract dependencies” in your setup.py file and you’ll have “concrete dependencies” in your requirements.txt file.

The idea is that a dependency is “abstract” when it’s stated only as a name, possibly with an optional version specifier attached. A dependency is “concrete” when it also specifies where the dependency should be gathered from or what specific version should be used.

The main thing to really understand is that a pip install
command, which someone will likely use to install your package, does not look at the requirements.txt file by default; instead it only looks at an “install_requires” section in setup.py. However, where you will see requirements.txt files become handy is if someone is using your package in a virtual environment or if they want to contribute to developing your application.

Some people like to have both files, however, and I personally think this makes sense. The nice thing is that you can maintain your dependencies in requirements.txt and then have setup.py use that file. You can also just have pip use the file, like this:

pip install -r requirements.txt

But, again, why? Why have two files? The core problem is that pip has no dependency resolver
. So pip, at the time I write this, uses the first specification (and thus version) that it finds for a project package. Requirements files are used to force pip to properly resolve dependencies.

I won’t go into that too much here. For now, just be aware of this.

Command Line Scripts

If you provide test solutions, you might provide a script that allows someone to use your solution as a test runner. This means your users need a way to run that script from the command line.

There is one approach where you create a bin
directory, put a Python script file in there, and then use a “scripts” keyword in your setup.py
file. I’m not going to do that. Instead I’m going to take another approach, which involves using what’s called an entry point.

In this case, the entry point is referred to as “console_scripts”. This allows Python functions to be directly registered as command-line accessible tools.

Note that it’s a Python function
that is registered as a command line statement to be executed. If you instead wanted, say, a bash shell script, then you would have to go the “scripts” route with a “bin” directory.

In your inner
proverb
directory, create a file called command_line.py
and put the following in it:

import proverb
 
def main():
    print(proverb.saying())

You can test the “script” by running it directly:

>>> import proverb.command_line
>>> proverb.command_line.main()

Now the main() function can then be registered in setup.py. Make the following change to that file:

setup(
    name='proverb',
    version='0.1',
    description='Statement to Live By',
    long_description=readme(),
    classifiers=[
        'Development Status :: 3 - Alpha',
        'License :: OSI Approved :: MIT License',
        'Programming Language :: Python :: 3',
        'Topic :: Text Processing :: Linguistic',
      ],
    keywords='bible proverbs',
    url='http://github.com/testerstories/proverb',
    author='Jeff Nyman',
    author_email=jeff@gmail.com',
    license='MIT',
    packages=['proverb'],
    install_requires=['markdown'],
    entry_points = {
        'console_scripts': ['proverb-saying=proverb.command_line:main'],
    },
    include_package_data=True,
    zip_safe=False
)

Once the package has been installed with this modification in place, you will now have a command line executable available to you called “proverb-saying” which you can run directly at the command line. In Windows, this will be an executable called “proverb-saying.exe”.

Do note this does require that the location where your Python scripts are stored is part of the path. This largely depends on how you installed Python and what particular Python distribution you installed.

Testing

I most definitely am not going to go into every nuance of unit testing your solutions with Python; at least not in this series. But I do want to include at least one test just to show you how to do it.

These tests should generally be placed in a submodule of your project. The reason for this is that this approach means your tests can be imported, but they won’t pollute the global namespace. So here’s an example of where you can create a tests
directory:

proverb
    tests
        __init__.py
    proverb
	  __init__.py
             command_line.py
             saying.py
setup.py

Notice you have an __init__.py
to mark the directory as a module. In this case, that file can be empty. Now create a file called test_saying.py
and put the following in it:

from unittest import TestCase
 
import proverb
 
class TestSaying(TestCase):
    def test_is_string(self):
        s = proverb.saying()
        self.assertTrue(isinstance(s, str))

The Python testing ecosystem is large so I’m not going to cover much about it here. What I will say is that I’ll useNose for the test runner. Let’s install it:

pip install nose

Now you can run it from the directory of your project:

nosetests

You should see that the single test passes. That’s great but let’s make sure we can run our tests via our setup.py as well as make Nose a dependency. Modify setup.py as follows:

setup(
    name='proverb',
    version='0.1',
    description='Statement to Live By',
    long_description=readme(),
    classifiers=[
        'Development Status :: 3 - Alpha',
        'License :: OSI Approved :: MIT License',
        'Programming Language :: Python :: 3',
        'Topic :: Text Processing :: Linguistic',
      ],
    keywords='bible proverbs',
    url='http://github.com/testerstories/proverb',
    author='Jeff Nyman',
    author_email='jeffnyman@gmail.com',
    license='MIT',
    packages=['proverb'],
    install_requires=['markdown'],
    tests_require=['nose'],
    test_suite='nose.collector',
    entry_points = {
        'console_scripts': ['proverb-saying=proverb.command_line:main'],
    },
    include_package_data=True,
    zip_safe=False
)

Then, to run tests, you can do the following:

python setup.py test

And that should cover us for now. In this post, we’ve updated our project to use a dependency, to provide a command-line script, and to incorporate some tests. This is important stuff when you are creating a test solution in Python. So here I’ve shown you the simplest way to get started with these and (hopefully) gain some confidence.

In the next, and last, part of this series I’ll cover packaging up your solution and deploying it.

责编内容来自:Stories from a Software Tester (源链) | 更多关于

阅读提示:酷辣虫无法对本内容的真实性提供任何保证,请自行验证并承担相关的风险与后果!
本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 综合编程 » Solution Development in Python, Part 2

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录