diff --git a/product/ERP5Type/tests/CodingStyleTestCase.py b/product/ERP5Type/tests/CodingStyleTestCase.py
index 52ea1d574a865c0cf0f787a83e958d345e454ade..58b7e0229dc802dd7a238fbb4700755615f008ec 100644
--- a/product/ERP5Type/tests/CodingStyleTestCase.py
+++ b/product/ERP5Type/tests/CodingStyleTestCase.py
@@ -28,9 +28,13 @@
 ##############################################################################
 
 import collections
+import difflib
+import filecmp
+import fnmatch
 import glob
 import os
-import tarfile
+import shutil
+import tempfile
 
 from Acquisition import aq_base
 from Testing import ZopeTestCase
@@ -105,29 +109,84 @@ class CodingStyleTestCase(ERP5TypeTestCase):
     for business_template in self._getTestedBusinessTemplateValueList():
       self.assertEqual(business_template.BusinessTemplate_getPythonSourceCodeMessageList(), [])
 
+
+  # Paths for which we ignore differences when re-exporting business templates
+  rebuild_business_template_ignored_path = """
+  # portal_transforms seem to be always different
+  erp5_core/ToolTemplateItem/portal_transforms.xml
+
+  # Empty messages are exported in message catalog, so they can be different because of
+  # empty messages. Reindexing during creation for example insert entries for all workflow
+  # states, so this change often.
+  */MessageTranslationTemplateItem/*/*/translation.po
+
+  # This seem to be a copy of
+  # bt5/erp5_configurator_standard/PathTemplateItem/business_configuration_module/default_standard_configuration.xml
+  # that was modified. When re-exporting it is different, but since this is test data we ignore it for now.
+  erp5_scalability_test/PathTemplateItem/business_configuration_module/default_standard_configuration.xml
+  erp5_scalability_test/PathTemplateItem/business_configuration_module/default_standard_configuration/*
+
+  # This is different for some unknown reason, because it's test data we ignore for now
+  erp5_payroll_l10n_fr_test/PathTemplateItem/accounting_module/trainee_january.xml
+  erp5_payroll_l10n_fr_test/PathTemplateItem/accounting_module/trainee_january/*
+  """
   def test_rebuild_business_template(self):
-    """Try to rebuild business template to catch packaging errors.
+    """Try to rebuild business template to catch packaging errors and make sur output is stable.
     """
+    self.maxDiff = None
     template_tool = self.portal.portal_templates
     for bt_title in self.getTestedBusinessTemplateList():
       bt = template_tool.getInstalledBusinessTemplate(bt_title, strict=True)
       # make sure we can rebuild
       bt.build()
 
-      # check we don't add or remove members.
-      # first, build a set of files that were on the original business template repository
-      base_path, local_path = self.portal.portal_templates.getLastestBTOnRepos(bt_title)
-      existing_files = set([os.path.relpath(y, base_path)
-        for x in os.walk(os.path.join(base_path, local_path))
-            for y in glob.glob(os.path.join(x[0], '*')) if os.path.isfile(y)])
+      # Compute the differences between the reference business template
+      # from the working copy and the newly exported business template.
+      bt_base_path, bt_local_path = self.portal.portal_templates.getLastestBTOnRepos(bt_title)
+      bt_dir = os.path.join(bt_base_path, bt_local_path)
+      export_base_path = tempfile.mkdtemp()
+      self.addCleanup(shutil.rmtree, export_base_path)
+      export_dir = os.path.join(export_base_path, bt_local_path)
+      bt.export(export_dir, local=True)
+
+      ignored_paths = {
+          p.strip() for p in self.rebuild_business_template_ignored_path.splitlines()
+          if p and not p.strip().startswith("#")}
+
+      diff_line_list = []
+      def get_differences(dcmp, base):
+        for name in dcmp.left_only:
+          yield 'removed: ' + os.path.join(base, name)
+        for name in dcmp.right_only:
+          yield 'added: ' + os.path.join(base, name)
+        for name in dcmp.funny_files:
+          yield 'funny: ' + os.path.join(base, name)
+        for name in dcmp.diff_files:
+          path = os.path.join(base, name)
+          if not any(fnmatch.fnmatch(path, ignored_path) for ignored_path in ignored_paths):
+            yield 'modified: ' + path
+            with open(os.path.join(bt_base_path, path)) as ff, \
+                open(os.path.join(export_base_path, path)) as tf:
+              diff_line_list.extend(
+                  difflib.unified_diff(
+                      ff.readlines(),
+                      tf.readlines(),
+                      os.path.join('git', path),
+                      os.path.join('bt5', path),
+                  ))
+              diff_line_list.append('\n')
+        for sub_path, sub_dcmp in dcmp.subdirs.iteritems():
+          for diff in get_differences(sub_dcmp, os.path.join(base, sub_path)):
+            yield diff
 
-      # then compare this with the files in the newly exported business template.
-      bt_file = bt.export()
-      bt_file.seek(0) # XXX this StringIO was already read...
-      new_files = set(tarfile.open(fileobj=bt_file, mode='r:gz').getnames())
+      diff_files = list(get_differences(filecmp.dircmp(bt_dir, export_dir), bt_local_path))
+      # dump a diff in log directory, to help debugging
+      from Products.ERP5Type.tests.runUnitTest import log_directory
+      if log_directory and diff_line_list:
+        with open(os.path.join(log_directory, '%s.diff' % self.id()), 'w') as f:
+          f.writelines(diff_line_list)
+      self.assertEqual(diff_files, [])
 
-      self.maxDiff = None
-      self.assertEqual(existing_files, new_files)
 
   def test_run_upgrader(self):
     # Check that pre and post upgrade do not raise problems.