啊。这是丑陋的。美丽胜于丑陋。我不再主要是 Python 程序员,所以可能有更好的工具来解决这个问题。但是让我们从概念层面来解决这个问题。
这是标准的命令式编程,使问题过于复杂。它很容易迷失在实施噪音中,比如你遇到的问题。它使您无法只见树木不见森林。让我们尝试另一种方法。
让我们专注于我们需要做的事情,并让实施由此产生。首先我们知道我们需要从文件中读取。
从文件中读取
Scenario: Calculate totals within region database
Feature: Read from database
As a user, in order to be able to view the total sales of my books and differentiate them by fiction and nonfiction, I want to be able to read data from a file.
Given: I have a file that has region data, for example data.text
When: I load data from it
Then: I should have associated region data available in my program.
这是作为测试用例的 Python 实现:
import unittest
class RegionTests(unittest.TestCase):
def testLoadARegionDatabase(self):
"""Given a region file,when I load it, then it should be stored in memory"""
# Given region database
regionDatabase = []
# When I load it
with open('./regions.txt','r') as f:
regionDatabase = f.readlines()
# Then contents should be available
self.assertTrue(len(regionDatabase) > 0)
从文件中获取区域数据
从概念上讲,我们知道该文件中的每一行都是有意义的。从根本上说,每一行都是一个Region。我们在文件中存储了代码、虚构销售、非虚构销售和税率。区域的概念在我们的系统中应该有一个明确的、一流的表示,因为Explicit 比 Implicit 更好。
Feature: Create a Region
As a user, in order to be able to know a region is information--including nonfiction sales, fiction sales, and tax rate-- I want to be able to create a Region.
Given: I have data for fiction sales, non-fiction sales, and tax rate
When: I create a Region
Then: Its sales, non-fiction sales, and tax-rate should be set accordingly
这是作为测试用例的 Python 实现:
def testCreateRegionFromData(self):
"""Given a set of data, when I create a region, then its non-fiction sales, fiction sales,and tax rate should be set"""
# Given a set of data
texas = { "regionCode": "TX", "fiction" : 415, "nonfiction" : 555, "taxRate" : 0.55 }
# When I create a region
region = Region(texas["regionCode"], texas["fiction"], texas["nonfiction"], texas["taxRate"])
# Then its attributes should be set
self.assertEquals("TX", region.code)
self.assertEquals(415, region.fiction)
self.assertEquals(555, region.nonfiction)
self.assertEquals(0.55, region.taxRate)
这失败了。让我们让它过去吧。
class Region:
def __init__(self, code, fiction, nonfiction,rate):
self.code = code
self.fiction = fiction
self.nonfiction = nonfiction
self.taxRate = rate
分析总计
现在我们知道我们的系统可以表示区域。我们想要一些可以分析大量地区并为我们提供销售汇总统计数据的东西。让我们称其为Analyst。
Feature: Calculate Total Sales
As a user, in order to be able to know what is going on, I want to be able to ask an Analyst what the total sales are for my region
Given: I have a set of regions
When : I ask my Analyst what the total sales are
Then : The analyst should return me the correct answers
这是作为测试用例的 Python 实现。
def testAnalyzeRegionsForTotalNonFictionSales(self):
"""Given a set of Region, When I ask an Analyst for total non-fiction sales, then I should get the sum of non-fiction sales"""
# Given a set of regions
regions = [ Region("TX", 415, 555, 0.55), Region("MN", 330, 999, 0.78), Region("HA", 401, 674, 0.99) ]
# When I ask my analyst for the total non-fiction sales
analyst = Analyst(regions)
result = analyst.calculateTotalNonFictionSales()
self.assertEquals(2228, result)
这失败了。让我们让它过去吧。
class Analyst:
def __init__(self,regions):
self.regions = regions
def calculateTotalNonFictionSales(self):
return sum([reg.nonfiction for reg in self.regions])
您应该能够从这里推断小说的销售情况。
决定,决定
当涉及到总销售额时,需要做出一个有趣的设计决策。
- 我们是否应该让分析师直接阅读一个地区的小说和非小说属性并总结它们?
我们可以这样做:
def calculateTotalSales(self):
return sum([reg.fiction + reg.nonfiction for reg in self.regions])
但是,如果我们添加“历史剧”(小说和非小说)或其他属性会发生什么?然后每次我们更改 Region 时,我们都必须更改我们的 Analyst,以便将 Region 的新结构考虑在内。
不,这是一个糟糕的设计决定。Region 已经知道它需要知道的关于其总销售额的所有信息。地区应该能够报告其总数。
做出好的选择!
Feature: Report Total Sales
Given: I have a region with fiction and non-fiction sales
When : I ask the region for its total sales
Then: The region should tell me its total sales
这是作为测试用例的 Python 实现:
def testGetTotalSalesForRegion(self):
"""Given a region with fiction and nonfiction sales, when I ask for its total sales, then I should get the result"""
# Given a set of data
texas = { "regionCode": "TX", "fiction" : 415, "nonfiction" : 555, "taxRate" : 0.55 }
region = Region("TX", 415, 555, 0.55)
# When I ask the region for its total sales
result = region.totalSales()
# Then I should get the sum of the sales
self.assertEquals(970,result)
分析师应该告诉,不要问
def calculateTotalSales(self):
return sum([reg.totalSales() for reg in self.regions])
现在您拥有编写此应用程序所需的一切。另外,如果您以后进行更改,您可以使用自动回归套件。它可以准确地告诉你你破坏了什么,并且测试明确地指定应用程序是什么以及它可以做什么。
结果
这是生成的程序:
from region import Region
from analyst import Analyst
def main():
text = readFromRegionFile()
regions = createRegionsFromText(text)
analyst = Analyst(regions)
printResults(analyst)
def readFromRegionFile():
regionDatabase = []
with open('./regions.txt','r') as f:
regionDatabase = f.readlines()
return regionDatabase
def createRegionsFromText(text):
regions = []
for line in text:
data = line.split()
regions.append(Region(data[0],data[1], data[2], data[3]))
return regions
def printResults(analyst):
totSales = analyst.calculateTotalSales()
totFic = analyst.calculateTotalFictionSales()
totNon = analyst.calculateTotalNonFictionSales()
for r in analyst.regions:
print("{0:2}{1:10}{2:13}{3:4}{4:14.2f}{5:10.2f}".format(
"", r.code, r.fiction, r.nonfiction, r.totalSales(), r.taxRate))
print("---------------------------------------------------------------------")
print("{0:11}{1:13}{2:34}{3:2}{4:8}".format(
"Total", totFic, totNon, "$", totSales))
if __name__ == "__main__":
main()
把你写的和这个比较一下。哪一个更容易理解?简洁的?如果:
- 您为每个地区添加了音乐销售?
- 您从文本文件转移到 MySQL 数据库或 Web 服务调用?
让你的概念体现出来。使您的代码清晰、简洁且富有表现力。