Product teams
Deliver localized versions of your product faster by automating tedious localization steps.
Localization teams
Streamline your workflows and simplify collaboration with efficient localization management.
Developers teams
Add Transifex to your CI/CD pipeline to continuously deploy new translations.
Marketing teams
Quickly launch multilingual websites to accelerate international growth and conversions.
Translators
Deliver more accurate translations faster, leveraging advanced linguistic tools.
Software localization
Keep software continuously localized and in sync using automated CI/CD workflows.
Website localization
Automate and scale website localization to attract and convert international visitors.
Mobile App localization
Rapidly translate and launch apps globally, ensuring high-quality user experiences across markets.
Get a Personalized Demo Today
Precise, on-brand translations at scale. Language AI delivers context-rich content faster.
Get a personalized demo today
Request a personalized demo to learn how to integrate Transifex into your CI/CD
Product teams
Deliver localized versions of your product faster by automating tedious localization steps.
Localization teams
Streamline your workflows and simplify collaboration with efficient localization management.
Developers teams
Add Transifex to your CI/CD pipeline to continuously deploy new translations.
Marketing teams
Quickly launch multilingual websites to accelerate international growth and conversions.
Translators
Deliver more accurate translations faster, leveraging advanced linguistic tools.
Software localization
Keep software continuously localized and in sync using automated CI/CD workflows.
Website localization
Automate and scale website localization to attract and convert international visitors.
Mobile App localization
Rapidly translate and launch apps globally, ensuring high-quality user experiences across markets.
Get a Personalized Demo Today
Precise, on-brand translations at scale. Language AI delivers context-rich content faster.
Get a personalized demo today
Request a personalized demo to learn how to integrate Transifex into your CI/CD
There are countless posts out there evangelizing the importance of testing in
the development process. This is not one of those posts. Just to make sure we
are all on the same page though, as a team we strongly believe you should first
write your tests, then (re)write the actual code again and again, until all tests
pass and finally enjoy a (more) peaceful night. If you don’t do so, you’d better have
Jack Sparrow's improvisation skills and love caffeine.
Now, time to get technical. Here’s how we managed to speed up our test suite by a 3x factor.
We are not talking about “Unit test vs System test” here. Unit tests are fast, granular and localized. They should be used to test as much code as possible. However, they are not a replacement for system tests or integration tests and vice versa. We need system tests to ensure that the separate units fit together nicely to make the entire application work. Since, system tests tend to be slower, their count should be very low compared to unit tests. A reasonable ratio between unit and system tests would be 9:1.
I feel we are being too harsh on system tests, ain’t we? Wouldn’t it be wonderful if you could make system tests faster? The faster the better. Let’s see how we did it in Transifex.
transifex.txcommon.tests.base.BaseTestCase
, a subclass of django.test.TestCase
and other helper classes.BaseTestCase
is responsible for loading fixtures and setting up test data like sample projects, resources, permissions, user, clients, etc.setUp()
method.BaseTestCase
).You may be thinking that “Why the hell do I need to setup a lot of data for each test? I can just setup what data I need.”
Yes, you are correct in that. [1] has got a lot of latency the usual way. But there are other things to consider too. It helps a developer spend less time setting up the world during writing a test. It’s an overkill to setup the world for each test case separately. Also, it leads to redundancy of setup code. About fixtures, we plan to get rid of them in due course of time.
It seems like it’s trade off between the ease of writing tests and test speed. Well, we are kind of greedy in these cases and want to have both :D
All we needed was to find a way to do away with the latency of setting up the world for the BaseTestCase.
BaseTestCase
or TestCase
)class TxTestSuiteRunner(DjangoTestSuiteRunner):
def setup_databases(self, **kwargs):
return_val = super(TxTestSuiteRunner, self).setup_databases(
**kwargs)
databases = connections
for db in databases:
management.call_command(‘loaddata’, *fixtures,
**{‘verbosity’: 0, ‘database’: db})
return return_val
setUpClass
method of BaseTestCase. Data setup in
setUpClasswill be persistent throughout the run of the entire test case. Until and unless required, data initialization in
setUp()method of a test case can be skipped. For a simple
TestCase“, Django anyways rolls back all changes done within a test method._Model.objects.get_or_create()
method to fetch/initialize data to minimize database writesetUp()
method, we copy the class wide variables using copy.copy()
to some temporary variables. The test method works with these temporary variables. This leaves the original class wide variables intact. from copy import copy
class BaseTestCase(Languages, NoticeTypes, Translations, TestCase):
@classmethod
def setUpClass(cls):
super(BaseTestCase, cls).setUpClass(cls)
# Only showing a code snippet…
# Create teams
cls._team = Team.objects.get_or_create(language=cls._language,
project=cls._project, creator=cls._user['maintainer'])[0]
cls._team_private = Team.objects.get_or_create(
language=cls._language, project=cls._project_private,
creator=cls._user['maintainer'])[0]# ...
def setUp(self):
super(BaseTestCase, self).setUp(self)
# Only copy test case wide variables
# to temporary ones to work with in a
# test method.# Only showing a code snippet...
# test method operate on self.team instead of self._team
# and similarly for other variables too
self.team = copy(self._team)
self.team_private = copy(self._team_private)
# ...
class BaseTestCase(Languages, NoticeTypes, Translations, TestCase):
# Only showing a code snippet…
def _pre_setup(self):
if not connections_support_transactions():
# truncate tables, load initial date
# in case database does not support
# transactions. Hence, no optimization
# in such cases.
fixtures = ["sample_users", "sample_site",
"sample_languages", "sample_data"]
if getattr(self, 'multi_db', False):
databases = connections
else:
databases = [DEFAULT_DB_ALIAS]
for db in databases:
call_command('flush', verbosity=0, interactive=False,
database=db)
call_command('loaddata', *fixtures, **{'verbosity': 0,
'database': db})else:
# Optimization achieved if database
# supports transactions
if getattr(self, 'multi_db', False):
databases = connections
else:
databases = [DEFAULT_DB_ALIAS]for db in databases:
transaction.enter_transaction_management(using=db)
transaction.managed(True, using=db)
disable_transaction_methods()
mail.outbox = []def _post_teardown(self):
if connections_support_transactions():
# If the test case has a multi_db=True flag, teardown all
# databases. Otherwise, just teardown default.
if getattr(self, 'multi_db', False):
databases = connections
else:
databases = [DEFAULT_DB_ALIAS]
restore_transaction_methods()
for db in databases:
transaction.rollback(using=db)
transaction.leave_transaction_management(using=db)
for connection in connections.all():
connection.close()
The results were quite satisfying. With the custom test runner and the new test suite, tests got around 2-3 times faster. The new test suite’s speed up factor is proportional to the number of test methods in a test case when compared to its older counterpart. The new test suite, although not yet perfect , is working quite well. As kbairak said here:
holy shit! @rtnpro ‘s modifications make @transifex ‘s test-suite run like a hamster on coffee !!!