So you've got a test in your Django application that isn't quite working as expected.
Sees to be an issue with the User
model and the full_name
method.
class User(AbstractUser):
password = models.CharField(max_length=128)
last_login = models.DateTimeField(blank=True, null=True)
is_superuser = models.BooleanField(default=False)
username = models.CharField(unique=True, max_length=150)
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.CharField(max_length=254)
phone = models.CharField(max_length=8, null=True, blank=True)
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
birth_date = models.DateField(null=True, blank=True)
@property
def full_name(self):
return f"{self.first_name} {self.last_name}"
You like ease of use - so you've got Factory Boy installed and have a simple UserFactory
created:
class UserFactory(factory.django.DjangoModelFactory):
"""
Factory for creating basic Users
"""
password = "test"
last_login = factory.LazyAttribute(
lambda o: timezone.now() - timedelta(hours=1)
)
is_superuser = False
username = factory.LazyAttributeSequence(
lambda a, n: "{0}{1}{2}".format(a.first_name, a.last_name, n).lower()
)
first_name = factory.Faker("first_name")
last_name = factory.Faker("last_name")
email = factory.LazyAttributeSequence(
lambda a, n: "{0}{1}{2}@your.org".format(
a.first_name, a.last_name, n
).lower()
)
is_staff = False
is_active = True
phone = factory.Faker("numerify", text="####")
class Meta:
model = User
django_get_or_create = ("username",)
@factory.post_generation
def groups(self, create, extracted, **kwargs):
if not create or not extracted:
# Simple build, or nothing to add, do nothing.
return
# Add the iterable of groups using bulk addition
self.groups.add(*extracted)
No problem from here on out - you know the drill on finding these issues and get right into it. You hop into the test and throw in a breakpoint()
where you think the issue is. You want to hop in the debugger poke around a little - maybe it's something simple.
It's a simple TestCase
after all:
class TestCaseUser(TestCase):
def test_full_name_method(self):
"""
Test the full name method returns expected values
"""
full_name = UserFactory.create(first_name="Bob", last_name="Roberts")
breakpoint()
self.assertEqual(full_name, "Bob Roberts")
# Yes, this is oversimplified, to provide the example!
You run the test, the debugger opens and you crack your knuckles. This issue won't last long! You try to print out the variable that is causing the issue (print(full_name)
)- and the shell spits out that the variable is not defined.
--Call--
> /Users/chrisgoodwin/easy_project/simple_app/models.py(119)full_name()
-> @property
(Pdb) print(full_name)
*** NameError: name 'full_name' is not defined
(Pdb)
Wait, what?
You scratch your head and do the thing everyone in technology generally does: try it again (turn it off and back on, right?) Same results.
You see what the debugger is stating - but it is not supposed to work that way. It would appear that you're somehow dropped into the full_name
function on the User
model.
Your breakpoint is listed in the test; so your code should stop right there and toss you into the debugger - but it did not, the code kept executing until you hit another function - then it stopped executing and got you into the debugger.
It's bizarre, and it's weird. But it happened to me.
The reason? Apparently, there are some issues between the coverage
library and the pytest-cov
library. If you downgrade coverage
enough versions - the issue goes away. Specifically downgrading to versions 6.4 and earlier eliminates the issue. Versions 6.4.1 and later cause this issue to occur. But downgrading that far? Eww.
I think there may be some unexpected complications from factory_boy
library as well - because if you just manually create a User
model using the Django provided methods (User.objects.create()
) the issue does not occur in that case either. But seriously, giving up the functionality of Factory Boy, or downgrading multiple versions of the coverage
library? No, thank you.
There is a simple solution (since the developers of some of these libraries are aware of the issues): run the tests with the additional --no-cov
flag when you want the breakpoints to work as expected. There is a discussion in the PyCharm community about it because it affects the built-in debugger.
pytest simple_app/tests/test_models.py --no-cov
... then we're dumped into the debugger where we would expect and things work exactly as they should.
For the time being, until these libraries get the details of this little bugger under control (which at this point seems unlikely), running with --no-cov
when we need to use breakpoints seems to suffice. It's a minor inconvenience, but at least there's a temporary solution!
For PyCharm users - you can add this to the debugging configuration pretty easily:
Happy Debugging!