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..d3f83e6 --- /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. diff --git a/run_test.py b/run_test.py new file mode 100644 index 0000000..5595d1a --- /dev/null +++ b/run_test.py @@ -0,0 +1,24 @@ +import unittest + + +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_suite) diff --git a/stock_alerter/tests/test_alert.py b/stock_alerter/tests/test_alert.py index d7f38e1..dc83efe 100644 --- a/stock_alerter/tests/test_alert.py +++ b/stock_alerter/tests/test_alert.py @@ -22,3 +22,31 @@ 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) + + 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) + self.assertEqual([mock.call.rule.depends_on(), + mock.call.rule.matches(exchange), + mock.call.action.execute("sample alert")], + main_mock.mock_calls) 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) diff --git a/stock_alerter/tests/test_stock.py b/stock_alerter/tests/test_stock.py index b97de2b..cfb644b 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") @@ -115,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):