在Django中测试“不同层”的最佳实践是什么?

时间:2021-01-13 03:25:27

I'm NOT new to testing, but got really confused with the mess of recommendations for testing different layers in Django.

我对测试并不陌生,但对Django中测试不同层的各种建议感到非常困惑。

Some recommend (and they are right) to avoid Doctests in the model as they are not maintainable...

有些人建议(他们是对的)避免模型中的doctest,因为它们是不可维护的……

Others say don't use fixtures, as they are less flexible than helper functions, for instance..

另一些人说不要使用fixture,它们比helper函数更不灵活。

There are also two groups of people who fight for using Mock objects. The first group believe in using Mock and isolating the rest of the system, while another group prefer to Stop Mocking and start testing..

也有两组人为使用模拟对象而战。第一组相信使用Mock并隔离系统的其他部分,而另一组则喜欢停止Mock并开始测试。

All I have mentioned above, were mostly in regards to testing models. Functional testing is an another story (using test.Client() VS webTest VS etc. )

我上面提到的,大部分都是关于测试模型的。功能测试是另一个故事(使用test.Client() VS webTest VS .等等)

Is there ANY maintainable, extandible and proper way for testing different layers??

是否有可维护的、可扩展的和合适的方法来测试不同的层?

UPDATE

更新

I am aware of Carl Meyer's talk at PyCon 2012..

我知道Carl Meyer在PyCon 2012上的讲话。

2 个解决方案

#1


44  

UPDATE 08-07-2012

更新08-07-2012

I can tell you my practices for unit testing that are working pretty well for my own ends and I'll give you my reasons:

我可以告诉你们我的单元测试的实践我的目的很好,我会告诉你们我的理由:

1.- Use Fixtures only for information that is necessary for testing but is not going to change, for example, you need a user for every test you do so use a base fixture to create users.

1。-只对测试所必需的但不会改变的信息使用fixture,例如,在每次测试中都需要一个用户,所以要使用base fixture来创建用户。

2.- Use a factory to create your objects, I personally love FactoryBoy (this comes from FactoryGirl which is a ruby library). I create a separate file called factories.py for every app where I save all these objects. This way I keep off the test files all the objects I need which makes it a lot more readable and easy to maintain. The cool thing about this approach is that you create a base object that can be modified if you want to test something else based on some object from the factory. Also it doesn't depend on django so when I migrated these objects when I started using mongodb and needed to test them, everything was smooth. Now after reading about factories it's common to say "Why would I want to use fixtures then". Since these fixtures should never change all the extra goodies from factories are sort of useless and django supports fixtures very well out of the box.

2。-使用工厂来创建你的对象,我个人喜欢factory - boy(这来自一个ruby库factory - girl)。我创建了一个名为factory的单独文件。对于保存所有这些对象的每个应用。这样,我就不需要测试文件中的所有对象,从而使它更易于阅读和维护。这种方法很酷的一点是,如果您希望基于工厂的某个对象测试其他对象,那么您可以创建一个基对象,该对象可以进行修改。它也不依赖于django,所以当我开始使用mongodb并需要测试这些对象时,一切都很顺利。现在,在阅读了有关工厂的文章后,人们通常会说:“那我为什么要使用设备呢?”因为这些夹具不应该改变工厂提供的所有额外的好东西都是无用的,django非常支持开箱即用的夹具。

3.- I Mock calls to external services, because these calls make my tests very slow and they depend on things that have nothing to do with my code being right or wrong. for example, if I tweet within my test, I do test it to tweet rightly, copy the response and mock that object so it returns that exact response every time without doing the actual call. Also sometimes is good to test when things go wrong and mocking is great for that.

3所示。-我模拟对外部服务的调用,因为这些调用使我的测试非常缓慢,并且它们依赖于与我的代码是否正确或错误无关的东西。例如,如果我在我的测试中tweet,我确实会测试它正确地tweet,复制响应并模拟该对象,以便它每次都返回准确的响应,而不执行实际调用。有时候,当事情出错的时候测试一下是很好的,而嘲笑则是很好的方法。

4.- I use an integration server (jenkins is my recommendation here) which runs the tests every time I push to my staging server and if they fail it sends me an email. This is just great since it happens to me a lot that I break something else in my last change and I forgot to run the tests. It also gives you other goodies like a coverage report, pylint/jslint/pep8 verifications and there exists a lot of plugins where you can set different statistics.

