I really admire the way you explain all these concepts. Even the quality of the video itself should not be underestimated: from the way you zoom the source code to the transitions from VS Code to yourself, it is really clear to me how much time and passion you put into this.
This two-parter is a great tutorial. Thanks for talking the time and effort to provide us with this content. It's really high quality. I'll have to rewatch the second video another time. Around the 8:00 minute mark I had a light bulb moment how I can turn one of my helper functions into a class and instantiate the object as needed. Not only will this improved my unit test, this was actually a helper function with important functionality that I may change depending on the business need. I can see how i may define different classes based on different use cases. Thanks for that. The video after the 8:00 minute mark was too quick and too dense for me to follow. I'll have to sit down and rewatch what you actually did there. I know this was originally intended as one video but i think you could have spread out the content over 4 videos easily, essentially adding 2 separate videos about refactoring your code. I would have watched those, too! 😂
Phenomenal video about software testing, in python and in general. Also, an amazing demonstration of how unit testing can improve your code design. Subscribed!
This was excellent! Thanks for this, I think i'll be watching both videos many times and studying your refactored code so I can properly implement unit tests into my own projects
I am getting deeper understanding about all the core concepts of python for the software development, Great ability to teach complex logic with very ease, Thanks 🙏🏻
@@ArjanCodes extensively helpful, in our project we are moving forward with the SOLID principle and you videos helps me to understand everything related it❤️, Thanks
I'm curious about the test cases for payment.py. Shouldn't we test if the payment processor charge method was called when testing for paying the order? Since "test_pay_order" would pass even if we remove line 17 "payment_processor.charge(card, amount=order.total)" in payment.py. Similarly, shouldn't we test if the order status wasn't changed if ValueError was raised?
Amazing video! The last part about env variables was really great because I needed it for a long time and was only defining env variables in terminal using export. Thank you very much for your help!
Great video! I'm just binge watching all of them :) Note: I would consider inverting also dependency to datetime.now(). Thanks to that you can mock in your tests what day is today and test i.e. how PaymentProcessor would behave on Feb 29th or Dec 31st
This is an excellent example, I would just stress how important is to keep in mind the actual functionality of the software, as presented the tests all pass but the main function includes original and non-functional call to pay_order that is missing arguments.
Very good and helpful, thanks a lot a little feedback, its kind harder to focus on the code if you zooming on your face like that, would be better to have the code on the screen all the time.
Great video as always, a side note is that I always wonder what are the shortcuts you keep using on your videos when coding, it really makes your coding looks very smooth and easy, I hope you could cover that in a future video
They sound almost identical to vim shortcuts/keybinds (especially copy/paste for instance -- sounds like he's using the `yy` keybind to copy a line, then `p` to paste it after current line). Most IDEs with a plugin ecosystem have a vim keybindings plugin, so give that a shot. They can take a while to learn, but once you get used to them they really help do things quickly.
Amazing video. So much to take away and put into practice in my current projects. Thank you very much. I’ve watched several videos about TDD, and this if by far the most useful one, I’ve spent my time watching. Thank you very much!
For dates one package I love is freezegun (and there is the appropriate pytest plugin for it as well) that it allows you to monkeypatch the today date so you do not ever have to worry about dates anymore. And, another thing, I would argue that hardcoding API keys for testing is fine (given that they are not real api keys) and that you mock the requests to the API itself so you also solve a whole lot of problems with that part specifically. But, with good design, it should be possible to sort that out easily.
Thanks for the tip! And indeed, you won't always need an API key directly in the testing code (I did this in the example as well, using the PaymentProcessorMock).
Great! I worked a lot with dates and timestamps. I suggest to inject time to functions instead of give them the responsability of getting the right time data. Thats makes code more testable and eiser to reuse.
@@ArjanCodes I do not use fixture because function depends heavily on current date/time. It is important to test the behavior for limit values like last day of the month, last day of the year, leap years, etc. In some cases we implemented a function to provide the current time in order to ease mocking for integration tests.
Really informative video! One thing I do for sensitive variables like API keys is to use the keyring module, that way you can avoid any extra files or plaintext storage of secrets. Another thought, would it not make sense to validate the credit card number as part of the dataclass?
I understand how dependency injection (removing the responsability of creating the dependent objects inside a function/method) makes the tests easier, since it allows passing mocks or stubs directly instead of monkey patching the actual implementation. However, the objects creation was moved to the main function, since they have to be created somewhere. Shouldn't we test it? Would that be an integration test? How does it differ from the unit tests? I always struggle with this because I still end up having to monkeypatch stuff to test the complete flow.
Usually, you don't test "main" at all because you should keep it super simple. But you could add a few integration tests if you still think your "main" is not simple enough ;)
I think the main function here would be what's called the "composition root". Its job is supposed to be the place where you "wire up" your app's dependency tree, so the code within it should as simple as possible. This usually means its not worth testing. But it depends on your specific situation
This is also something I don't understand. Most applications involve the use of many side effects. Moving all side effects to a main function and not testing them does not seem like a realistic solution. Are there any good resources out there that tackle this question?
@@apoca1ypse1 to unit test side effects, you don’t actually test the effects themselves. You just test to make sure that the side effect function was called correctly. You mock the side effect function and run assertions like “mock.wasCalledWithArguments(expected)”
@@vikingthedude I understand that you can mock side effects to test your code. Dependency injection looks to me like a method of moving where you mock rather than how to avoid mocking all together. If my understanding is correct then how do you decide where the best place for mocking is?
@arjan love your videos they are both informative and fun and have helped me a lot. I’m currently working on writing unit tests and am using pytest. I was wondering what the advantage was of using a pytest fixture as opposed to just defining a constant with the object at the top of the unit test script?
Hi Dirk, glad to hear you like the videos! If you use a fixture, a fresh, new object is created for each test. This is better than using a constant, which creates an object only once, when loading the script. You don't want that one test accidentally influences another test by changing the object that both tests rely on.
2 роки тому
Excellent question Dirk, I was wondering the exact same thing. Thanks for the answer Arjan!
@@ArjanCodes Thanks a lot for the videos, Arjan-I've been learning tons. One thing I like to do sometimes is define a _function_ that returns a fresh object--almost like a constructor. Similar to the current setup in the video, just without the decorator. Works just as well as a pytest fixture, but is a bit more explicit (in terms of linting support), I feel.
I asked this last week, but I still want to know - what are your thoughts on unittest vs pytest? Who here uses which one, and why do you prefer it to the other? I use both together sometimes, but I worry that this could lead to trouble. What do you think?
I'll share my experience, maybe it will be useful. I started with pytest but then switched to unittest because grouping related things under a TestCase seemed very nice and mainainable. But I still used "assert" and "pytest.raises" because they are prettier ;) but then I discovered that when a project grows, TestCases become in some way noisy. Also, you will always find out that, for example, "self" is not used (i.e. it could be a simple function). The next step for me was to return back to plain functions. I replaced TestCases by more complex packages/modules hiararchy and in the end it seems much better. About pytest fixtures... I tried them hard but in the end I abandoned it. I find them not flexible enough because they don't accept parameters (ofc you can make them to accept parameters but it looks hacky). So I use helper functions usually stored under tests/utils package. The only thing I use fixtures for is setup/teardown. And almost always those are fixtures with "session" scope and autouse=True located under conftest py. For example, loading of dummy SQL to a database.
Pytest is much bigger, have more features to use, and good solution as well, has good documentation and support. Unittest Library is kind of local or small story and pytest is a complete solution for really big and complex things
@@sulfur32066 That makes me think I'm missing out on a lot of what pytest can do. I like unittest `self.assertEqual`, `assertIn`, `assertRaises`, and 'assertLogs`... I'm not sure I know the idiomatic way to do all of those things in pytest, and I'm not sure what kind of context it gives when assertions fail - pytest users, do you ever test for log statements? Is there a pytest way? I also like using `Mock.assert_called_with`. Since I started watching this channel, I've been replacing patching with dependency injection. But I still want to assert that the right calls were made, so I'm still using unittest.Mock... what are your thoughts?
@@mikeciul8599 for such cases I personally prefer to use patch from unittest.mock inside pytests, this method allows you to patch specific method call and return whatever you want as a result and you could use built-in things like .call_count, .call_args etc on patched method
Would the validate card and luhn functions be better placed in the CreditCard class? As they’re credit card specific? Thanks very much for the great videos!
Hi. I want to say that I really got a lot more information and views about testing and software, and I was able to program with a better and more accurate view through the 7 steps you mentioned for software design. But I have a request: if it is possible for you to extend this project to "bdd in Python" so that we can make more use of this knowledge Thank you very much, Younes
You mentioned in the previous video that you're testing your own code rather than 3rd party APIs. But in general, I kind of need to do both? There are times where I want to abstract that away and there are times where I'd like to have it. What's the best way to represent this with code?
An even better way to take care of the date issue: Inject a datetime object into the validate_card function. Then you can create unit tests with a fake date.
Excellent video as usual ! A little thought : Sometimes I feel like dataclasses without any methods could really be named tuples. That seems especially the case for the creditCard class, as it would be realistic that the values (number, month, year) should never change once the card is initialized.
You're right, the CreditCard class here could just be a named tuple. There isn't really much difference between a named tuple and a basic dataclass in practice, since both are meant to be simple wrappers for data. I personally prefer the dataclass approach, as I have always just felt that tuples are objects that were poorly defined. Plus, if you use a dataclass then you can easily add things like equality checking and ordering, without needing to define a separate function to handle that behavior correctly. You can just add the appropriate dunder methods to the class so it's all in the same spot.
Hi,@@DeathBean89 thanks for confirming the feeling. I think I understand your point of view. I guess that choosing data class over named tuple allows more flexibility in later developments and if required it is always possible to have it frozen (and make it hashable).
If you go back to Part 1 @9:30, you can see that he has two test methods with the same name in test_order.py. The one that is defined first is not being run, since the second one is overwriting it in the namespace. As a result, 3 lines of test code are not being run and are counted as misses in the report.
Love your content! I know it's off topic, but I like you better in a T-shirt. you just feel more comfortable that way. (congrats on the new studio, btw!)
@arjan, thanks for replying to my previous comment, appreciate it and using it successfully. I had another question, if you have the time. Again on the subject of unit tests :) Described simply I have a class that contains a data cube (3-D) that contains methods used for plotting different 2-D slices of the cube (index and axis, and also a scrollable interactive window). I was wondering if there is an appropriate way to pytest figures/axes objects made by matplotlib. Would I have to monkey patch it / is there a good / standard way of asserting proper figures/axes objects?
how do you get away with pay_order(order) when the function expects pay_order to have 2 positional args order and processor. I tried replicating this pattern in my code and the tests work because I can mock a processor or some function and pass it like pay_order(order, ProccessorMock()) but when I try to run the real code it fails saying I didn't pass a processor. Does that mean you have to import the real Processor function and pass that whenever you want to use pay_order()? So you only get rid of the import dependency for the test, the live code would still need the import?
Hello, Arjan. Thank you for amazing video. You explained most of things about the unit testing. I like your code refactor. I want to ask a question about this video. You used dotenv library to read API_KEY. Can we use .ini file and read this file with configparser library? Which method is more generic?
Hi Ugur, Sure, using an ini file with the key is also possible. You just have to make sure that the file is only placed in locations where it's secure. In systems deployed in the cloud, it's common practice to use environment variables for this. That's why I used a .env file in this example.
Hey Aryan, when you create the PaymentProcessorMock class, aren’t you no longer testing the actual code? But a simplified version of the real class? If you ever change the actual class, won’t you have to change the mock class, thus defeating the purpose of having these tests?
Hi Johnny, the place where I use the PaymentProcessorMock class is in the code where I test the pay_order function. So I use the mock to make testing the original code (which is the pay_order function) a lot easier. As I mention near the end of the video: it’s important to always make sure that you’re testing the right thing at the right level. It’s really easy to make the mistake where you’re testing built in Python functionality, or as you write, you’re testing mock classes and functions instead of the actual code.
Does anyone know how to setup one's environment in WSL so pytest recognizes python 3.10 syntax such as the new match / case stuff? I'm having a heck of a time not finding a solution when I'm pretty sure one exists.
For when you refactor PaymentProcessor.luhn_checksum into a module-level method, what are your thoughts in refactoring it to a staticmethod of the class instead? Arguably the method is logically related to the class and you wouldn't need to create a new PaymentProcessor object to access/test it if it is static, but I could see where having it as a module function allows for more flexibility.
Hey Arjan, did you miss out on the actual use case test for "making a payment via credit card"? I'm not so sure anymore if you added such a test for validating the whole thing 🤔😅
@@Lars16 You can basically put as much code together as you like. If you're creating larger sized components where the payment is just a small submodule, it can be a legitimate "unit test" 😂 So I'd say don't tunnel too much on testing single classes, codefiles or functions. Rather test meaningful behavior that has a real-world use case behind it 😄 If you're just asserting that each line is doing what you've written in your implementation, your tests will be so closely coupled to the code that refactoring will be very painful 😅
When I create an object to replace an injected dependency, I often call it a Fake. But I don't really know the difference between a fake and a mock. Can anyone tell me?
It depends on who you ask. I'd say most people don't care and use them interchangeably. But one way to classify them, for those who draw a distinction: mocks and stubs are two kinds of fakes: a stub is what was shown in this video, and what you're describing: a do-nothing object created to satisfy a dependency. Mocks, on the other hand, are used to test the other code's interaction with the object. For example, the PaymentProcessorMock here is really a stub because it doesn't contribute to testing anything. But if you had it keep track of when its charge() method is called, and at the end of the pay_order test you assert that charge() was called exactly once and with the correct arguments, then it would be functioning more like a mock.
@@mikeciul8599 I'm not familiar with that term. But a quick web search suggests that a spy is a wrapper around the original object to intercept or track its usage. Whereas a mock is more of a brand new object that fakes the behavior while keeping track of its usage.
I am confused. Why lots of your tests are not asserting anything? Virtually you have 100% coverage, but with no asserts you never test if your pay_order function even calls any method of payment procesor. You can delete whole code that is interacting with payment procesor from the function, and your tests will not fail.
I don't understand what you mean. When I look at the tests in the repository, all of them have an assert statement in them, except for the ones where I'm checking that an action raises an error using pytest.raises (in which case adding an assert doesn't make sense). There's one test that doesn't have an assert or a pytest.raises, which is test_charge_card_valid. This one could be wrapped with a 'not pytest.raises' statement, but all the other tests seem fine to me.
Ok. Maybe it is my fault. I looked only into the video, and on UA-cam I don't see these asserts, but on repository they are added. Anyway, issue that I mention seems still existing. You can delete line 17 from payment.py file (charging of card) and tests will still not fail, but the card will not be charged.
Do you have to be a bit careful mocking certain parts of code in your tests? Could that not lead to tests that pass without actually testing the intended functionality? I guess the developer just has to think carefully.
Great points and I agree with your conclusion. To expand on thinking carefully; thinking specifically about what is being tested. Mocking should allow the test to be laser focused on validating that thing by abstracting the other parts that serve as dependencies for the thing to happen. I.E. When “this happens”, my code should react “this way”. I’m mocking to ensure “this happens” and leaving my test to focus on validating the reaction is “this way” as expected. This point also draws the distinction between unit tests and integration tests. A unit test’s purpose is to validate some code reacts as expected when faced with various scenarios, so I control the environment to mock the scenarios. Integration tests focus on the interaction between two components, to ensure if one has changed, you become aware how you have inadvertently broken the other so you’re prompted to fix things and probably also update unit tests. My newbie understanding, anyway.
💡 Get my FREE 7-step guide to help you consistently design great software: arjancodes.com/designguide.
I really admire the way you explain all these concepts. Even the quality of the video itself should not be underestimated: from the way you zoom the source code to the transitions from VS Code to yourself, it is really clear to me how much time and passion you put into this.
This two-parter is a great tutorial. Thanks for talking the time and effort to provide us with this content. It's really high quality.
I'll have to rewatch the second video another time. Around the 8:00 minute mark I had a light bulb moment how I can turn one of my helper functions into a class and instantiate the object as needed. Not only will this improved my unit test, this was actually a helper function with important functionality that I may change depending on the business need. I can see how i may define different classes based on different use cases. Thanks for that.
The video after the 8:00 minute mark was too quick and too dense for me to follow. I'll have to sit down and rewatch what you actually did there. I know this was originally intended as one video but i think you could have spread out the content over 4 videos easily, essentially adding 2 separate videos about refactoring your code. I would have watched those, too! 😂
Great series on testing! I would love it if you would do a video or two going through the deeper features of pytest.
Same here! I would like to see Arjan delve further into unit testing even more
Phenomenal video about software testing, in python and in general. Also, an amazing demonstration of how unit testing can improve your code design. Subscribed!
This was excellent! Thanks for this, I think i'll be watching both videos many times and studying your refactored code so I can properly implement unit tests into my own projects
Awesome! Invaluable learning resource. Thank you Arjan!!!
Glad you liked it!
I am getting deeper understanding about all the core concepts of python for the software development, Great ability to teach complex logic with very ease, Thanks 🙏🏻
Glad to hear the content is helpful!
@@ArjanCodes extensively helpful, in our project we are moving forward with the SOLID principle and you videos helps me to understand everything related it❤️, Thanks
Great work man. Thanks for sharing your expertise in such high quality videos. Pleasure to watch. :)
Glad you enjoy it!
Damn Arjan you really helped my out a lot! Thanks man! I wish i knew about Unit tests BEFORE starting to write my code lol
You're welcome! :)
You are BRILLIANT , thank you for the 2 part videos!
I'm curious about the test cases for payment.py. Shouldn't we test if the payment processor charge method was called when testing for paying the order? Since "test_pay_order" would pass even if we remove line 17 "payment_processor.charge(card, amount=order.total)" in payment.py. Similarly, shouldn't we test if the order status wasn't changed if ValueError was raised?
Amazing video! The last part about env variables was really great because I needed it for a long time and was only defining env variables in terminal using export. Thank you very much for your help!
Thanks so much Paulo, glad it was helpful!
Great video! I'm just binge watching all of them :)
Note: I would consider inverting also dependency to datetime.now(). Thanks to that you can mock in your tests what day is today and test i.e. how PaymentProcessor would behave on Feb 29th or Dec 31st
This is an excellent example, I would just stress how important is to keep in mind the actual functionality of the software, as presented the tests all pass but the main function includes original and non-functional call to pay_order that is missing arguments.
This man is a magician
Very good and helpful, thanks a lot
a little feedback, its kind harder to focus on the code if you zooming on your face like that, would be better to have the code on the screen all the time.
Great video as always,
a side note is that I always wonder what are the shortcuts you keep using on your videos when coding, it really makes your coding looks very smooth and easy, I hope you could cover that in a future video
They sound almost identical to vim shortcuts/keybinds (especially copy/paste for instance -- sounds like he's using the `yy` keybind to copy a line, then `p` to paste it after current line). Most IDEs with a plugin ecosystem have a vim keybindings plugin, so give that a shot. They can take a while to learn, but once you get used to them they really help do things quickly.
Amazing video. So much to take away and put into practice in my current projects. Thank you very much. I’ve watched several videos about TDD, and this if by far the most useful one, I’ve spent my time watching.
Thank you very much!
Thanks, happy that it’s helpful to you!
For dates one package I love is freezegun (and there is the appropriate pytest plugin for it as well) that it allows you to monkeypatch the today date so you do not ever have to worry about dates anymore.
And, another thing, I would argue that hardcoding API keys for testing is fine (given that they are not real api keys) and that you mock the requests to the API itself so you also solve a whole lot of problems with that part specifically. But, with good design, it should be possible to sort that out easily.
Thanks for the tip! And indeed, you won't always need an API key directly in the testing code (I did this in the example as well, using the PaymentProcessorMock).
@@ArjanCodes yeah, yeah, for the video makes sense to do so
I love freezegun, thanks for posting!
Great tutorial. Thanks
Glad it was helpful!
Great!
I worked a lot with dates and timestamps. I suggest to inject time to functions instead of give them the responsability of getting the right time data. Thats makes code more testable and eiser to reuse.
Good suggestion! You could use a fixture for this that delivers the current date/time. Or do you use a separate package for this?
@@ArjanCodes I do not use fixture because function depends heavily on current date/time. It is important to test the behavior for limit values like last day of the month, last day of the year, leap years, etc. In some cases we implemented a function to provide the current time in order to ease mocking for integration tests.
Really informative video! One thing I do for sensitive variables like API keys is to use the keyring module, that way you can avoid any extra files or plaintext storage of secrets.
Another thought, would it not make sense to validate the credit card number as part of the dataclass?
Totally makes sense to me, you can have a property checking the card validity inside the dataclass
Great Video! Wonderful way to explain, it helped me a lot!
Thank you Lucas, glad you liked the video and it was helpful!
As usual, wonderful video and much appreciated!
Thank you, glad you enjoyed the content!
I understand how dependency injection (removing the responsability of creating the dependent objects inside a function/method) makes the tests easier, since it allows passing mocks or stubs directly instead of monkey patching the actual implementation. However, the objects creation was moved to the main function, since they have to be created somewhere. Shouldn't we test it? Would that be an integration test? How does it differ from the unit tests? I always struggle with this because I still end up having to monkeypatch stuff to test the complete flow.
Usually, you don't test "main" at all because you should keep it super simple. But you could add a few integration tests if you still think your "main" is not simple enough ;)
I think the main function here would be what's called the "composition root". Its job is supposed to be the place where you "wire up" your app's dependency tree, so the code within it should as simple as possible. This usually means its not worth testing. But it depends on your specific situation
This is also something I don't understand. Most applications involve the use of many side effects. Moving all side effects to a main function and not testing them does not seem like a realistic solution. Are there any good resources out there that tackle this question?
@@apoca1ypse1 to unit test side effects, you don’t actually test the effects themselves. You just test to make sure that the side effect function was called correctly. You mock the side effect function and run assertions like “mock.wasCalledWithArguments(expected)”
@@vikingthedude I understand that you can mock side effects to test your code. Dependency injection looks to me like a method of moving where you mock rather than how to avoid mocking all together. If my understanding is correct then how do you decide where the best place for mocking is?
I really enjoy watching your great videos. Thank you.
Thanks Abdelkarim, happy you’re enjoying the content!
@arjan love your videos they are both informative and fun and have helped me a lot. I’m currently working on writing unit tests and am using pytest. I was wondering what the advantage was of using a pytest fixture as opposed to just defining a constant with the object at the top of the unit test script?
Hi Dirk, glad to hear you like the videos! If you use a fixture, a fresh, new object is created for each test. This is better than using a constant, which creates an object only once, when loading the script. You don't want that one test accidentally influences another test by changing the object that both tests rely on.
Excellent question Dirk, I was wondering the exact same thing. Thanks for the answer Arjan!
@@ArjanCodes Thanks a lot for the videos, Arjan-I've been learning tons.
One thing I like to do sometimes is define a _function_ that returns a fresh object--almost like a constructor.
Similar to the current setup in the video, just without the decorator.
Works just as well as a pytest fixture, but is a bit more explicit (in terms of linting support), I feel.
I asked this last week, but I still want to know - what are your thoughts on unittest vs pytest? Who here uses which one, and why do you prefer it to the other? I use both together sometimes, but I worry that this could lead to trouble. What do you think?
I'll share my experience, maybe it will be useful. I started with pytest but then switched to unittest because grouping related things under a TestCase seemed very nice and mainainable. But I still used "assert" and "pytest.raises" because they are prettier ;) but then I discovered that when a project grows, TestCases become in some way noisy. Also, you will always find out that, for example, "self" is not used (i.e. it could be a simple function). The next step for me was to return back to plain functions. I replaced TestCases by more complex packages/modules hiararchy and in the end it seems much better. About pytest fixtures... I tried them hard but in the end I abandoned it. I find them not flexible enough because they don't accept parameters (ofc you can make them to accept parameters but it looks hacky). So I use helper functions usually stored under tests/utils package. The only thing I use fixtures for is setup/teardown. And almost always those are fixtures with "session" scope and autouse=True located under conftest py. For example, loading of dummy SQL to a database.
Pytest is much bigger, have more features to use, and good solution as well, has good documentation and support. Unittest Library is kind of local or small story and pytest is a complete solution for really big and complex things
And of course, even into pytest documentation you could find some things from unittest like patch for example
@@sulfur32066 That makes me think I'm missing out on a lot of what pytest can do. I like unittest `self.assertEqual`, `assertIn`, `assertRaises`, and 'assertLogs`... I'm not sure I know the idiomatic way to do all of those things in pytest, and I'm not sure what kind of context it gives when assertions fail - pytest users, do you ever test for log statements? Is there a pytest way?
I also like using `Mock.assert_called_with`. Since I started watching this channel, I've been replacing patching with dependency injection. But I still want to assert that the right calls were made, so I'm still using unittest.Mock... what are your thoughts?
@@mikeciul8599 for such cases I personally prefer to use patch from unittest.mock inside pytests, this method allows you to patch specific method call and return whatever you want as a result and you could use built-in things like .call_count, .call_args etc on patched method
Would the validate card and luhn functions be better placed in the CreditCard class? As they’re credit card specific?
Thanks very much for the great videos!
Yes, good suggestion, that makes more sense than leaving it with the payment processor as it is now.
For .env you can also use python-decouple.
Thanks for sharing that, looks nice!
Hi. I want to say that I really got a lot more information and views about testing and software, and I was able to program with a better and more accurate view through the 7 steps you mentioned for software design.
But I have a request: if it is possible for you to extend this project to "bdd in Python" so that we can make more use of this knowledge
Thank you very much, Younes
You mentioned in the previous video that you're testing your own code rather than 3rd party APIs. But in general, I kind of need to do both? There are times where I want to abstract that away and there are times where I'd like to have it. What's the best way to represent this with code?
Great content! Thank's a lot.
Thank you so much!
An even better way to take care of the date issue: Inject a datetime object into the validate_card function. Then you can create unit tests with a fake date.
Excellent video as usual ! A little thought : Sometimes I feel like dataclasses without any methods could really be named tuples. That seems especially the case for the creditCard class, as it would be realistic that the values (number, month, year) should never change once the card is initialized.
You're right, the CreditCard class here could just be a named tuple. There isn't really much difference between a named tuple and a basic dataclass in practice, since both are meant to be simple wrappers for data. I personally prefer the dataclass approach, as I have always just felt that tuples are objects that were poorly defined.
Plus, if you use a dataclass then you can easily add things like equality checking and ordering, without needing to define a separate function to handle that behavior correctly. You can just add the appropriate dunder methods to the class so it's all in the same spot.
Hi,@@DeathBean89 thanks for confirming the feeling. I think I understand your point of view. I guess that choosing data class over named tuple allows more flexibility in later developments and if required it is always possible to have it frozen (and make it hashable).
Great stuff man, always helpful.
What's up with those 82% coverage at 16:51 tho?
IIRC there were two test-methods with same name in the last video.
If you go back to Part 1 @9:30, you can see that he has two test methods with the same name in test_order.py. The one that is defined first is not being run, since the second one is overwriting it in the namespace. As a result, 3 lines of test code are not being run and are counted as misses in the report.
Hey! Can you make a video about proxy and decorator patterns?
Love your content! I know it's off topic, but I like you better in a T-shirt. you just feel more comfortable that way. (congrats on the new studio, btw!)
Haha, yes I went fancy for the first batch of videos in my new studio. I'm going back to t-shirts and hoodies for the next batch ;).
@arjan, thanks for replying to my previous comment, appreciate it and using it successfully. I had another question, if you have the time. Again on the subject of unit tests :) Described simply I have a class that contains a data cube (3-D) that contains methods used for plotting different 2-D slices of the cube (index and axis, and also a scrollable interactive window). I was wondering if there is an appropriate way to pytest figures/axes objects made by matplotlib. Would I have to monkey patch it / is there a good / standard way of asserting proper figures/axes objects?
how do you get away with pay_order(order) when the function expects pay_order to have 2 positional args order and processor. I tried replicating this pattern in my code and the tests work because I can mock a processor or some function and pass it like pay_order(order, ProccessorMock()) but when I try to run the real code it fails saying I didn't pass a processor. Does that mean you have to import the real Processor function and pass that whenever you want to use pay_order()? So you only get rid of the import dependency for the test, the live code would still need the import?
Hello, Arjan. Thank you for amazing video. You explained most of things about the unit testing. I like your code refactor. I want to ask a question about this video. You used dotenv library to read API_KEY. Can we use .ini file and read this file with configparser library? Which method is more generic?
Hi Ugur, Sure, using an ini file with the key is also possible. You just have to make sure that the file is only placed in locations where it's secure. In systems deployed in the cloud, it's common practice to use environment variables for this. That's why I used a .env file in this example.
What would be the argument of using pytest vs unittest?
At 15:40, the line `API_KEY = os.getenv("API_KEY") or ""`
New to me, and pretty cool.
Is there documentation for this, or some resource explaining it?
Hey Aryan, when you create the PaymentProcessorMock class, aren’t you no longer testing the actual code? But a simplified version of the real class? If you ever change the actual class, won’t you have to change the mock class, thus defeating the purpose of having these tests?
Hi Johnny, the place where I use the PaymentProcessorMock class is in the code where I test the pay_order function. So I use the mock to make testing the original code (which is the pay_order function) a lot easier. As I mention near the end of the video: it’s important to always make sure that you’re testing the right thing at the right level. It’s really easy to make the mistake where you’re testing built in Python functionality, or as you write, you’re testing mock classes and functions instead of the actual code.
Thanks!
You’re welcome, glad you enjoyed it!
Great video! I guess using freeze_gun for datetime would be better than your trick with date, wdyt?)
Great suggestion, that's indeed a good, generic solution to dealing with dates in tests.
You are great!
It’s amazing…
Does anyone know how to setup one's environment in WSL so pytest recognizes python 3.10 syntax such as the new match / case stuff? I'm having a heck of a time not finding a solution when I'm pretty sure one exists.
Thanks!
Thank you Samuel!
What about saving API keys in a separate json or yaml file?
Sure, that would work too. The most important thing is to make sure you don't commit the file to the repository.
For when you refactor PaymentProcessor.luhn_checksum into a module-level method, what are your thoughts in refactoring it to a staticmethod of the class instead? Arguably the method is logically related to the class and you wouldn't need to create a new PaymentProcessor object to access/test it if it is static, but I could see where having it as a module function allows for more flexibility.
If anything it should be moved to CreditCard class, perhaps even as creation time validator.
Hey Arjan, did you miss out on the actual use case test for "making a payment via credit card"? I'm not so sure anymore if you added such a test for validating the whole thing 🤔😅
Wouldn't that be an integration test? These videos have focused on unit tests.
Idea for a next video? How can you be sure, that the refactored code is working as intended?
@@Lars16 You can basically put as much code together as you like. If you're creating larger sized components where the payment is just a small submodule, it can be a legitimate "unit test" 😂
So I'd say don't tunnel too much on testing single classes, codefiles or functions. Rather test meaningful behavior that has a real-world use case behind it 😄
If you're just asserting that each line is doing what you've written in your implementation, your tests will be so closely coupled to the code that refactoring will be very painful 😅
@@troncooo409 Haha yeah, that's always a good starting point 😂
When I create an object to replace an injected dependency, I often call it a Fake. But I don't really know the difference between a fake and a mock. Can anyone tell me?
It depends on who you ask. I'd say most people don't care and use them interchangeably. But one way to classify them, for those who draw a distinction: mocks and stubs are two kinds of fakes: a stub is what was shown in this video, and what you're describing: a do-nothing object created to satisfy a dependency.
Mocks, on the other hand, are used to test the other code's interaction with the object. For example, the PaymentProcessorMock here is really a stub because it doesn't contribute to testing anything. But if you had it keep track of when its charge() method is called, and at the end of the pay_order test you assert that charge() was called exactly once and with the correct arguments, then it would be functioning more like a mock.
@@ChrisMasto Is that also called a spy?
@@mikeciul8599 I'm not familiar with that term. But a quick web search suggests that a spy is a wrapper around the original object to intercept or track its usage. Whereas a mock is more of a brand new object that fakes the behavior while keeping track of its usage.
I am confused. Why lots of your tests are not asserting anything? Virtually you have 100% coverage, but with no asserts you never test if your pay_order function even calls any method of payment procesor. You can delete whole code that is interacting with payment procesor from the function, and your tests will not fail.
I don't understand what you mean. When I look at the tests in the repository, all of them have an assert statement in them, except for the ones where I'm checking that an action raises an error using pytest.raises (in which case adding an assert doesn't make sense). There's one test that doesn't have an assert or a pytest.raises, which is test_charge_card_valid. This one could be wrapped with a 'not pytest.raises' statement, but all the other tests seem fine to me.
Ok. Maybe it is my fault. I looked only into the video, and on UA-cam I don't see these asserts, but on repository they are added. Anyway, issue that I mention seems still existing. You can delete line 17 from payment.py file (charging of card) and tests will still not fail, but the card will not be charged.
Do you have to be a bit careful mocking certain parts of code in your tests? Could that not lead to tests that pass without actually testing the intended functionality? I guess the developer just has to think carefully.
Great points and I agree with your conclusion. To expand on thinking carefully; thinking specifically about what is being tested. Mocking should allow the test to be laser focused on validating that thing by abstracting the other parts that serve as dependencies for the thing to happen. I.E. When “this happens”, my code should react “this way”. I’m mocking to ensure “this happens” and leaving my test to focus on validating the reaction is “this way” as expected. This point also draws the distinction between unit tests and integration tests. A unit test’s purpose is to validate some code reacts as expected when faced with various scenarios, so I control the environment to mock the scenarios. Integration tests focus on the interaction between two components, to ensure if one has changed, you become aware how you have inadvertently broken the other so you’re prompted to fix things and probably also update unit tests. My newbie understanding, anyway.
@@leaky3955 Good approach I think. From a newbie, also
Thanks!
Thanks so much Curt, glad you liked it!