Author: Jason Diamond
Translator: Wu Haiyan
Originally published Date:
12/02/2004
Translation date:
2/17/2005
Original file location: http://www.onlamp.com/pub/a/python/2004/12/02/tdd_pyunit.html
l introduction
l Python's UnitTest module
L motivation
l Sample input
l Let us start
l Advance
l reconstruct
l summarize
Introduction
Test drive development is not a test. Test driver development is development (and design), mainly used to improve the quality of design and code. The unit test sample is only a useful by-product it produces.
The above is what I want to tell you about test-driven things. The rest of this article is to tell you how the test driver is working. Now start to develop a small tool with me, here we will encounter an error, solve the mistake and change our design according to the test results, and we will use the reconstruction, design pattern and object-oriented ideas. In order to make our development more convenient, we use Python to do this development.
Python is very easy to test drive development, because he can work well without hindering our work. Even we don't need any additional modules to do test driver development, because the modules needed are already included in the standard release.
In this article I assume that you are familiar with Python, but you don't ask for familiarity with the test drive development or Python's UnitTest module. In fact, this article does not ask you to know how much ^ _ ^
Python's UnitTest module
Since the 2.1 version start, Python's standard library already contains the UnitTest module, this module is based on JAVA's unit test standard framework JUnit (Kent Beck and Erich Gamma). Before 2.1, this module is called PyUnit and needs to be downloaded separately.
Let's get started, here there is a function and his test --- in a file.
Import UnitTest
# 这 这
Def isodd (n):
Return n% 2 == 1
# 这 儿 单 单 测试
Class isoddtests:
DEF TESTONE (SELF):
Self.Failunless (isoD (1))
Def TestTWO (Self):
Self.Failif (isodd (2))
Def main ():
UnitTest.main ()
IF __NAME__ == '__main__':
Main ()
In this article, I will use traffic lights to indicate test results. Green represents test, red warnings our test failed. And a bright yellow light indicates a problem, so that we can't complete the test. TDD developers usually talk about the green light or green strip in the JUnit graphics
In the class inherited from UnitTest.TestCase, starting with Test, there is a method of parameters called Self, we call Test Cases. In the above example, Testone and TestTwo are two Test Cases
Put the associated Test Cases into a class inherited from UnitTest.TestCase, which we call Test fixTure, isoDtest is a Test fixture, although this class is inherited from Testcase, not from TestFixTure.
Test fixture can include Setup and Teardown methods, which are running before the TEST CASE runs before and after running. In FixTures allows the SETUP method to be a wise decision, because so we can transfer the public initialization code from each Test Cases to Setup. In the Python program, we usually do not need a Teardown method because we usually rely on Python's garbage collection system to collect useless objects. But when we test the database, Teardown is useful, we can use it to turn off the connection, delete the table, and more.
Let's go back to the head to re-look at the main function, this function defined in the UnitTest module allows us to perform tests with the same way that calls other scripts, this function checks sys.argv, from defining test output results or only execute only Specify FixTures or Cases (using -help to get help). The default case will execute all of FixTures in a file containing the UnitTest.main () function.
Execute the above test script We will get the following results:
.
-------------------------------------------------- --------------------
Ran 2 Tests in 0.000s
OK
If the second test fails, then we will get below:
.F
============================================================================================================================================================================================================= ====================
Fail: testtwo (__main __. Isoddtests)
-------------------------------------------------- --------------------
TRACEBACK (MOST RECENT CALL LAST):
File "c: /jason/projects/tdd-py/test.py", LINE 14, in TestTwo
Self.Failif (isodd (2))
File "c: /python23/lib/Unittest.py", Line 274, In Failif
IF expr: raise self.failureexception, msg
Assertionerror
-------------------------------------------------- --------------------
Ran 2 Tests in 0.000s
Failed (failures = 1)
Under normal circumstances, we will not put the test program and the test program in a file, but don't have any harm when you start, after all, we can also reconstruct the code.
motivation
Imagine I am easy to forget:
0 0 * * * [`Date /% M` -ne` Date -D 4DAYS /% M`] /
&& mail -s 'pay the rest!' me-nd-my-wife@example.org dev / null Some confusing code makes me abstract from my crontab, it is in each I sent an email in the last four days of the month to pay my rent. Is it very miserable? Maybe, but after I use him, I have never been late.
Of course, this is not suitable for all situations - especially when the notice only occurs once. Moreover, I have no way to teach my wife to add a notice using the Bash script.
Most people use old-fashioned calendars to do such things, but this way is too old to me.
I can use Outlook or Evolution or some other programs, but it will bring another trouble, because we don't just use a computer. We use multiple machine calculations and different operating systems at home and the company, how can I be able to synchronize between these computers? In my project, I found that I can use email, no matter which machine I use, what operating system, I can check my email through IMAP, so I can notify me to pay for it through email.
So why not use Calendar as I inform to use Calendar to inform me what is about to happen? Moreover, I also know which programs can do this: BSD Calendar program and new PAL program.
My wife and I have a private wiki used to record the notes, that is very easy. Although my wife is a accountant rather than a technician, she can use her without barrier. I pointed out that we can use wiki to edit our calendar file, then I can write a planned task to pick this calendar file from Wiki - I use WGET - then transferred to my selected tool. Unfortunately, when I studied Calendar and Pal carefully, I found that they didn't want what I would.
Calendar's file requires the use of
And the PAL's format is too difficult to master, and he does not support an important function I want: notice in the last few days of each month.
Sample input
My wife and I sit down and discuss the format we will use. Here is some examples
30 Nov 2004: DINNER with the DARGHAMS.
April 10: Happy Anniversary!
Wednesday: Piano Lesson.
Last Thursday: Goody Night At Book Study. Yum.
-1: pay the rest!
Unlike the format of Calendar, this is used to separate the date and description with ':'. The same is the same as Calendar, the omitted field is represented using wildcard. April
10 "
The event happens at this time every year, "Last Thursday" event happened every Thursday every year, "
-1"
Time happened in the last day of the month, this is inspiration from Python's Array's syntax, foo [-1] selects the last element in the foo array, maybe this is a bit of disintegration, but my wife will soon accept It is.
My goal is to write a small program, then run the file to read this format using the planned task, and then send me an email and my wife in the first seven days before the event. This is not difficult, isn't it?
Below we will enter the process of writing, note that I am not writing the program first and then write this article, I am writing this article while writing the program. Yes, I will make mistakes in the following process, and I also prepared for mistakes, in fact, mistakes are a good learning method in some extent. let's start
Test driver means that I have to write a test code for this code before starting writing code. Whenever I started a new project, I always create a FixTure that will fail. As follows:
Import UnitTest
Class Footests (UnitTest.testcase):
DEF TESTFOO (Self):
Self.Failunless (False)
Def main ():
UnitTest.main ()
IF __NAME__ == '__main__':
Main ()
I am used to do this, because this ensures that the test program is running correctly.
Notice that the above class name is Footests, and there is a method called Testfoo. I don't know what I want to test at the current position. I just make sure I have prepared everything. Let us start testing the development day, month, years, in order to create a test program, I need to know what to test. I need a class, or only one function? Now that we take the hat to think, we can design something according to our previous experience, this will have no relationship, because the test will be too many errors in us. Tell us that we don't need those designs with icons, these work we can stay to our exact knew how to do it.
In this project, I will create an object that matches a specific date. These objects play a "mode" role, of course, I need to write an analyzer to read the calendar file and create these mode objects, but these we Leave it to do it later, let us first complete these mode objects.
We may need a variety of different mode objects - but I am not thinking so, because it may be wrong. On the contrary, I will start coding first, then tell me how to do it.
DEF TestMatches (Self):
P = DatePattern (2004, 9, 28)
D = datetime.date (2004, 9, 28)
Self.Failunless (p.matches (d))
Because I already know what I need to test, so I rename Testfoo to TestMatches to better reflect the test purpose. At the same time, I also define a class name DatePattern and a method Matches (DateTime module is already included in the standard module above version 2.3, only need to include this module).
This test will fail, because I haven't realized the datepattern class, but I already know that I need to implement the class, and I also know the parameter requirements of the __init__ method, the following is based on the test reflection Code
Class datepattern:
DEF __INIT __ (Self, Year, Month, DAY):
PASS
Def matches (self, date):
Return True
Now the test passed, it is time to write the next test.
You may think that I am joking, but in fact this is true.
Small step forward
Under the case of small steps, the test drive development is the most appropriate. You only need to write code to pass the failed test, once the test passed, then we have completed the encoding work, should stop.
The previous code is not too much because he matches any date. How do I judge the above program has implemented the features I want, it is very simple, add this test: DEF TestMatchesfalse (Self):
P = DatePattern (2004, 9, 28)
D = datetime.date (2004, 9, 29)
Self.Failif (p.matches (d))
Now we have not passed the test.
I can modify the Matches method, let him return false values, but doing this will make the previous test procedure could not pass, there is no way, I have to go correctly to implement DatePattern this class. Below is the code after modification:
Class datepattern:
DEF __INIT __ (Self, Year, Month, DAY):
Self.date = datetime.date (Year, Month, Day)
Def matches (self, date):
Return Self.Date == Date
Test all passed, very good! But I am not satisfied with the DatePattern class, because so far, he is just a simple packaging of Python's DateTime module. In this case, why don't I use the DateTime module directly?
Perhaps DatePattern doesn't use it, but I don't plan to decide whether this class is useless. On the contrary, I will write another test - I use it to affirm the DatePattern class has a necessary test.
Def TestmatchesyearasWildcard (Self):
P = datepattern (0, 4, 10)
D = datetime.date (2005, 4, 10)
Self.Failunless (p.matches (d))
Great, this test failed!
Why is this test failure? Because: he proved that the datepattern class has not been fully implemented, he is not able to only pack the DateTime module, which is not enough.
Just when writing this test, I decided to use 0 to represent wildcard, because there is no one year, month, the day will be 0.
There will be no more than this better, I decided to use him from now.
Let us now write code to pass the test:
Class datepattern:
DEF __INIT __ (Self, Year, Month, DAY):
Self.year = year
Self.month = month
Self.day = day
Def matches (self, date):
Return ((self.year and self.year == Date.year or true) and
SELF.MONTH == Date.Month and
SELF.DAY == Date.day)
To be honest, I have already felt that I need to be reconstructed to add more wildcard functions to this class, but before this, we will first write more tests.
Let's add a test to verify the case of using wildcard:
Def TestmatchesyearandmonthasWildCards (Self):
P = datepattern (0, 0, 1)
D = datetime.date (2004, 10, 1)
Self.Failunless (p.matches (d))
Then we modify the Matches method to pass: Def matches (self, date):
Return ((self.year and self.year == Date.year or true) and
(Self.Month and Self.month == Date.Month or true) and
SELF.DAY == Date.day)
It can be seen that this method is getting more and more beautiful, it seems that our first needs to be reconstructed is him.
Now I have tested the test for testing the aura and the moon, then I still have to add the consecutive wildcards? A matching object that only wildcards will match any day, I can't think of this, so I will no longer write such a test. In this way, I will not go to realize the DataPattern classes that don't need, remember that the code only needs to be written under the test failure, so that we will prevent our program to prevent our programs complex.
Now let us continue, we need to support the model object to develop a week:
Def Testmatchesweekday (Self):
P = datepattern
So what do we do now?
At this time, I have already made the DataPattern class not to meet the requirements of this test, and his __init__ method is not able to accept this parameter on Sunday. I should use another class or modify this now?
At present, I only intend to modify this in front of you, because this requirement is the smallest, if the fact proves that this is not good, I can also refactor him.
Def Testmatchesweekday (Self):
P = datepattern (0, 0, 0, 2) # 2 is Wednesday
D = datetime.date (2004, 9, 29)
Self.Failunless (p.matches (d))
Because DatePattern's __init__ is not able to accept 5 parameters, this test failed. So I modified the __init__ method as follows:
DEF __INIT __ (Self, Year, Month, Day, Weekday = 0):
Self.year = year
Self.month = month
Self.day = day
Self.weekday = weekday
I gave the parameter of Weekday to the default, so I can use other Test Case. In addition to the new Test Case, others have passed the test.
Smart readers must have noticed that I have passed the 0 wildcard to "God" parameters, and this is what I have previously thought that there is no need, but now I need it, so I have modified the Matches method as follows:
Def matches (self, date):
Return ((self.year and self.year == Date.year or true) and
(Self.Month and Self.month == Date.Month or true) and
(Self.day and self.day == Date.day or true) and
(self.weekday and self.weekday == Date.weekday () or true))
Now all parameters allow receiving wildcards, interesting, new methods do not pass Testmatchesfalse all tests, except what is wrong? Reconstruct
Now I can't see why TestMatchesfalse will fail with the code code. This needs to be debugged. Unfortunately, I put all the logic processing of the Matches method in an expression (occupied 4 lines) so I don't have place to insert the print statement to help me locate the wrong location. It seems that it is time to refactor. Here I want to use the reconstruction method is the Compose Method, which is mentioned in Joshua Kerievsky's book, and decompose Method, which is divided into each smaller method from the Matches method. Matches can be easier to understand and more easily debugged.
The following is the result after modification:
Def matches (self, date):
Return (Self.Yearmatches (Date) and
Self.Monthmatches (Date) and
Self.daymatches (Date) and
Self.WeekdayMatches (Date))
Def Yearmatches (Self, Date):
IF not self.year: return True
Return self.Year == Date.Year
DEF MONTHMATCHES (Self, Date):
IF not self.month: return true
Return self.month == Date.month
Def Daymatches (Self, Date):
IF not self.day: return true
Return self.day == Date.day
Def WeekdayMatches (Self, Date):
IF not self.weekday: return true
Return self.weekday == Date.Weekday ()
You should have discovered that the Matches method is now easier to understand. It seems that there is no need to do this, but the clear code is much better than writing "smart" code. It is because I played a little smart, causing a bug, if I did this from the beginning, I won't commit this mistake.
After the reconstruction, I originally thought that TestMatchesfalse will still fail, but it passed the test. In my start of the program, I made a mistake, and I didn't find this error - I found this wrong task left to the reader as a practice. Now, not only is a simpler code, and he can work normally, this is fine.
If you don't use a test, can I find this mistake? I think it should be discovered, but I have to find out how long I don't know, but I use test, I immediately discovered this mistake, and I also know how to fix him.
Wildcards have been working properly before this test, which is good, but I want the following test to make wildcards can't pass. code show as below:
DEF TestMatcheslastweekday (Self):
P = datepattern (0, 0, 0, 3
Now, I don't know how to do it.
This looks less understanding (in fact, why can Python's DateTime module define constants for some weeks?) 3 represents Thursday.
How do I say that I just want to match the last Thursday of a month? May I have to add a parameter to the datepattern .__ init__ method? Looking at more and more parameters, add it in the class, I started to feel that I have added too much in DatePattern.
to sum up
I haven't written too much code yet, this is a good thing! Because I have already felt that the code I have written is not very good, I can't fully meet my requirements, I don't know when I can find this. Now, I haven't spend much time on the datepatter class, so if I have necessary, I can throw DatePattern.
I have some restrictions on how to make him more simple and more in line with my request, but that is going to wait until the second part of this article, I will release the second part in the shortest time. from.
Code and tests can be downloaded and checked
Jason Diamond is in the south of California, he is a consultant from C , C # and XML
Wu Haiyan lived across Beijing Jiaotong University, opposite the ocean hall. Now use the Beijing New Internet Internet Company WINDOWS operation and maintenance department engineer. Specialized in Windows virtual host management, is very interested in Python and Linux.
The translator's words:
I will translate a 10 page article every week. Most of them are Python, the second part of this article will be completed next week. The third part of this article has not been written yet, and it may wait until I have finished the second part, I will finish ^ _ ^.
This is the first article. The mistakes are inevitable, I also want to improve my own translation level through the translation article, which will get better and better. In fact, this article is tight, I am not satisfied with ^ _ ^!