4所示。-我使用一个集成服务器(jenkins是我的建议),每次我推到我的staging服务器时都运行测试,如果他们失败了,它会给我发邮件。这很好,因为我在上次的变更中有很多地方出错了,我忘记运行测试了。它还为您提供了其他好处,比如覆盖率报告、pylint/jslint/pep8验证,还有许多插件可以在其中设置不同的统计数据。

About your question for testing front end, django comes with some helper functions to handle this in a basic way.

关于测试前端的问题,django提供了一些帮助函数来以基本的方式处理这个问题。

This is what I personally use, you can fire gets, posts, login the user, etc. that's enough for me. I don't tend to use a complete front end testing engine like selenium since I feel it's an overkill to test anything else besides the business layer. I am sure some will differ and it always depends on what you are working on.

这是我个人使用的,你可以使用get、post、login等对我来说已经足够了。我不倾向于使用像selenium这样的完整前端测试引擎,因为我觉得除了业务层之外,测试其他任何东西都是多余的。我相信有些人会有所不同,这取决于你在做什么。

Besides my opinion, django 1.4 comes with a very handy integration for in-browser frameworks.

除了我的看法之外,django 1.4提供了一个非常方便的浏览器内框架集成。

I'll set an example app where I can apply this practices so it is more understandable. Let's create a very basic blog app:

我将设置一个示例应用程序,在其中我可以应用这些实践,以便更容易理解。让我们创建一个非常基本的博客应用:

structure

结构

blogger/
    __init__.py
    models.py
    fixtures/base.json
    factories.py
    tests.py

models.py

models.py

 from django.db import models

 class Blog(models.Model):
     user = models.ForeignKey(User)
     text = models.TextField()
     created_on = models.DateTimeField(default=datetime.now())

fixtures/base.json

设备/ base.json

[
{
    "pk": 1,
    "model": "auth.user",
    "fields": {
        "username": "fragilistic_test",
        "first_name": "demo",
        "last_name": "user",
        "is_active": true,
        "is_superuser": true,
        "is_staff": true,
        "last_login": "2011-08-16 15:59:56",
        "groups": [],
        "user_permissions": [],
        "password": "IAmCrypted!",
        "email": "test@email.com",
        "date_joined": "1923-08-16 13:26:03"
    }
}
]

factories.py

factories.py

import factory
from blog.models import User, Blog

class BlogFactory(factory.Factory):
    FACTORY_FOR = Blog

    user__id = 1
    text = "My test text blog of fun"

tests.py

tests.py

class BlogTest(TestCase):
    fixtures = ['base']  # loads fixture

    def setUp(self):
        self.blog = BlogFactory()
        self.blog2 = BlogFactory(text="Another test based on the last one")

    def test_blog_text(self):
        self.assertEqual(Blog.objects.filter(user__id=1).count(), 2)

    def test_post_blog(self):
        # Lets suppose we did some views
        self.client.login(username='user', password='IAmCrypted!')
        response = self.client.post('/blogs', {'text': "test text", user='1'})

        self.assertEqual(response.status, 200)
        self.assertEqual(Blog.objects.filter(text='test text').count(), 1)

    def test_mocker(self):
        # We will mock the datetime so the blog post was created on the date
        # we want it to
        mocker = Mock()
        co = mocker.replace('datetime.datetime')
        co.now()
        mocker.result(datetime.datetime(2012, 6, 12))

        with mocker:
            res = Blog.objects.create(user__id=1, text='test')

        self.assertEqual(res.created_on, datetime.datetime(2012, 6, 12))

    def tearDown(self):
        # Django takes care of this but to be strict I'll add it
        Blog.objects.all().delete()

Notice I am using some specific technology for the sake of the example (which haven't been tested btw).

请注意,我使用一些特定的技术是为了这个示例(还没有经过测试)。

I have to insist, this may not be the standard best practice (which I doubt it exists) but it is working pretty well for me.

我必须坚持,这可能不是标准的最佳实践(我怀疑它的存在),但它对我来说非常有效。

#2


20  

I really like the suggestions from @Hassek and want to stress out what an excellent point he makes about the obvious lack of standard practices, which holds true for many of Django's aspects, not just testing, since all of us approach the framework with different concerns in mind, also adding to that the great degree of flexibility we have with designing our applications, we often end up with drastically different solutions that are applicable to the same problem.

我真的喜欢@Hassek的建议,想强调出一个优秀的点他明显缺乏标准实践,它适用于Django的许多方面,不仅测试,因为我们所有的方法框架记住不同的关注点,也增加了我们伟大的程度的灵活性与设计应用程序时,我们经常会得到截然不同的解决方案,适用于同样的问题。

Having said that, though, most of us still strive for many of the same goals when testing our applications, mainly:

尽管如此,在测试我们的应用程序时,我们大多数人仍然在努力实现许多相同的目标,主要是:

  • Keeping our test modules neatly organized
  • 保持我们的测试模块整洁有序。
  • Creating reusable assertion and helper methods, helper functions that reduce the LOC for test methods, to make them more compact and readable
  • 创建可重用的断言和帮助器方法,帮助器函数减少测试方法的LOC,以使它们更紧凑和可读
  • Showing that there is an obvious, systematic approach to how the application components are tested
  • 显示了一种明显的、系统的方法来测试应用程序组件

Like @Hassek, these are my preferences that may directly conflict with the practices that you may be applying, but I feel it's nice to share the things we've proven that work, if only in our case.

就像@Hassek一样,这些是我的偏好,它们可能直接与您正在应用的实践相冲突,但是我觉得分享我们已经证明有效的东西是很好的,即使只是在我们的案例中。

No test case fixtures

Application fixtures work great, in cases you have certain constant model data you'd like to guarantee to be present in the database, say a collection of towns with their names and post office numbers.

应用程序装置工作得很好,如果您有一定的固定模型数据,您希望保证在数据库中显示,比如包含有它们的名称和邮局编号的城镇集合。

However, I see this as an inflexible solution for providing test case data. Test fixtures are very verbose, model mutations force you to either go through a lengthy process of reproducing the fixture data or to perform tedious manual changes and maintaining referential integrity is difficult to manually perform.

但是,我认为这是提供测试用例数据的一个灵活的解决方案。测试装置是非常冗长的,模型突变迫使您要么经历一个冗长的过程,重新生成fixture数据,要么执行冗长的手动更改,维护引用完整性是很难手动执行的。

Additionally, you'll most likely use many kinds of fixtures in your tests, not just for models: you'd like to store the response body from API requests, to create fixtures that target NoSQL database backends, to write have fixtures that are used to populate form data, etc.

此外,您很可能会在测试中使用许多类型的fixture,而不仅仅是模型:您希望从API请求中存储响应主体,创建目标为NoSQL数据库后端的fixture,编写用于填充表单数据的have fixture,等等。

In the end, utilizing APIs to create data is concise, readable and it makes it much easier to spot relations, so most of us resort to using factories for dynamically creating fixtures.

最后,使用api来创建数据是简洁的、可读的,并且可以更容易地发现关系,所以我们大多数人都使用工厂来动态地创建fixture。

Make extensive use of factories

Factory functions and methods are preferable to stomping out your test data. You can create helper factory module-level functions or test case methods that you may want to either reuse across application tests or throughout the whole project. Particularly, factory_boy, that @Hassek mentions, provides you with the ability to inherit/extend fixture data and do automatic sequencing, which might look a bit clumsy if you'd do it by hand otherwise.

工厂功能和方法比大量输出测试数据更可取。您可以创建帮助工厂模块级函数或测试用例方法,您可能希望在应用程序测试或整个项目中重用它们。特别地,@Hassek提到的factory_boy为您提供了继承/扩展fixture数据并进行自动排序的能力,如果不这样做的话,看起来可能有点笨拙。

The ultimate goal of utilizing factories is to cut down on code-duplication and streamline how you create test data. I cannot give you exact metrics, but I'm sure if you go through your test methods with a discerning eye you will notice that a large portion of your test code is mainly preparing the data that you'll need to drive your tests.

利用工厂的最终目标是减少代码重复和简化如何创建测试数据。我不能给你确切的度量标准,但是我确信如果你用一个有眼光的眼光来检查你的测试方法,你会注意到你的测试代码的很大一部分主要是准备你需要驱动你的测试的数据。

When this is done incorrectly, reading and maintaining tests becomes an exhausting activity. This tends to escalate when data mutations lead to not-so-obvious test failures across the board, at which point you'll not be able to apply systematic refactoring efforts.

当不正确地完成时,读取和维护测试将成为一项令人筋疲力尽的活动。当数据突变导致全面的不太明显的测试失败时,这种情况就会升级,这时您将无法应用系统的重构工作。

My personal approach to this problem is to start with a myproject.factory module that creates easy-to-access references to QuerySet.create methods for my models and also for any objects I might regularly use in most of my application tests:

我个人解决这个问题的方法是从myproject开始。创建易于访问的QuerySet引用的工厂模块。为我的模型创建方法,也为我在大多数应用程序测试中经常使用的对象创建方法:

from django.contrib.auth.models import User, AnonymousUser
from django.test import RequestFactory

from myproject.cars.models import Manufacturer, Car
from myproject.stores.models import Store


create_user = User.objects.create_user
    create_manufacturer = Manufacturer.objects.create
create_car = Car.objects.create
create_store = Store.objects.create

_factory = RequestFactory()


def get(path='/', data={}, user=AnonymousUser(), **extra):
    request = _factory.get(path, data, **extra)
    request.user = user

    return request


def post(path='/', data={}, user=AnonymousUser(), **extra):
    request = _factory.post(path, data, **extra)
    request.user = user

    return request

This in turn allows me to do something like this:

这反过来又允许我做这样的事情:

from myproject import factory as f  # Terse alias

# A verbose, albeit readable approach to creating instances
manufacturer = f.create_manufacturer(name='Foomobiles')
car1 = f.create_car(manufacturer=manufacturer, name='Foo')
car2 = f.create_car(manufacturer=manufacturer, name='Bar')

# Reduce the crud for creating some common objects
manufacturer = f.create_manufacturer(name='Foomobiles')
data = {name: 'Foo', manufacturer: manufacturer.id)
request = f.post(data=data)
view = CarCreateView()

response = view.post(request)

Most people are rigorous about reducing code duplication, but I actually intentionally introduce some whenever I feel it contributes to test comprehensiveness. Again, the goal with whichever approach you take to factories is to minimize the amount of brainfuck you introduce into the header of each test method.

大多数人对减少代码重复都很严格,但是我实际上是在我觉得它有助于测试全面性的时候特意引入一些。同样,无论你采用哪种方法到工厂,目标都是将你在每个测试方法的头中引入的脑力最小化。

Use mocks, but use them wisely

I'm a fan of mock, as I've developed an appreciation for the author's solution to what I believe was the problem he wanted to address. The tools provided by the package allow you to form test assertions by injecting expected outcomes.

我是mock的粉丝,因为我对作者对我认为是他想解决的问题的解决方案产生了赞赏。包提供的工具允许您通过注入预期结果来形成测试断言。

# Creating mocks to simplify tests
factory = RequestFactory()
request = factory.get()
request.user = Mock(is_authenticated=lamda: True)  # A mock of an authenticated user
view = DispatchForAuthenticatedOnlyView().as_view()

response = view(request)


# Patching objects to return expected data
@patch.object(CurrencyApi, 'get_currency_list', return_value="{'foo': 1.00, 'bar': 15.00}")
def test_converts_between_two_currencies(self, currency_list_mock):
    converter = Converter()  # Uses CurrencyApi under the hood

    result = converter.convert(from='bar', to='foo', ammount=45)
    self.assertEqual(4, result)

As you can see, mocks are really helpful, but they have a nasty side effect: your mocks clearly show your making assumptions on how it is that your application behaves, which introduces coupling. If Converter is refactored to use something other than the CurrencyApi, someone may not obviously understand why the test method is suddenly failing.

正如您所看到的,mock非常有用,但是它们有一个讨厌的副作用:您的mock清楚地显示了您对应用程序行为的假设,这将引入耦合。如果转换器被重构以使用除CurrencyApi之外的其他东西,可能会有人不明白为什么测试方法突然失效。

So with great power comes great responsibility--if your going to be a smartass and use mocks to avoid deeply rooted test obstacles, you may completely obfuscate the true nature of your test failures.

因此,强大的力量带来了巨大的责任——如果你想成为一个聪明的人,并使用mock来避免根深蒂固的测试障碍,你可能会完全混淆测试失败的真正本质。

Above all, be consistent. Very very consistent

This is the most important point to be made. Be consistent with absolutely everything:

这是最重要的一点。与所有的事情保持一致:

  • how you organize code in each of your test modules
  • 如何在每个测试模块中组织代码
  • how you introduce test cases for your application components
  • 如何为应用程序组件引入测试用例
  • how you introduce test methods for asserting the behavior of those components
  • 如何引入测试方法来断言这些组件的行为
  • how you structure test methods
  • 如何构造测试方法?
  • how you approach testing common components (class-based views, models, forms, etc.)
  • 如何测试通用组件(基于类的视图、模型、表单等)
  • how you apply reuse
  • 你如何应用重用

For most projects, the bit about how your collaboratively going to approach testing is often overlooked. While the application code itself looks perfect--adhering to style guides, use of Python idioms, reapplying Django's own approach to solving related problems, textbook use of framework components, etc.--no one really makes it an effort to figure out how to turn test code into a valid, useful communication tool and it's a shame if, perhaps, having clear guidelines for test code is all it takes.

对于大多数项目来说,关于协作如何进行测试的内容常常被忽略。虽然应用程序代码本身看起来完美——坚持风格指南,使用Python的成语,重新使用Django的方法解决相关问题,教科书框架组件的使用,等等——没有人真正使它试图找出如何将测试代码转变成一个有效的、有用的沟通工具,这是一个耻辱,如果可能,为测试代码就明确的指导方针。

#1


44  

UPDATE 08-07-2012

更新08-07-2012

I can tell you my practices for unit testing that are working pretty well for my own ends and I'll give you my reasons:

我可以告诉你们我的单元测试的实践我的目的很好,我会告诉你们我的理由:

1.- Use Fixtures only for information that is necessary for testing but is not going to change, for example, you need a user for every test you do so use a base fixture to create users.

1。-只对测试所必需的但不会改变的信息使用fixture,例如,在每次测试中都需要一个用户,所以要使用base fixture来创建用户。

2.- Use a factory to create your objects, I personally love FactoryBoy (this comes from FactoryGirl which is a ruby library). I create a separate file called factories.py for every app where I save all these objects. This way I keep off the test files all the objects I need which makes it a lot more readable and easy to maintain. The cool thing about this approach is that you create a base object that can be modified if you want to test something else based on some object from the factory. Also it doesn't depend on django so when I migrated these objects when I started using mongodb and needed to test them, everything was smooth. Now after reading about factories it's common to say "Why would I want to use fixtures then". Since these fixtures should never change all the extra goodies from factories are sort of useless and django supports fixtures very well out of the box.

2。-使用工厂来创建你的对象,我个人喜欢factory - boy(这来自一个ruby库factory - girl)。我创建了一个名为factory的单独文件。对于保存所有这些对象的每个应用。这样,我就不需要测试文件中的所有对象,从而使它更易于阅读和维护。这种方法很酷的一点是,如果您希望基于工厂的某个对象测试其他对象,那么您可以创建一个基对象,该对象可以进行修改。它也不依赖于django,所以当我开始使用mongodb并需要测试这些对象时,一切都很顺利。现在,在阅读了有关工厂的文章后,人们通常会说:“那我为什么要使用设备呢?”因为这些夹具不应该改变工厂提供的所有额外的好东西都是无用的,django非常支持开箱即用的夹具。

3.- I Mock calls to external services, because these calls make my tests very slow and they depend on things that have nothing to do with my code being right or wrong. for example, if I tweet within my test, I do test it to tweet rightly, copy the response and mock that object so it returns that exact response every time without doing the actual call. Also sometimes is good to test when things go wrong and mocking is great for that.

3所示。-我模拟对外部服务的调用,因为这些调用使我的测试非常缓慢,并且它们依赖于与我的代码是否正确或错误无关的东西。例如,如果我在我的测试中tweet,我确实会测试它正确地tweet,复制响应并模拟该对象,以便它每次都返回准确的响应,而不执行实际调用。有时候,当事情出错的时候测试一下是很好的,而嘲笑则是很好的方法。

4.- I use an integration server (jenkins is my recommendation here) which runs the tests every time I push to my staging server and if they fail it sends me an email. This is just great since it happens to me a lot that I break something else in my last change and I forgot to run the tests. It also gives you other goodies like a coverage report, pylint/jslint/pep8 verifications and there exists a lot of plugins where you can set different statistics.

4所示。-我使用一个集成服务器(jenkins是我的建议),每次我推到我的staging服务器时都运行测试,如果他们失败了,它会给我发邮件。这很好,因为我在上次的变更中有很多地方出错了,我忘记运行测试了。它还为您提供了其他好处,比如覆盖率报告、pylint/jslint/pep8验证,还有许多插件可以在其中设置不同的统计数据。

About your question for testing front end, django comes with some helper functions to handle this in a basic way.

关于测试前端的问题,django提供了一些帮助函数来以基本的方式处理这个问题。

This is what I personally use, you can fire gets, posts, login the user, etc. that's enough for me. I don't tend to use a complete front end testing engine like selenium since I feel it's an overkill to test anything else besides the business layer. I am sure some will differ and it always depends on what you are working on.

这是我个人使用的,你可以使用get、post、login等对我来说已经足够了。我不倾向于使用像selenium这样的完整前端测试引擎,因为我觉得除了业务层之外,测试其他任何东西都是多余的。我相信有些人会有所不同,这取决于你在做什么。

Besides my opinion, django 1.4 comes with a very handy integration for in-browser frameworks.

除了我的看法之外,django 1.4提供了一个非常方便的浏览器内框架集成。

I'll set an example app where I can apply this practices so it is more understandable. Let's create a very basic blog app:

我将设置一个示例应用程序,在其中我可以应用这些实践,以便更容易理解。让我们创建一个非常基本的博客应用:

structure

结构

blogger/
    __init__.py
    models.py
    fixtures/base.json
    factories.py
    tests.py

models.py

models.py

 from django.db import models

 class Blog(models.Model):
     user = models.ForeignKey(User)
     text = models.TextField()
     created_on = models.DateTimeField(default=datetime.now())

fixtures/base.json

设备/ base.json

[
{
    "pk": 1,
    "model": "auth.user",
    "fields": {
        "username": "fragilistic_test",
        "first_name": "demo",
        "last_name": "user",
        "is_active": true,
        "is_superuser": true,
        "is_staff": true,
        "last_login": "2011-08-16 15:59:56",
        "groups": [],
        "user_permissions": [],
        "password": "IAmCrypted!",
        "email": "test@email.com",
        "date_joined": "1923-08-16 13:26:03"
    }
}
]

factories.py

factories.py

import factory
from blog.models import User, Blog

class BlogFactory(factory.Factory):
    FACTORY_FOR = Blog

    user__id = 1
    text = "My test text blog of fun"

tests.py

tests.py

class BlogTest(TestCase):
    fixtures = ['base']  # loads fixture

    def setUp(self):
        self.blog = BlogFactory()
        self.blog2 = BlogFactory(text="Another test based on the last one")

    def test_blog_text(self):
        self.assertEqual(Blog.objects.filter(user__id=1).count(), 2)

    def test_post_blog(self):
        # Lets suppose we did some views
        self.client.login(username='user', password='IAmCrypted!')
        response = self.client.post('/blogs', {'text': "test text", user='1'})

        self.assertEqual(response.status, 200)
        self.assertEqual(Blog.objects.filter(text='test text').count(), 1)

    def test_mocker(self):
        # We will mock the datetime so the blog post was created on the date
        # we want it to
        mocker = Mock()
        co = mocker.replace('datetime.datetime')
        co.now()
        mocker.result(datetime.datetime(2012, 6, 12))

        with mocker:
            res = Blog.objects.create(user__id=1, text='test')

        self.assertEqual(res.created_on, datetime.datetime(2012, 6, 12))

    def tearDown(self):
        # Django takes care of this but to be strict I'll add it
        Blog.objects.all().delete()

Notice I am using some specific technology for the sake of the example (which haven't been tested btw).

请注意,我使用一些特定的技术是为了这个示例(还没有经过测试)。

I have to insist, this may not be the standard best practice (which I doubt it exists) but it is working pretty well for me.

我必须坚持,这可能不是标准的最佳实践(我怀疑它的存在),但它对我来说非常有效。

#2


20  

I really like the suggestions from @Hassek and want to stress out what an excellent point he makes about the obvious lack of standard practices, which holds true for many of Django's aspects, not just testing, since all of us approach the framework with different concerns in mind, also adding to that the great degree of flexibility we have with designing our applications, we often end up with drastically different solutions that are applicable to the same problem.

我真的喜欢@Hassek的建议,想强调出一个优秀的点他明显缺乏标准实践,它适用于Django的许多方面,不仅测试,因为我们所有的方法框架记住不同的关注点,也增加了我们伟大的程度的灵活性与设计应用程序时,我们经常会得到截然不同的解决方案,适用于同样的问题。

Having said that, though, most of us still strive for many of the same goals when testing our applications, mainly:

尽管如此,在测试我们的应用程序时,我们大多数人仍然在努力实现许多相同的目标,主要是:

  • Keeping our test modules neatly organized
  • 保持我们的测试模块整洁有序。
  • Creating reusable assertion and helper methods, helper functions that reduce the LOC for test methods, to make them more compact and readable
  • 创建可重用的断言和帮助器方法,帮助器函数减少测试方法的LOC,以使它们更紧凑和可读
  • Showing that there is an obvious, systematic approach to how the application components are tested
  • 显示了一种明显的、系统的方法来测试应用程序组件

Like @Hassek, these are my preferences that may directly conflict with the practices that you may be applying, but I feel it's nice to share the things we've proven that work, if only in our case.

就像@Hassek一样,这些是我的偏好,它们可能直接与您正在应用的实践相冲突,但是我觉得分享我们已经证明有效的东西是很好的,即使只是在我们的案例中。

No test case fixtures

Application fixtures work great, in cases you have certain constant model data you'd like to guarantee to be present in the database, say a collection of towns with their names and post office numbers.

应用程序装置工作得很好,如果您有一定的固定模型数据,您希望保证在数据库中显示,比如包含有它们的名称和邮局编号的城镇集合。

However, I see this as an inflexible solution for providing test case data. Test fixtures are very verbose, model mutations force you to either go through a lengthy process of reproducing the fixture data or to perform tedious manual changes and maintaining referential integrity is difficult to manually perform.

但是,我认为这是提供测试用例数据的一个灵活的解决方案。测试装置是非常冗长的,模型突变迫使您要么经历一个冗长的过程,重新生成fixture数据,要么执行冗长的手动更改,维护引用完整性是很难手动执行的。

Additionally, you'll most likely use many kinds of fixtures in your tests, not just for models: you'd like to store the response body from API requests, to create fixtures that target NoSQL database backends, to write have fixtures that are used to populate form data, etc.

此外,您很可能会在测试中使用许多类型的fixture,而不仅仅是模型:您希望从API请求中存储响应主体,创建目标为NoSQL数据库后端的fixture,编写用于填充表单数据的have fixture,等等。

In the end, utilizing APIs to create data is concise, readable and it makes it much easier to spot relations, so most of us resort to using factories for dynamically creating fixtures.

最后,使用api来创建数据是简洁的、可读的,并且可以更容易地发现关系,所以我们大多数人都使用工厂来动态地创建fixture。

Make extensive use of factories

Factory functions and methods are preferable to stomping out your test data. You can create helper factory module-level functions or test case methods that you may want to either reuse across application tests or throughout the whole project. Particularly, factory_boy, that @Hassek mentions, provides you with the ability to inherit/extend fixture data and do automatic sequencing, which might look a bit clumsy if you'd do it by hand otherwise.

工厂功能和方法比大量输出测试数据更可取。您可以创建帮助工厂模块级函数或测试用例方法,您可能希望在应用程序测试或整个项目中重用它们。特别地,@Hassek提到的factory_boy为您提供了继承/扩展fixture数据并进行自动排序的能力,如果不这样做的话,看起来可能有点笨拙。

The ultimate goal of utilizing factories is to cut down on code-duplication and streamline how you create test data. I cannot give you exact metrics, but I'm sure if you go through your test methods with a discerning eye you will notice that a large portion of your test code is mainly preparing the data that you'll need to drive your tests.

利用工厂的最终目标是减少代码重复和简化如何创建测试数据。我不能给你确切的度量标准,但是我确信如果你用一个有眼光的眼光来检查你的测试方法,你会注意到你的测试代码的很大一部分主要是准备你需要驱动你的测试的数据。

When this is done incorrectly, reading and maintaining tests becomes an exhausting activity. This tends to escalate when data mutations lead to not-so-obvious test failures across the board, at which point you'll not be able to apply systematic refactoring efforts.

当不正确地完成时,读取和维护测试将成为一项令人筋疲力尽的活动。当数据突变导致全面的不太明显的测试失败时,这种情况就会升级,这时您将无法应用系统的重构工作。

My personal approach to this problem is to start with a myproject.factory module that creates easy-to-access references to QuerySet.create methods for my models and also for any objects I might regularly use in most of my application tests:

我个人解决这个问题的方法是从myproject开始。创建易于访问的QuerySet引用的工厂模块。为我的模型创建方法,也为我在大多数应用程序测试中经常使用的对象创建方法:

from django.contrib.auth.models import User, AnonymousUser
from django.test import RequestFactory

from myproject.cars.models import Manufacturer, Car
from myproject.stores.models import Store


create_user = User.objects.create_user
    create_manufacturer = Manufacturer.objects.create
create_car = Car.objects.create
create_store = Store.objects.create

_factory = RequestFactory()


def get(path='/', data={}, user=AnonymousUser(), **extra):
    request = _factory.get(path, data, **extra)
    request.user = user

    return request


def post(path='/', data={}, user=AnonymousUser(), **extra):
    request = _factory.post(path, data, **extra)
    request.user = user

    return request

This in turn allows me to do something like this:

这反过来又允许我做这样的事情:

from myproject import factory as f  # Terse alias

# A verbose, albeit readable approach to creating instances
manufacturer = f.create_manufacturer(name='Foomobiles')
car1 = f.create_car(manufacturer=manufacturer, name='Foo')
car2 = f.create_car(manufacturer=manufacturer, name='Bar')

# Reduce the crud for creating some common objects
manufacturer = f.create_manufacturer(name='Foomobiles')
data = {name: 'Foo', manufacturer: manufacturer.id)
request = f.post(data=data)
view = CarCreateView()

response = view.post(request)

Most people are rigorous about reducing code duplication, but I actually intentionally introduce some whenever I feel it contributes to test comprehensiveness. Again, the goal with whichever approach you take to factories is to minimize the amount of brainfuck you introduce into the header of each test method.

大多数人对减少代码重复都很严格,但是我实际上是在我觉得它有助于测试全面性的时候特意引入一些。同样,无论你采用哪种方法到工厂,目标都是将你在每个测试方法的头中引入的脑力最小化。

Use mocks, but use them wisely

I'm a fan of mock, as I've developed an appreciation for the author's solution to what I believe was the problem he wanted to address. The tools provided by the package allow you to form test assertions by injecting expected outcomes.

我是mock的粉丝,因为我对作者对我认为是他想解决的问题的解决方案产生了赞赏。包提供的工具允许您通过注入预期结果来形成测试断言。

# Creating mocks to simplify tests
factory = RequestFactory()
request = factory.get()
request.user = Mock(is_authenticated=lamda: True)  # A mock of an authenticated user
view = DispatchForAuthenticatedOnlyView().as_view()

response = view(request)


# Patching objects to return expected data
@patch.object(CurrencyApi, 'get_currency_list', return_value="{'foo': 1.00, 'bar': 15.00}")
def test_converts_between_two_currencies(self, currency_list_mock):
    converter = Converter()  # Uses CurrencyApi under the hood

    result = converter.convert(from='bar', to='foo', ammount=45)
    self.assertEqual(4, result)

As you can see, mocks are really helpful, but they have a nasty side effect: your mocks clearly show your making assumptions on how it is that your application behaves, which introduces coupling. If Converter is refactored to use something other than the CurrencyApi, someone may not obviously understand why the test method is suddenly failing.

正如您所看到的,mock非常有用,但是它们有一个讨厌的副作用:您的mock清楚地显示了您对应用程序行为的假设,这将引入耦合。如果转换器被重构以使用除CurrencyApi之外的其他东西,可能会有人不明白为什么测试方法突然失效。

So with great power comes great responsibility--if your going to be a smartass and use mocks to avoid deeply rooted test obstacles, you may completely obfuscate the true nature of your test failures.

因此,强大的力量带来了巨大的责任——如果你想成为一个聪明的人,并使用mock来避免根深蒂固的测试障碍,你可能会完全混淆测试失败的真正本质。

Above all, be consistent. Very very consistent

This is the most important point to be made. Be consistent with absolutely everything:

这是最重要的一点。与所有的事情保持一致:

  • how you organize code in each of your test modules
  • 如何在每个测试模块中组织代码
  • how you introduce test cases for your application components
  • 如何为应用程序组件引入测试用例
  • how you introduce test methods for asserting the behavior of those components
  • 如何引入测试方法来断言这些组件的行为
  • how you structure test methods
  • 如何构造测试方法?
  • how you approach testing common components (class-based views, models, forms, etc.)
  • 如何测试通用组件(基于类的视图、模型、表单等)
  • how you apply reuse
  • 你如何应用重用

For most projects, the bit about how your collaboratively going to approach testing is often overlooked. While the application code itself looks perfect--adhering to style guides, use of Python idioms, reapplying Django's own approach to solving related problems, textbook use of framework components, etc.--no one really makes it an effort to figure out how to turn test code into a valid, useful communication tool and it's a shame if, perhaps, having clear guidelines for test code is all it takes.

对于大多数项目来说,关于协作如何进行测试的内容常常被忽略。虽然应用程序代码本身看起来完美——坚持风格指南,使用Python的成语,重新使用Django的方法解决相关问题,教科书框架组件的使用,等等——没有人真正使它试图找出如何将测试代码转变成一个有效的、有用的沟通工具,这是一个耻辱,如果可能,为测试代码就明确的指导方针。