简体   繁体   English

我将如何对这样的代码进行单元测试?

[英]How would I unit test code like this?

I have the following class and am currently testing it via running a file which calls the methods in this file. 我有以下课程,目前正在通过运行一个调用该文件中方法的文件进行测试。 I then use a mixture of print statements and checking the blog to make sure the code has worked. 然后,我将混合使用打印语句并检查博客,以确保代码有效。

I'd really like to write some pytest unit tests for this and automate it all, but how can I do this? 我真的很想为此编写一些pytest单元测试并将其全部自动化,但是我该怎么做呢? Also, if authentication doesn't exist or had become invalid it opens the browser and prompts the user to input the auth code. 另外,如果身份验证不存在或变为无效,它将打开浏览器并提示用户输入身份验证代码。 Later on this will be handled by a gui form. 稍后将通过gui表单处理。 Pytest doesn't take user input, and rightly so; Pytest不会接受用户输入,这是正确的。 it wouldn't be automated. 它不会是自动化的。

class BloggerInterface(object):
    """Connects to blogger api and authorises client."""

    def get_credentials(self):
        """Gets google api credentials, or generates new credentials
        if they don't exist or are invalid."""
        scope = 'https://www.googleapis.com/auth/blogger'

        flow = oauth2client.client.flow_from_clientsecrets(
                'client_secret.json', scope,
                redirect_uri='urn:ietf:wg:oauth:2.0:oob')

        storage = oauth2client.file.Storage('credentials.dat')
        credentials = storage.get()

        if not credentials or credentials.invalid:
            auth_uri = flow.step1_get_authorize_url()
            webbrowser.open(auth_uri)

            auth_code = input('Enter the auth code: ')
            credentials = flow.step2_exchange(auth_code)

            storage.put(credentials)

        return credentials

    def get_service(self):
        """Returns an authorised blogger api service."""
        credentials = self.get_credentials()
        http = httplib2.Http()
        http = credentials.authorize(http)
        service = apiclient.discovery.build('blogger', 'v3', http=http)

        return service

    def get_blog(self, blog_id):
        """Gets the details ofthe blog withthe id blog_id"""
        BlogDetails = collections.namedtuple('BlogDetails', 'blog_id, name, desc, url')

        conn = self.get_service()
        request = conn.blogs().get(blogId=blog_id, view='ADMIN')
        response = request.execute()

        name = response.get('name')
        desc = response.get('description')
        url = response.get('url')

        blog = BlogDetails(blog_id=blog_id, name=name, desc=desc, url=url)

        return blog

    def get_posts(self, blog_id, status='live'):
        """Gets all posts from the blog with the id blog_id"""
        posts = []

        conn = self.get_service()
        request = conn.posts().list(blogId=blog_id, view='ADMIN',
        status=status)

        #Responses are paginated, so a paging loop is required.
        while request:

            response = request.execute()

            for post in response.get('items', []):
                post_id = post.get('id')
                title = post.get('title')
                url = post.get('url')
                status = post.get('status')
                content = post.get('content')

                posts.append({'post_id':post_id, 'title':title, 'url':url,
                    'status':status, 'content':content})

            request = conn.posts().list_next(request, response)

        return posts

    def add_post(self, blog_id, post, is_draft=True):
        """Adds a new post to the blog with the id blog_id"""
        conn = self.get_service()

        #post is in the form {title, content, (labels), author_name, author_id.
        title, content, author_name, author_id, labels = post

        data = {
                'kind': 'blogger#post',
                'title': title,
                'content': content,
                'labels': labels,
                'author': {'displayName':author_name, 'id':author_id}
                }

        request = conn.posts().insert(blogId=blog_id, body=data,
                isDraft=is_draft)
        response = request.execute()
        post_id = response.get('id')

        return post_id

Don't test the oauth2client or webbrowser projects. 不要测试oauth2clientwebbrowser项目。 Test how your code reacts to input and output from other parts. 测试您的代码如何响应其他部分的输入和输出。 Those are black boxes, which you replace with your own mocks, so you can see how your code responds to different return values. 这些是黑盒,您可以用自己的模拟代替它们,以便您可以查看代码如何响应不同的返回值。

Use the unittest.mock module to produce the mocks. 使用unittest.mock模块生成模拟 If you are using Python < 3.3, install the backport mock project to do so. 如果您使用的是Python <3.3,请安装backport mock项目

For example, for BloggerInterface.get_credentials() , you mock out oauth2client.client.flow_from_clientsecrets() , oauth2client.file.Storage() , webbrowser.open() and input . 例如,对于BloggerInterface.get_credentials() ,您可以模拟oauth2client.client.flow_from_clientsecrets()oauth2client.file.Storage()webbrowser.open()input You can then play with the response from storage.get() to force your code to use webbrowser.open() , and test if your code correctly tried to open a webbrowser, and then called storage.put() to store the credentials: 然后,您可以使用storage.get()的响应来强制您的代码使用webbrowser.open() ,并测试您的代码是否正确尝试打开了一个Webbrowser,然后调用storage.put()来存储凭据:

with mock.patch('oauth2client.client.flow_from_clientsecrets') as mockflow, \
        mock.patch('oauth2client.file.Storage') as MockStorage, \
        mock.patch('webbrowser.open') as mockwbopen, \
        mock.patch('yourmodule.input') as mockinput:
    # set the credentials to invalid
    storage = MockStorage.return_value
    credentials = storage.get.return_value
    credentials.invalid = True

    # run the method and see if we get what we want
    result = BloggerInterface().get_credentials()

    # check that the flow was initialised correctly
    mockflow.assert_called_with(
        'client_secret.json', 'https://www.googleapis.com/auth/blogger',
        redirect_uri='urn:ietf:wg:oauth:2.0:oob')
    MockStorage.assert_called_with('credentials.dat')

    # With invalid credentials, the code should obtain a auth url from the
    # flow, pass it to the browser. Then the authentication code should be taken
    # from input and passed back to the flow for exchange. Test these
    # interactions took place:
    flow.step1_get_authorize_url.assert_called_once_with()
    mockwbopen.assert_called_once_with(flow.step1_get_authorize_url.return_value)
    flow.step2_exchange.assert_called_once_with(mockinput.return_value)
    storage.put(flow.step2_exchange.return_value)
    assert result == flow.step2_exchange.return_value

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM