From 480cf94e7684f0de16e00560fa867f879ee46dc1 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 08:37:09 +0530 Subject: [PATCH 01/37] Added processor and reader modules which are used in the doctests --- stock_alerter/processor.py | 9 +++++++++ stock_alerter/reader.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 stock_alerter/processor.py create mode 100644 stock_alerter/reader.py diff --git a/stock_alerter/processor.py b/stock_alerter/processor.py new file mode 100644 index 0000000..6077fda --- /dev/null +++ b/stock_alerter/processor.py @@ -0,0 +1,9 @@ +class Processor: + def __init__(self, reader, exchange): + self.reader = reader + self.exchange = exchange + + def process(self): + for symbol, timestamp, price in self.reader.get_updates(): + stock = self.exchange[symbol] + stock.update(timestamp, price) diff --git a/stock_alerter/reader.py b/stock_alerter/reader.py new file mode 100644 index 0000000..8ec792f --- /dev/null +++ b/stock_alerter/reader.py @@ -0,0 +1,28 @@ +from datetime import datetime + + +class ListReader: + """Reads a series of updates from a list""" + def __init__(self, updates): + self.updates = updates + + def get_updates(self): + for update in self.updates: + yield update + + +class FileReader: + """Reads a series of stock updates from a file""" + def __init__(self, filename): + self.filename = filename + + def get_updates(self): + """Returns the next update everytime the method is called""" + with open(self.filename, "r") as fp: + data = fp.read() + lines = data.split() + for line in lines: + symbol, timestamp, price = line.split(",") + yield (symbol, + datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%f"), + int(price)) From a5099f2722db9220ba3701f601bccf8ec96426df Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 08:39:19 +0530 Subject: [PATCH 02/37] Added basic doctest for Stock.price method --- stock_alerter/stock.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/stock_alerter/stock.py b/stock_alerter/stock.py index e8bcddb..e8af696 100644 --- a/stock_alerter/stock.py +++ b/stock_alerter/stock.py @@ -22,6 +22,14 @@ def __init__(self, symbol): @property def price(self): + """Returns the current price of the Stock + + >>> from datetime import datetime + >>> stock = Stock("GOOG") + >>> stock.update(datetime(2011, 10, 3), 10) + >>> stock.price + 10 + """ try: return self.history[-1].value except IndexError: @@ -54,3 +62,8 @@ def get_crossover_signal(self, on_date): return StockSignal.neutral return StockSignal.neutral + + +if __name__ == "__main__": + import doctest + doctest.testmod() From 1aee33d948389d08d8411e6dbd4c721f0738066c Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 08:40:03 +0530 Subject: [PATCH 03/37] Expanded doctest --- stock_alerter/stock.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/stock_alerter/stock.py b/stock_alerter/stock.py index e8af696..86ea652 100644 --- a/stock_alerter/stock.py +++ b/stock_alerter/stock.py @@ -29,6 +29,28 @@ def price(self): >>> stock.update(datetime(2011, 10, 3), 10) >>> stock.price 10 + + The method will return the latest price by timestamp, so even if + updates are out of order, it will return the latest one + + >>> stock = Stock("GOOG") + >>> stock.update(datetime(2011, 10, 3), 10) + + Now, let us do an update with a date that is earlier than the previous + one + + >>> stock.update(datetime(2011, 10, 2), 5) + + And the method still returns the latest price + + >>> stock.price + 10 + + If there are no updates, then the method returns None + + >>> stock = Stock("GOOG") + >>> print(stock.price) + None """ try: return self.history[-1].value From 1725ce6edb22b599dd8e18ef24a038f4b4a6769e Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 08:40:46 +0530 Subject: [PATCH 04/37] Doctest - test failure due to exception --- stock_alerter/stock.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/stock_alerter/stock.py b/stock_alerter/stock.py index 86ea652..02935ee 100644 --- a/stock_alerter/stock.py +++ b/stock_alerter/stock.py @@ -64,6 +64,14 @@ def update(self, timestamp, price): self.updated.fire(self) def is_increasing_trend(self): + """Returns True if the past three values have been strictly increasing + + Returns False if there have been less than three updates so far + + >>> stock = Stock("GOOG") + >>> stock.is_increasing_trend() + False + """ return self.history[-3].value < self.history[-2].value < self.history[-1].value def _is_crossover_below_to_above(self, on_date, ma, reference_ma): From dae4de5400f3ae394873bb2cf0eca8a898fed992 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 08:41:18 +0530 Subject: [PATCH 05/37] Doctest - test failure due to bug --- stock_alerter/stock.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/stock_alerter/stock.py b/stock_alerter/stock.py index 02935ee..84cb1b0 100644 --- a/stock_alerter/stock.py +++ b/stock_alerter/stock.py @@ -72,7 +72,10 @@ def is_increasing_trend(self): >>> stock.is_increasing_trend() False """ - return self.history[-3].value < self.history[-2].value < self.history[-1].value + try: + return self.history[-3].value < self.history[-2].value < self.history[-1].value + except IndexError: + return True def _is_crossover_below_to_above(self, on_date, ma, reference_ma): prev_date = on_date - timedelta(1) From 2f245a278770077e6a661d4c3f646d5462f40722 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 08:41:35 +0530 Subject: [PATCH 06/37] Doctest - all tests pass again --- stock_alerter/stock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock_alerter/stock.py b/stock_alerter/stock.py index 84cb1b0..3f6bbe9 100644 --- a/stock_alerter/stock.py +++ b/stock_alerter/stock.py @@ -75,7 +75,7 @@ def is_increasing_trend(self): try: return self.history[-3].value < self.history[-2].value < self.history[-1].value except IndexError: - return True + return False def _is_crossover_below_to_above(self, on_date, ma, reference_ma): prev_date = on_date - timedelta(1) From 01ac62716880473e06423b52747b4d0ceda83d56 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 08:43:44 +0530 Subject: [PATCH 07/37] Doctest - checking for exceptions --- stock_alerter/stock.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/stock_alerter/stock.py b/stock_alerter/stock.py index 3f6bbe9..24651d3 100644 --- a/stock_alerter/stock.py +++ b/stock_alerter/stock.py @@ -58,6 +58,21 @@ def price(self): return None def update(self, timestamp, price): + """Updates the stock with the price at the given timestamp + + >>> from datetime import datetime + >>> stock = Stock("GOOG") + >>> stock.update(datetime(2014, 10, 2), 10) + >>> stock.price + 10 + + The method raises a ValueError exception if the price is negative + + >>> stock.update(datetime(2014, 10, 2), -1) + Traceback (most recent call last): + ... + ValueError: price should not be negative + """ if price < 0: raise ValueError("price should not be negative") self.history.update(timestamp, price) From 659ce0c0ef658b05053f50150f8dd205e91da956 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 08:45:09 +0530 Subject: [PATCH 08/37] Package level doctests --- stock_alerter/__init__.py | 47 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/stock_alerter/__init__.py b/stock_alerter/__init__.py index e69de29..90a8606 100644 --- a/stock_alerter/__init__.py +++ b/stock_alerter/__init__.py @@ -0,0 +1,47 @@ +r""" +The stock_alerter module allows you to setup rules and get alerted when +those rules are met. + +>>> from datetime import datetime + +First, we need to setup an exchange which contains all the stocks that +are going to be processed. A simple dictionary will do. + +>>> from stock_alerter.stock import Stock +>>> exchange = {"GOOG": Stock("GOOG"), "AAPL": Stock("AAPL")} + +Next, we configure the reader. The reader is the source from where the +stock updates are coming. The module provides two readers out of the +box: A FileReader for reading updates from a comma separated file, +and a ListReader to get updates from a list. You can create other +readers, such as an HTTPReader to get updates from a remote server. +Here we create a simple ListReader by passing in a list of 3-tuples +containing the stock symbol, timestamp and price. + +>>> from stock_alerter.reader import ListReader +>>> reader = ListReader([("GOOG", datetime(2014, 2, 8), 5)]) + +Next, we setup an Alert. We give it a rule, and an action to be taken +when the rule is fired. + +>>> from stock_alerter.alert import Alert +>>> from stock_alerter.rule import PriceRule +>>> from stock_alerter.action import PrintAction +>>> alert = Alert("GOOG > $3", PriceRule("GOOG", lambda s: s.price > 3),\ +... PrintAction()) + +Connect the alert to the exchange + +>>> alert.connect(exchange) + +Now that everything is setup, we can start processing the updates + +>>> from stock_alerter.processor import Processor +>>> processor = Processor(reader, exchange) +>>> processor.process() +GOOG > $3 +""" + +if __name__ == "__main__": + import doctest + doctest.testmod() From fbf30bfc0ba7107a60b90f0eaabdb32a3523ced0 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 08:47:58 +0530 Subject: [PATCH 09/37] Doctests moved to a separate readme.txt file --- stock_alerter/__init__.py | 46 +-------------------------------------- stock_alerter/readme.txt | 41 ++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 45 deletions(-) create mode 100644 stock_alerter/readme.txt diff --git a/stock_alerter/__init__.py b/stock_alerter/__init__.py index 90a8606..e2bebd2 100644 --- a/stock_alerter/__init__.py +++ b/stock_alerter/__init__.py @@ -1,47 +1,3 @@ -r""" -The stock_alerter module allows you to setup rules and get alerted when -those rules are met. - ->>> from datetime import datetime - -First, we need to setup an exchange which contains all the stocks that -are going to be processed. A simple dictionary will do. - ->>> from stock_alerter.stock import Stock ->>> exchange = {"GOOG": Stock("GOOG"), "AAPL": Stock("AAPL")} - -Next, we configure the reader. The reader is the source from where the -stock updates are coming. The module provides two readers out of the -box: A FileReader for reading updates from a comma separated file, -and a ListReader to get updates from a list. You can create other -readers, such as an HTTPReader to get updates from a remote server. -Here we create a simple ListReader by passing in a list of 3-tuples -containing the stock symbol, timestamp and price. - ->>> from stock_alerter.reader import ListReader ->>> reader = ListReader([("GOOG", datetime(2014, 2, 8), 5)]) - -Next, we setup an Alert. We give it a rule, and an action to be taken -when the rule is fired. - ->>> from stock_alerter.alert import Alert ->>> from stock_alerter.rule import PriceRule ->>> from stock_alerter.action import PrintAction ->>> alert = Alert("GOOG > $3", PriceRule("GOOG", lambda s: s.price > 3),\ -... PrintAction()) - -Connect the alert to the exchange - ->>> alert.connect(exchange) - -Now that everything is setup, we can start processing the updates - ->>> from stock_alerter.processor import Processor ->>> processor = Processor(reader, exchange) ->>> processor.process() -GOOG > $3 -""" - if __name__ == "__main__": import doctest - doctest.testmod() + doctest.testfile("readme.txt") diff --git a/stock_alerter/readme.txt b/stock_alerter/readme.txt new file mode 100644 index 0000000..573ba8e --- /dev/null +++ b/stock_alerter/readme.txt @@ -0,0 +1,41 @@ +The stock_alerter module allows you to setup rules and get alerted when +those rules are met. + +>>> from datetime import datetime + +First, we need to setup an exchange which contains all the stocks that +are going to be processed. A simple dictionary will do. + +>>> from stock_alerter.stock import Stock +>>> exchange = {"GOOG": Stock("GOOG"), "AAPL": Stock("AAPL")} + +Next, we configure the reader. The reader is the source from where the +stock updates are coming. The module provides two readers out of the +box: A FileReader for reading updates from a comma separated file, +and a ListReader to get updates from a list. You can create other +readers, such as an HTTPReader to get updates from a remote server. +Here we create a simple ListReader by passing in a list of 3-tuples +containing the stock symbol, timestamp and price. + +>>> from stock_alerter.reader import ListReader +>>> reader = ListReader([("GOOG", datetime(2014, 2, 8), 5)]) + +Next, we setup an Alert. We give it a rule, and an action to be taken +when the rule is fired. + +>>> from stock_alerter.alert import Alert +>>> from stock_alerter.rule import PriceRule +>>> from stock_alerter.action import PrintAction +>>> alert = Alert("GOOG > $3", PriceRule("GOOG", lambda s: s.price > 3),\ +... PrintAction()) + +Connect the alert to the exchange + +>>> alert.connect(exchange) + +Now that everything is setup, we can start processing the updates + +>>> from stock_alerter.processor import Processor +>>> processor = Processor(reader, exchange) +>>> processor.process() +GOOG > $3 From 1f5db0eab37bbf40f8ac3ad8fd619b41188f0da9 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 08:50:28 +0530 Subject: [PATCH 10/37] Wrapped doctest execution in a unittest.TestCase class --- stock_alerter/__init__.py | 3 --- stock_alerter/stock.py | 5 ----- stock_alerter/tests/test_doctest.py | 11 +++++++++++ 3 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 stock_alerter/tests/test_doctest.py diff --git a/stock_alerter/__init__.py b/stock_alerter/__init__.py index e2bebd2..e69de29 100644 --- a/stock_alerter/__init__.py +++ b/stock_alerter/__init__.py @@ -1,3 +0,0 @@ -if __name__ == "__main__": - import doctest - doctest.testfile("readme.txt") diff --git a/stock_alerter/stock.py b/stock_alerter/stock.py index 24651d3..73f3d55 100644 --- a/stock_alerter/stock.py +++ b/stock_alerter/stock.py @@ -110,8 +110,3 @@ def get_crossover_signal(self, on_date): return StockSignal.neutral return StockSignal.neutral - - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/stock_alerter/tests/test_doctest.py b/stock_alerter/tests/test_doctest.py new file mode 100644 index 0000000..d6731e7 --- /dev/null +++ b/stock_alerter/tests/test_doctest.py @@ -0,0 +1,11 @@ +import doctest +import unittest +from stock_alerter import stock + + +class PackageDocTest(unittest.TestCase): + def test_stock_module(self): + doctest.testmod(stock) + + def test_doc(self): + doctest.testfile(r"..\readme.txt") From d57f1c30d6992394ef608740b343b0d6cb65deb5 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 08:53:37 +0530 Subject: [PATCH 11/37] Using load_tests protocol to create a unittest compatible test suite --- stock_alerter/tests/test_doctest.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/stock_alerter/tests/test_doctest.py b/stock_alerter/tests/test_doctest.py index d6731e7..d4eafca 100644 --- a/stock_alerter/tests/test_doctest.py +++ b/stock_alerter/tests/test_doctest.py @@ -1,11 +1,8 @@ import doctest -import unittest from stock_alerter import stock -class PackageDocTest(unittest.TestCase): - def test_stock_module(self): - doctest.testmod(stock) - - def test_doc(self): - doctest.testfile(r"..\readme.txt") +def load_tests(loader, tests, pattern): + tests.addTests(doctest.DocTestSuite(stock)) + tests.addTests(doctest.DocFileSuite("../readme.txt")) + return tests From 7a6049238286418742804bbfab02c94d9553fbb3 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 08:55:01 +0530 Subject: [PATCH 12/37] Passing a custom context to doctest --- stock_alerter/stock.py | 2 -- stock_alerter/tests/test_doctest.py | 7 ++++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/stock_alerter/stock.py b/stock_alerter/stock.py index 73f3d55..dc46555 100644 --- a/stock_alerter/stock.py +++ b/stock_alerter/stock.py @@ -24,7 +24,6 @@ def __init__(self, symbol): def price(self): """Returns the current price of the Stock - >>> from datetime import datetime >>> stock = Stock("GOOG") >>> stock.update(datetime(2011, 10, 3), 10) >>> stock.price @@ -60,7 +59,6 @@ def price(self): def update(self, timestamp, price): """Updates the stock with the price at the given timestamp - >>> from datetime import datetime >>> stock = Stock("GOOG") >>> stock.update(datetime(2014, 10, 2), 10) >>> stock.price diff --git a/stock_alerter/tests/test_doctest.py b/stock_alerter/tests/test_doctest.py index d4eafca..0e0dddb 100644 --- a/stock_alerter/tests/test_doctest.py +++ b/stock_alerter/tests/test_doctest.py @@ -1,8 +1,13 @@ import doctest +from datetime import datetime + from stock_alerter import stock def load_tests(loader, tests, pattern): - tests.addTests(doctest.DocTestSuite(stock)) + tests.addTests(doctest.DocTestSuite(stock, globs={ + "datetime": datetime, + "Stock": stock.Stock + })) tests.addTests(doctest.DocFileSuite("../readme.txt")) return tests From 3a366b828938fd3834f74b79691a02b16e0371e0 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 08:57:25 +0530 Subject: [PATCH 13/37] Using a doctest setup function --- stock_alerter/stock.py | 3 --- stock_alerter/tests/test_doctest.py | 7 ++++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/stock_alerter/stock.py b/stock_alerter/stock.py index dc46555..49893af 100644 --- a/stock_alerter/stock.py +++ b/stock_alerter/stock.py @@ -24,7 +24,6 @@ def __init__(self, symbol): def price(self): """Returns the current price of the Stock - >>> stock = Stock("GOOG") >>> stock.update(datetime(2011, 10, 3), 10) >>> stock.price 10 @@ -59,7 +58,6 @@ def price(self): def update(self, timestamp, price): """Updates the stock with the price at the given timestamp - >>> stock = Stock("GOOG") >>> stock.update(datetime(2014, 10, 2), 10) >>> stock.price 10 @@ -81,7 +79,6 @@ def is_increasing_trend(self): Returns False if there have been less than three updates so far - >>> stock = Stock("GOOG") >>> stock.is_increasing_trend() False """ diff --git a/stock_alerter/tests/test_doctest.py b/stock_alerter/tests/test_doctest.py index 0e0dddb..d774a4d 100644 --- a/stock_alerter/tests/test_doctest.py +++ b/stock_alerter/tests/test_doctest.py @@ -4,10 +4,15 @@ from stock_alerter import stock +def setup_stock_doctest(doctest): + s = stock.Stock("GOOG") + doctest.globs.update({"stock": s}) + + def load_tests(loader, tests, pattern): tests.addTests(doctest.DocTestSuite(stock, globs={ "datetime": datetime, "Stock": stock.Stock - })) + }, setUp=setup_stock_doctest)) tests.addTests(doctest.DocFileSuite("../readme.txt")) return tests From 9f52df60764585b31d02bbed76e130911a30046e Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 09:00:53 +0530 Subject: [PATCH 14/37] Doctest directives --- stock_alerter/readme.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/stock_alerter/readme.txt b/stock_alerter/readme.txt index 573ba8e..9b95435 100644 --- a/stock_alerter/readme.txt +++ b/stock_alerter/readme.txt @@ -8,6 +8,11 @@ are going to be processed. A simple dictionary will do. >>> from stock_alerter.stock import Stock >>> exchange = {"GOOG": Stock("GOOG"), "AAPL": Stock("AAPL")} +>>> for key in sorted(exchange.keys()): #doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE +... print(key, exchange[key]) +... +AAPL +GOOG Next, we configure the reader. The reader is the source from where the stock updates are coming. The module provides two readers out of the From 45f4506973363da513b8f3110cf59e971c0d3fdd Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 09:02:59 +0530 Subject: [PATCH 15/37] Locating doctest file relative to the package --- stock_alerter/tests/test_doctest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock_alerter/tests/test_doctest.py b/stock_alerter/tests/test_doctest.py index d774a4d..6cb3b1a 100644 --- a/stock_alerter/tests/test_doctest.py +++ b/stock_alerter/tests/test_doctest.py @@ -14,5 +14,5 @@ def load_tests(loader, tests, pattern): "datetime": datetime, "Stock": stock.Stock }, setUp=setup_stock_doctest)) - tests.addTests(doctest.DocFileSuite("../readme.txt")) + tests.addTests(doctest.DocFileSuite("readme.txt", package="stock_alerter")) return tests From b572b41d1200c710f2b269b4005a8a91ac0757a8 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 09:04:54 +0530 Subject: [PATCH 16/37] Passing doctest directives via the test wrapper --- stock_alerter/readme.txt | 2 +- stock_alerter/tests/test_doctest.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/stock_alerter/readme.txt b/stock_alerter/readme.txt index 9b95435..a6cd892 100644 --- a/stock_alerter/readme.txt +++ b/stock_alerter/readme.txt @@ -8,7 +8,7 @@ are going to be processed. A simple dictionary will do. >>> from stock_alerter.stock import Stock >>> exchange = {"GOOG": Stock("GOOG"), "AAPL": Stock("AAPL")} ->>> for key in sorted(exchange.keys()): #doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE +>>> for key in sorted(exchange.keys()): ... print(key, exchange[key]) ... AAPL diff --git a/stock_alerter/tests/test_doctest.py b/stock_alerter/tests/test_doctest.py index 6cb3b1a..e5da804 100644 --- a/stock_alerter/tests/test_doctest.py +++ b/stock_alerter/tests/test_doctest.py @@ -14,5 +14,6 @@ def load_tests(loader, tests, pattern): "datetime": datetime, "Stock": stock.Stock }, setUp=setup_stock_doctest)) - tests.addTests(doctest.DocFileSuite("readme.txt", package="stock_alerter")) + options = doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE + tests.addTests(doctest.DocFileSuite("readme.txt", package="stock_alerter", optionflags=options)) return tests From bc3b94f264ca3e126dd963ef9108246fd78dddd5 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 09:19:52 +0530 Subject: [PATCH 17/37] nose2 - function style test case --- stock_alerter/tests/test_stock.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/stock_alerter/tests/test_stock.py b/stock_alerter/tests/test_stock.py index a7453d4..fc101cd 100644 --- a/stock_alerter/tests/test_stock.py +++ b/stock_alerter/tests/test_stock.py @@ -5,6 +5,11 @@ from ..stock import Stock, StockSignal +def test_price_of_a_new_stock_class_should_be_None(): + goog = Stock("GOOG") + assert goog.price is None + + class StockTest(unittest.TestCase): def setUp(self): self.goog = Stock("GOOG") From 3fb38fd2ef457cfb01853750b258050db764225f Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 09:21:14 +0530 Subject: [PATCH 18/37] nose2 - setup and teardown --- stock_alerter/tests/test_stock.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/stock_alerter/tests/test_stock.py b/stock_alerter/tests/test_stock.py index fc101cd..9d96ee9 100644 --- a/stock_alerter/tests/test_stock.py +++ b/stock_alerter/tests/test_stock.py @@ -5,9 +5,20 @@ from ..stock import Stock, StockSignal -def test_price_of_a_new_stock_class_should_be_None(): +def setup_test(): + global goog goog = Stock("GOOG") + + +def teardown_test(): + global goog + goog = None + + +def test_price_of_a_new_stock_class_should_be_None(): assert goog.price is None +test_price_of_a_new_stock_class_should_be_None.setup = setup_test +test_price_of_a_new_stock_class_should_be_None.teardown = teardown_test class StockTest(unittest.TestCase): From ac686a1a23e334f3a092cf20be26be7d124cc530 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 09:22:25 +0530 Subject: [PATCH 19/37] nose2 - parametrised tests --- stock_alerter/tests/test_stock.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/stock_alerter/tests/test_stock.py b/stock_alerter/tests/test_stock.py index 9d96ee9..75f0937 100644 --- a/stock_alerter/tests/test_stock.py +++ b/stock_alerter/tests/test_stock.py @@ -2,6 +2,8 @@ import collections from datetime import datetime, timedelta +from nose2.tools.params import params + from ..stock import Stock, StockSignal @@ -21,6 +23,24 @@ def test_price_of_a_new_stock_class_should_be_None(): test_price_of_a_new_stock_class_should_be_None.teardown = teardown_test +def given_a_series_of_prices(stock, prices): + timestamps = [datetime(2014, 2, 10), datetime(2014, 2, 11), + datetime(2014, 2, 12), datetime(2014, 2, 13)] + for timestamp, price in zip(timestamps, prices): + stock.update(timestamp, price) + + +@params( + ([8, 10, 12], True), + ([8, 12, 10], False), + ([8, 10, 10], False) +) +def test_stock_trends(prices, expected_output): + goog = Stock("GOOG") + given_a_series_of_prices(goog, prices) + assert goog.is_increasing_trend() == expected_output + + class StockTest(unittest.TestCase): def setUp(self): self.goog = Stock("GOOG") From 6c71a99f449e2800770062786f754c8decfc8670 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 09:23:42 +0530 Subject: [PATCH 20/37] nose2 - generated tests --- stock_alerter/tests/test_stock.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/stock_alerter/tests/test_stock.py b/stock_alerter/tests/test_stock.py index 75f0937..27fd3c0 100644 --- a/stock_alerter/tests/test_stock.py +++ b/stock_alerter/tests/test_stock.py @@ -41,6 +41,17 @@ def test_stock_trends(prices, expected_output): assert goog.is_increasing_trend() == expected_output +def test_trend_with_all_consecutive_values_upto_100(): + for i in range(100): + yield stock_trends_with_consecutive_prices, [i, i+1, i+2] + + +def stock_trends_with_consecutive_prices(prices): + goog = Stock("GOOG") + given_a_series_of_prices(goog, prices) + assert goog.is_increasing_trend() + + class StockTest(unittest.TestCase): def setUp(self): self.goog = Stock("GOOG") From 7b6daf6be23102550a0674dd99ce360ec9eac0f4 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 09:30:14 +0530 Subject: [PATCH 21/37] nose2 - Using Such DSL with the Layers plugin --- nose2.cfg | 9 +++++++++ requirements.txt | 1 + stock_alerter/tests/test_stock.py | 32 +++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 nose2.cfg create mode 100644 requirements.txt diff --git a/nose2.cfg b/nose2.cfg new file mode 100644 index 0000000..c902aa5 --- /dev/null +++ b/nose2.cfg @@ -0,0 +1,9 @@ +[unittest] +test-file-pattern=test_*.py +test-method-prefix=test +plugins = nose2.plugins.layers +exclude-plugins = nose2.plugins.doctest + +[layer-reporter] +always-on = False +colors = False diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1f671a5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +nose2 \ No newline at end of file diff --git a/stock_alerter/tests/test_stock.py b/stock_alerter/tests/test_stock.py index 27fd3c0..b97de2b 100644 --- a/stock_alerter/tests/test_stock.py +++ b/stock_alerter/tests/test_stock.py @@ -3,6 +3,7 @@ from datetime import datetime, timedelta from nose2.tools.params import params +from nose2.tools import such from ..stock import Stock, StockSignal @@ -52,6 +53,37 @@ def stock_trends_with_consecutive_prices(prices): assert goog.is_increasing_trend() +with such.A("Stock class") as it: + + @it.has_setup + def setup(): + it.goog = Stock("GOOG") + + with it.having("a price method"): + @it.has_setup + def setup(): + it.goog.update(datetime(2014, 2, 12), price=10) + + @it.should("return the price") + def test(case): + assert it.goog.price == 10 + + @it.should("return the latest price") + def test(case): + it.goog.update(datetime(2014, 2, 11), price=15) + assert it.goog.price == 10 + + with it.having("a trend method"): + @it.should("return True if the last three updates were increasing") + def test(case): + it.goog.update(datetime(2014, 2, 11), price=12) + it.goog.update(datetime(2014, 2, 12), price=13) + it.goog.update(datetime(2014, 2, 13), price=14) + assert it.goog.is_increasing_trend() + + it.createTests(globals()) + + class StockTest(unittest.TestCase): def setUp(self): self.goog = Stock("GOOG") From 45e5705bb7d1e8a441395fc1c6f6376a6b09c5d2 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 09:39:33 +0530 Subject: [PATCH 22/37] nose2 - using a config file --- nose2.cfg | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/nose2.cfg b/nose2.cfg index c902aa5..9988d74 100644 --- a/nose2.cfg +++ b/nose2.cfg @@ -1,9 +1,19 @@ [unittest] test-file-pattern=test_*.py test-method-prefix=test -plugins = nose2.plugins.layers +plugins = nose2.plugins.coverage + nose2.plugins.junitxml + nose2.plugins.layers exclude-plugins = nose2.plugins.doctest [layer-reporter] -always-on = False +always-on = True colors = False + +[junit-xml] +always-on = False +path = nose2.xml + +[coverage] +always-on = False +coverage-report = ["html", "xml"] From 27992822c7d1b09f69f9efc9582c473dd6ca35ec Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Wed, 25 Mar 2015 09:44:26 +0530 Subject: [PATCH 23/37] Removed nose2 specific tests --- stock_alerter/tests/test_stock.py | 79 ------------------------------- 1 file changed, 79 deletions(-) diff --git a/stock_alerter/tests/test_stock.py b/stock_alerter/tests/test_stock.py index b97de2b..a7453d4 100644 --- a/stock_alerter/tests/test_stock.py +++ b/stock_alerter/tests/test_stock.py @@ -2,88 +2,9 @@ import collections from datetime import datetime, timedelta -from nose2.tools.params import params -from nose2.tools import such - from ..stock import Stock, StockSignal -def setup_test(): - global goog - goog = Stock("GOOG") - - -def teardown_test(): - global goog - goog = None - - -def test_price_of_a_new_stock_class_should_be_None(): - assert goog.price is None -test_price_of_a_new_stock_class_should_be_None.setup = setup_test -test_price_of_a_new_stock_class_should_be_None.teardown = teardown_test - - -def given_a_series_of_prices(stock, prices): - timestamps = [datetime(2014, 2, 10), datetime(2014, 2, 11), - datetime(2014, 2, 12), datetime(2014, 2, 13)] - for timestamp, price in zip(timestamps, prices): - stock.update(timestamp, price) - - -@params( - ([8, 10, 12], True), - ([8, 12, 10], False), - ([8, 10, 10], False) -) -def test_stock_trends(prices, expected_output): - goog = Stock("GOOG") - given_a_series_of_prices(goog, prices) - assert goog.is_increasing_trend() == expected_output - - -def test_trend_with_all_consecutive_values_upto_100(): - for i in range(100): - yield stock_trends_with_consecutive_prices, [i, i+1, i+2] - - -def stock_trends_with_consecutive_prices(prices): - goog = Stock("GOOG") - given_a_series_of_prices(goog, prices) - assert goog.is_increasing_trend() - - -with such.A("Stock class") as it: - - @it.has_setup - def setup(): - it.goog = Stock("GOOG") - - with it.having("a price method"): - @it.has_setup - def setup(): - it.goog.update(datetime(2014, 2, 12), price=10) - - @it.should("return the price") - def test(case): - assert it.goog.price == 10 - - @it.should("return the latest price") - def test(case): - it.goog.update(datetime(2014, 2, 11), price=15) - assert it.goog.price == 10 - - with it.having("a trend method"): - @it.should("return True if the last three updates were increasing") - def test(case): - it.goog.update(datetime(2014, 2, 11), price=12) - it.goog.update(datetime(2014, 2, 12), price=13) - it.goog.update(datetime(2014, 2, 13), price=14) - assert it.goog.is_increasing_trend() - - it.createTests(globals()) - - class StockTest(unittest.TestCase): def setUp(self): self.goog = Stock("GOOG") From 386e11bd76cd1d091190e9a6769f7a7f14a4ff4f Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Thu, 26 Mar 2015 08:03:00 +0530 Subject: [PATCH 24/37] Creating a basic test suite --- run_test.py | 7 +++++++ stock_alerter/tests/test_stock.py | 6 ++++++ 2 files changed, 13 insertions(+) create mode 100644 run_test.py diff --git a/run_test.py b/run_test.py new file mode 100644 index 0000000..6e6679c --- /dev/null +++ b/run_test.py @@ -0,0 +1,7 @@ +import unittest + +from stock_alerter.tests import test_stock + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + runner.run(test_stock.suite()) diff --git a/stock_alerter/tests/test_stock.py b/stock_alerter/tests/test_stock.py index a7453d4..efd3e0c 100644 --- a/stock_alerter/tests/test_stock.py +++ b/stock_alerter/tests/test_stock.py @@ -5,6 +5,12 @@ from ..stock import Stock, StockSignal +def suite(): + test_suite = unittest.TestSuite() + test_suite.addTest(StockTest("test_stock_update")) + return test_suite + + class StockTest(unittest.TestCase): def setUp(self): self.goog = Stock("GOOG") From 3cdd2c64f010d1cd1e3fcd0aef4e5777651a7757 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Thu, 26 Mar 2015 08:04:53 +0530 Subject: [PATCH 25/37] Using a test loader --- stock_alerter/tests/test_stock.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stock_alerter/tests/test_stock.py b/stock_alerter/tests/test_stock.py index efd3e0c..acda350 100644 --- a/stock_alerter/tests/test_stock.py +++ b/stock_alerter/tests/test_stock.py @@ -6,8 +6,10 @@ def suite(): + loader = unittest.TestLoader() test_suite = unittest.TestSuite() test_suite.addTest(StockTest("test_stock_update")) + test_suite.addTest(loader.loadTestsFromTestCase(StockCrossOverSignalTest)) return test_suite From 8f953ab29782da78448b7a00c718877f12352cc8 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Thu, 26 Mar 2015 08:06:44 +0530 Subject: [PATCH 26/37] Using the load_tests protocol --- stock_alerter/tests/test_stock.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/stock_alerter/tests/test_stock.py b/stock_alerter/tests/test_stock.py index acda350..f9e0bf7 100644 --- a/stock_alerter/tests/test_stock.py +++ b/stock_alerter/tests/test_stock.py @@ -1,3 +1,4 @@ +import sys import unittest import collections from datetime import datetime, timedelta @@ -5,12 +6,12 @@ from ..stock import Stock, StockSignal -def suite(): - loader = unittest.TestLoader() - test_suite = unittest.TestSuite() - test_suite.addTest(StockTest("test_stock_update")) - test_suite.addTest(loader.loadTestsFromTestCase(StockCrossOverSignalTest)) - return test_suite +def load_tests(loader, tests, pattern): + suite = unittest.TestSuite() + suite.addTest(loader.loadTestsFromTestCase(StockTest)) + if not sys.platform.startswith("win"): + suite.addTest(loader.loadTestsFromTestCase(StockCrossOverSignalTest)) + return suite class StockTest(unittest.TestCase): From a5b2011a8a7d5e481a0c39dd994ce7997da44c0c Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Thu, 26 Mar 2015 08:09:12 +0530 Subject: [PATCH 27/37] Skipping tests --- stock_alerter/tests/test_stock.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/stock_alerter/tests/test_stock.py b/stock_alerter/tests/test_stock.py index f9e0bf7..a90d199 100644 --- a/stock_alerter/tests/test_stock.py +++ b/stock_alerter/tests/test_stock.py @@ -1,4 +1,3 @@ -import sys import unittest import collections from datetime import datetime, timedelta @@ -6,14 +5,6 @@ from ..stock import Stock, StockSignal -def load_tests(loader, tests, pattern): - suite = unittest.TestSuite() - suite.addTest(loader.loadTestsFromTestCase(StockTest)) - if not sys.platform.startswith("win"): - suite.addTest(loader.loadTestsFromTestCase(StockCrossOverSignalTest)) - return suite - - class StockTest(unittest.TestCase): def setUp(self): self.goog = Stock("GOOG") @@ -21,6 +12,7 @@ def setUp(self): def test_price_of_a_new_stock_class_should_be_None(self): self.assertIsNone(self.goog.price) + @unittest.skip("skip this test for now") def test_stock_update(self): """An update should set the price on the stock object From 5da0924ddeee784ae5445e6939dc361150c02747 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Thu, 26 Mar 2015 08:11:19 +0530 Subject: [PATCH 28/37] Conditionally skipping tests --- stock_alerter/tests/test_stock.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/stock_alerter/tests/test_stock.py b/stock_alerter/tests/test_stock.py index a90d199..a69edad 100644 --- a/stock_alerter/tests/test_stock.py +++ b/stock_alerter/tests/test_stock.py @@ -1,3 +1,4 @@ +import sys import unittest import collections from datetime import datetime, timedelta @@ -12,7 +13,7 @@ def setUp(self): def test_price_of_a_new_stock_class_should_be_None(self): self.assertIsNone(self.goog.price) - @unittest.skip("skip this test for now") + @unittest.skipIf(sys.platform.startswith("win"), "skip on windows") def test_stock_update(self): """An update should set the price on the stock object @@ -21,6 +22,7 @@ def test_stock_update(self): self.goog.update(datetime(2014, 2, 12), price=10) self.assertEqual(10, self.goog.price) + @unittest.skipUnless(sys.platform.startswith("win"), "only run on windows") def test_negative_price_should_throw_ValueError(self): with self.assertRaises(ValueError): self.goog.update(datetime(2014, 2, 13), -1) From 780d5a13488ead5cf0a5c5fbab290c3e56f5769d Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Thu, 26 Mar 2015 08:15:21 +0530 Subject: [PATCH 29/37] Writing a custom test loader to load tests by attribute --- run_test.py | 21 +++++++++++++++++++-- stock_alerter/tests/test_stock.py | 5 ++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/run_test.py b/run_test.py index 6e6679c..5595d1a 100644 --- a/run_test.py +++ b/run_test.py @@ -1,7 +1,24 @@ import unittest -from stock_alerter.tests import test_stock + +class AttribLoader(unittest.TestLoader): + def __init__(self, attrib): + self.attrib = attrib + + def loadTestsFromModule(self, module, use_load_tests=False): + return super().loadTestsFromModule(module, use_load_tests=False) + + def getTestCaseNames(self, testCaseClass): + test_names = super().getTestCaseNames(testCaseClass) + filtered_test_names = [test + for test in test_names + if hasattr(getattr(testCaseClass, test), + self.attrib)] + return filtered_test_names + if __name__ == "__main__": + loader = AttribLoader("slow") + test_suite = loader.discover(".") runner = unittest.TextTestRunner() - runner.run(test_stock.suite()) + runner.run(test_suite) diff --git a/stock_alerter/tests/test_stock.py b/stock_alerter/tests/test_stock.py index a69edad..ea1ba54 100644 --- a/stock_alerter/tests/test_stock.py +++ b/stock_alerter/tests/test_stock.py @@ -1,4 +1,3 @@ -import sys import unittest import collections from datetime import datetime, timedelta @@ -13,7 +12,6 @@ def setUp(self): def test_price_of_a_new_stock_class_should_be_None(self): self.assertIsNone(self.goog.price) - @unittest.skipIf(sys.platform.startswith("win"), "skip on windows") def test_stock_update(self): """An update should set the price on the stock object @@ -22,7 +20,8 @@ def test_stock_update(self): self.goog.update(datetime(2014, 2, 12), price=10) self.assertEqual(10, self.goog.price) - @unittest.skipUnless(sys.platform.startswith("win"), "only run on windows") + test_stock_update.slow = True + def test_negative_price_should_throw_ValueError(self): with self.assertRaises(ValueError): self.goog.update(datetime(2014, 2, 13), -1) From 8115678d47004812eb9e90b667d2c53b773b9064 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Thu, 26 Mar 2015 08:23:25 +0530 Subject: [PATCH 30/37] Expected failure --- stock_alerter/tests/test_stock.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/stock_alerter/tests/test_stock.py b/stock_alerter/tests/test_stock.py index ea1ba54..c981e61 100644 --- a/stock_alerter/tests/test_stock.py +++ b/stock_alerter/tests/test_stock.py @@ -12,15 +12,14 @@ def setUp(self): def test_price_of_a_new_stock_class_should_be_None(self): self.assertIsNone(self.goog.price) + @unittest.expectedFailure def test_stock_update(self): """An update should set the price on the stock object We will be using the `datetime` module for the timestamp """ self.goog.update(datetime(2014, 2, 12), price=10) - self.assertEqual(10, self.goog.price) - - test_stock_update.slow = True + self.assertEqual(100, self.goog.price) def test_negative_price_should_throw_ValueError(self): with self.assertRaises(ValueError): From 600351309edb24feffa0cb1ce69e8fde47cbfd9b Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Thu, 26 Mar 2015 08:38:31 +0530 Subject: [PATCH 31/37] Data driven tests with subTest --- stock_alerter/tests/test_stock.py | 35 ++++++++++++++----------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/stock_alerter/tests/test_stock.py b/stock_alerter/tests/test_stock.py index c981e61..cfb644b 100644 --- a/stock_alerter/tests/test_stock.py +++ b/stock_alerter/tests/test_stock.py @@ -12,14 +12,13 @@ def setUp(self): def test_price_of_a_new_stock_class_should_be_None(self): self.assertIsNone(self.goog.price) - @unittest.expectedFailure def test_stock_update(self): """An update should set the price on the stock object We will be using the `datetime` module for the timestamp """ self.goog.update(datetime(2014, 2, 12), price=10) - self.assertEqual(100, self.goog.price) + self.assertEqual(10, self.goog.price) def test_negative_price_should_throw_ValueError(self): with self.assertRaises(ValueError): @@ -37,26 +36,24 @@ def test_price_is_the_latest_even_if_updates_are_made_out_of_order(self): class StockTrendTest(unittest.TestCase): - def setUp(self): - self.goog = Stock("GOOG") - - def given_a_series_of_prices(self, prices): + def given_a_series_of_prices(self, goog, prices): timestamps = [datetime(2014, 2, 10), datetime(2014, 2, 11), datetime(2014, 2, 12), datetime(2014, 2, 13)] for timestamp, price in zip(timestamps, prices): - self.goog.update(timestamp, price) - - def test_increasing_trend_is_true_if_price_increase_for_3_updates(self): - self.given_a_series_of_prices([8, 10, 12]) - self.assertTrue(self.goog.is_increasing_trend()) - - def test_increasing_trend_is_false_if_price_decreases(self): - self.given_a_series_of_prices([8, 12, 10]) - self.assertFalse(self.goog.is_increasing_trend()) - - def test_increasing_trend_is_false_if_price_equal(self): - self.given_a_series_of_prices([8, 10, 10]) - self.assertFalse(self.goog.is_increasing_trend()) + goog.update(timestamp, price) + + def test_stock_trends(self): + dataset = [ + ([8, 10, 12], True), + ([8, 12, 10], False), + ([8, 10, 10], False) + ] + for data in dataset: + prices, output = data + with self.subTest(prices=prices, output=output): + goog = Stock("GOOG") + self.given_a_series_of_prices(goog, prices) + self.assertEqual(output, goog.is_increasing_trend()) class StockCrossOverSignalTest(unittest.TestCase): From 21f8b9977798409ba1d3ade1d97999ed28c08f47 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Thu, 26 Mar 2015 08:49:38 +0530 Subject: [PATCH 32/37] Implementing a spy --- stock_alerter/tests/test_alert.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/stock_alerter/tests/test_alert.py b/stock_alerter/tests/test_alert.py index d7f38e1..2be7039 100644 --- a/stock_alerter/tests/test_alert.py +++ b/stock_alerter/tests/test_alert.py @@ -22,3 +22,15 @@ def test_action_is_executed_when_rule_matches(self): alert.connect(exchange) exchange["GOOG"].update(datetime(2014, 2, 10), 11) action.execute.assert_called_with("sample alert") + + def test_action_doesnt_fire_if_rule_doesnt_match(self): + goog = Stock("GOOG") + exchange = {"GOOG": goog} + rule = PriceRule("GOOG", lambda stock: stock.price > 10) + rule_spy = mock.MagicMock(wraps=rule) + action = mock.MagicMock() + alert = Alert("sample alert", rule_spy, action) + alert.connect(exchange) + alert.check_rule(goog) + rule_spy.matches.assert_called_with(exchange) + self.assertFalse(action.execute.called) From 5d10c464c5ef21d291e768c9b44ff9206ba53b2d Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Thu, 26 Mar 2015 08:51:27 +0530 Subject: [PATCH 33/37] Validating a sequence of calls across different objects --- stock_alerter/tests/test_alert.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/stock_alerter/tests/test_alert.py b/stock_alerter/tests/test_alert.py index 2be7039..c8b685f 100644 --- a/stock_alerter/tests/test_alert.py +++ b/stock_alerter/tests/test_alert.py @@ -34,3 +34,18 @@ def test_action_doesnt_fire_if_rule_doesnt_match(self): alert.check_rule(goog) rule_spy.matches.assert_called_with(exchange) self.assertFalse(action.execute.called) + + def test_action_fires_when_rule_matches(self): + goog = Stock("GOOG") + exchange = {"GOOG": goog} + main_mock = mock.MagicMock() + rule = main_mock.rule + rule.matches.return_value = True + rule.depends_on.return_value = {"GOOG"} + action = main_mock.action + alert = Alert("sample alert", rule, action) + alert.connect(exchange) + goog.update(datetime(2014, 5, 14), 11) + main_mock.assert_has_calls( + [mock.call.rule.matches(exchange), + mock.call.action.execute("sample alert")]) From cfab237bbd5cf7c83dc426458c5999fe01d0eaad Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Thu, 26 Mar 2015 08:53:04 +0530 Subject: [PATCH 34/37] Strictly asserting a sequence of calls --- stock_alerter/tests/test_alert.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/stock_alerter/tests/test_alert.py b/stock_alerter/tests/test_alert.py index c8b685f..dc83efe 100644 --- a/stock_alerter/tests/test_alert.py +++ b/stock_alerter/tests/test_alert.py @@ -46,6 +46,7 @@ def test_action_fires_when_rule_matches(self): alert = Alert("sample alert", rule, action) alert.connect(exchange) goog.update(datetime(2014, 5, 14), 11) - main_mock.assert_has_calls( - [mock.call.rule.matches(exchange), - mock.call.action.execute("sample alert")]) + self.assertEqual([mock.call.rule.depends_on(), + mock.call.rule.matches(exchange), + mock.call.action.execute("sample alert")], + main_mock.mock_calls) From 9d466f7bd9563f75485bf1561daf6dd37f622c88 Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Thu, 26 Mar 2015 09:05:45 +0530 Subject: [PATCH 35/37] Mocking the open function --- stock_alerter/tests/test_reader.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 stock_alerter/tests/test_reader.py diff --git a/stock_alerter/tests/test_reader.py b/stock_alerter/tests/test_reader.py new file mode 100644 index 0000000..d6f76c5 --- /dev/null +++ b/stock_alerter/tests/test_reader.py @@ -0,0 +1,18 @@ +import unittest +from unittest import mock +from datetime import datetime + +from ..reader import FileReader + + +class FileReaderTest(unittest.TestCase): + @mock.patch("builtins.open", + mock.mock_open(read_data="""\ +GOOG,2014-02-11T14:10:22.13,10""")) + def test_FileReader_returns_the_file_contents(self): + reader = FileReader("stocks.txt") + updater = reader.get_updates() + update = next(updater) + self.assertEqual(("GOOG", + datetime(2014, 2, 11, 14, 10, 22, 130000), + 10), update) From 76b9873c8396c916120b1b65fd69635513b182ca Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Tue, 9 Jun 2015 09:07:29 +0530 Subject: [PATCH 36/37] Added documentation on how to use this repository --- README.md | 3 --- README.rst | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) delete mode 100644 README.md create mode 100644 README.rst diff --git a/README.md b/README.md deleted file mode 100644 index 8236bd9..0000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Test Driven Python Development - -This repository contains the code for the book Test Driven Python Development (Packt Publishing). The repository has branches, so you can get the code for the individual chapters by choossing the appropriate branch. Feel free to fork the repository to work on the code as you go through the book. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..a862618 --- /dev/null +++ b/README.rst @@ -0,0 +1,24 @@ +Test Driven Python Development +============================== + +This repository contains the code for the book `Test Driven Python Development `. + +How to use this repository +-------------------------- + +The repository has tags, so you can get the code for the individual chapters by choosing the appropriate tag. + +**If you are familiar with using git & github**: Clone this repository, then look at tags to get the point in the code you want to go to. Checkout the appropriate tag on your local repository to get to that particular state of the code. + +**If you have not used github before**: Look at the `list of tags `, and click the *zip link* for the point in the code that you are interested in. This will download the code for that particular commit as a zip file. Unzip this file into your project folder and work from there. + +Notes +----- + +* Python 2.6+ compatible version of the code is available on it's own branch here - https://github.com/siddhi/test_driven_python/tree/py2.6 +* Some readers have submitted contributions to this repository. You can access the code with these contributions in the *contrib* branch - https://github.com/siddhi/test_driven_python/tree/contrib + +Submitting contributions +------------------------ + +If you would like to submit changes to this repository, checkout the *contrib* branch, make changes and submit a pull request. I will be happy to merge the changes in. From 751e204b0b32b2780db598297217ff2758a9f39f Mon Sep 17 00:00:00 2001 From: Siddharta Govindaraj Date: Tue, 9 Jun 2015 09:09:08 +0530 Subject: [PATCH 37/37] Updated amazon link in documentation --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index a862618..d3f83e6 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ Test Driven Python Development ============================== -This repository contains the code for the book `Test Driven Python Development `. +This repository contains the code for the book `Test Driven Python Development `. How to use this repository --------------------------