If the time test is performed manually, it can be annoying to change the sysdate
, and bugs are likely to be mixed in.
So I coded the test, ran it more easily, and tried to improve the quality.
The execution environment is as follows
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.15.1
BuildVersion: 19B88
$ python --version
Python 3.7.4
Also, for a quick test around the time, try using a package called freezegun.
You can use this package called freezegun
to replace the current time obtained from datetime
in the standard library of Python
with the specified one.
When testing the processing around the time, of course, I want to test using various times. However, running the test while changing the system time just for that is annoying.
So if you use freezegun
, you won't have to do that.
Install the package from the pip command.
$ pip install freezegun
In freezegun
,freezegun.freeze_time ()
can be replaced so that the function of the datetime
module returns a specific time.
The sample code looks at the contents of datetime.now ()
.
app_freezegun.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import freezegun
from datetime import datetime
def main():
#Replace and display current time using freezegun
freezer = freezegun.freeze_time('2015-10-21')
freezer.start()
try:
print("freezegun:" + str(datetime.now()))
finally:
freezer.stop()
#Show current time
print("nowtime:" + str(datetime.now()))
if __name__ == '__main__':
main()
Do the above.
$ python app_freezegun.py
freezegun:2015-10-21 00:00:00
nowtime:2019-12-14 10:16:49.847317
You can see that the current time has been replaced while using the freezegun.
Next is a pattern that uses the API as a decorator. The time is replaced only within the decorator-qualified function.
app_freezegun.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import freezegun
from datetime import datetime
#Replace and display current time using freezegun
@freezegun.freeze_time('2015-10-21')
def main():
print("freezegun:" + str(datetime.now()))
#Show current time
def main_2():
print("nowtime:" + str(datetime.now()))
if __name__ == '__main__':
main()
main_2()
Do the above.
$ python app_freezegun.py
freezegun:2015-10-21 00:00:00
nowtime:2019-12-14 10:16:49.847317
You can see that the current time in the decorator-qualified function has been replaced. Of course, you can also specify the time.
app_freezegun.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import freezegun
from datetime import datetime
#Replace current time using freezegun
@freezegun.freeze_time('2015-10-21 12:34:56')
def main():
print("freezegun:" + str(datetime.now()))
if __name__ == '__main__':
main()
The result is as follows.
$ python app_freezegun.py
freezegun:2015-10-21 12:34:56
Earlier I specified the time using a decorator. The same API can also be used as a context manager. The following is the pattern used as the context manager. In this case, the time is replaced only within the context manager block.
app_freezegun.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import freezegun
from datetime import datetime
def main():
#Replace and display current time using freezegun
with freezegun.freeze_time('2015-10-21'):
print("freezegun:" + str(datetime.now()))
#Show current time
print("nowtime:" + str(datetime.now()))
if __name__ == '__main__':
main()
The result is the same as [4.], so it is omitted.
When conducting a test, I think there is a need to once set the time as a reference time and then shift the time after performing specific processing. Try using freezegun
.
In the following sample code, tick ()
is used to shift the time by the timedelta
object, andmove_to ()
is used to switch the time.
app_freezegun.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import freezegun
from datetime import datetime
from datetime import timedelta
def main():
with freezegun.freeze_time('2015-10-21 00:00:00') as freeze_datetime:
print(datetime.now())
#Advance time by 1 second
freeze_datetime.tick()
print(datetime.now())
#Advance the time by 1 minute
freeze_datetime.tick(delta=timedelta(minutes=1))
print(datetime.now())
#Move to a specific time
freeze_datetime.move_to('2019-01-01 00:00:00')
print(datetime.now())
if __name__ == '__main__':
main()
Do the above.
$ python app_freezegun.py
2015-10-21 00:00:00
2015-10-21 00:00:01
2015-10-21 00:01:01
2019-01-01 00:00:00
I verified various things, but here is the actual production.
Use freezegun
in ʻunittest. I want to test what is created automatically using
datetime`, so mock AWS EC2.
First, prepare the app.
Get the created EC2, get the one older than the execution date and time of the application, and return the instance name.
app_freezegun.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import boto3
from datetime import datetime, timedelta, timezone
def main():
#Get the current time
now = datetime.now(timezone.utc)
#Initialize instance name as an array
instace_names = []
client = boto3.client('ec2')
#Get all instance information.
instances = client.describe_instances()
#Get the instance created before the execution date.
for instance_list in instances.get('Reservations'):
for instance in instance_list.get('Instances'):
if now > instance.get('LaunchTime'):
#Extract the instance name.
instace_names.append(instance.get('KeyName'))
return instace_names
Next is the test code.
Use freezegun
to mock 4 EC2s with different creation dates.
test_freezegun.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import unittest
import boto3
from moto import mock_ec2
import freezegun
from datetime import datetime, timedelta
from app import app_freezegun as app
class MyTestCase(unittest.TestCase):
@mock_ec2
def test_case_1(self):
with freezegun.freeze_time('2017-01-01 00:00:00') as freeze_datetime:
#Created date 2017-01-Create EC2 with 01
self.__common('test_ec2_name_1')
freeze_datetime.move_to('2018-01-01 00:00:00')
#Creation date 2018-01-Create EC2 with 01
self.__common('test_ec2_name_2')
freeze_datetime.move_to('2019-01-01 00:00:00')
#Creation date 2019-01-Create EC2 with 01
self.__common('test_ec2_name_3')
freeze_datetime.move_to('2020-01-01 00:00:00')
#Creation date 2020-01-Create EC2 with 01
self.__common('test_ec2_name_4')
#Run the app
instance_names = app.main()
#Check the result
self.assertEqual(instance_names, ['test_ec2_name_1', 'test_ec2_name_2', 'test_ec2_name_3'])
def __common(self, name):
client = boto3.client('ec2')
#Set the conditions for EC2 to be created
ec2objects = [
{'KeyName': name}
]
#Create EC2
for o in ec2objects:
client.run_instances(
ImageId='ami-03cf127a',
MinCount=1,
MaxCount=1,
KeyName=o.get('KeyName'))
if __name__ == '__main__':
unittest.main()
Try to run it.
$ python -m unittest tests.test_freezegun -v
test_case_1 (tests.test_freezegun.MyTestCase) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.387s
OK
I did well.
Recommended Posts