Archived
0
0
This commit is contained in:
2025-12-16 21:28:53 +08:00
parent 11d130c3fd
commit 1e534e5fe5
37 changed files with 428 additions and 207 deletions

View File

@@ -9,15 +9,19 @@ class TestSM2Algorithm(unittest.TestCase):
def setUp(self):
# 模拟 timer 函数
self.timestamp_patcher = patch('heurams.kernel.algorithms.sm2.timer.get_timestamp')
self.daystamp_patcher = patch('heurams.kernel.algorithms.sm2.timer.get_daystamp')
self.timestamp_patcher = patch(
"heurams.kernel.algorithms.sm2.timer.get_timestamp"
)
self.daystamp_patcher = patch(
"heurams.kernel.algorithms.sm2.timer.get_daystamp"
)
self.mock_get_timestamp = self.timestamp_patcher.start()
self.mock_get_daystamp = self.daystamp_patcher.start()
# 设置固定返回值
self.mock_get_timestamp.return_value = 1000.0
self.mock_get_daystamp.return_value = 100
def tearDown(self):
self.timestamp_patcher.stop()
self.daystamp_patcher.stop()
@@ -46,7 +50,14 @@ class TestSM2Algorithm(unittest.TestCase):
def test_revisor_feedback_less_than_3(self):
"""测试 feedback < 3 重置 rept 和 interval"""
algodata = {SM2Algorithm.algo_name: {"efactor": 2.5, "rept": 5, "interval": 10, "real_rept": 3}}
algodata = {
SM2Algorithm.algo_name: {
"efactor": 2.5,
"rept": 5,
"interval": 10,
"real_rept": 3,
}
}
SM2Algorithm.revisor(algodata, feedback=2)
self.assertEqual(algodata[SM2Algorithm.algo_name]["rept"], 0)
# rept=0 导致 interval 被设置为 1
@@ -55,7 +66,14 @@ class TestSM2Algorithm(unittest.TestCase):
def test_revisor_feedback_greater_equal_3(self):
"""测试 feedback >= 3 递增 rept"""
algodata = {SM2Algorithm.algo_name: {"efactor": 2.5, "rept": 2, "interval": 6, "real_rept": 2}}
algodata = {
SM2Algorithm.algo_name: {
"efactor": 2.5,
"rept": 2,
"interval": 6,
"real_rept": 2,
}
}
SM2Algorithm.revisor(algodata, feedback=4)
self.assertEqual(algodata[SM2Algorithm.algo_name]["rept"], 3)
self.assertEqual(algodata[SM2Algorithm.algo_name]["real_rept"], 3)
@@ -65,7 +83,14 @@ class TestSM2Algorithm(unittest.TestCase):
def test_revisor_new_activation(self):
"""测试 is_new_activation 重置 rept 和 efactor"""
algodata = {SM2Algorithm.algo_name: {"efactor": 3.0, "rept": 5, "interval": 20, "real_rept": 5}}
algodata = {
SM2Algorithm.algo_name: {
"efactor": 3.0,
"rept": 5,
"interval": 20,
"real_rept": 5,
}
}
SM2Algorithm.revisor(algodata, feedback=5, is_new_activation=True)
self.assertEqual(algodata[SM2Algorithm.algo_name]["rept"], 0)
self.assertEqual(algodata[SM2Algorithm.algo_name]["efactor"], 2.5)
@@ -74,11 +99,20 @@ class TestSM2Algorithm(unittest.TestCase):
def test_revisor_efactor_calculation(self):
"""测试 efactor 计算"""
algodata = {SM2Algorithm.algo_name: {"efactor": 2.5, "rept": 1, "interval": 6, "real_rept": 1}}
algodata = {
SM2Algorithm.algo_name: {
"efactor": 2.5,
"rept": 1,
"interval": 6,
"real_rept": 1,
}
}
SM2Algorithm.revisor(algodata, feedback=5)
# efactor = 2.5 + (0.1 - (5-5)*(0.08 + (5-5)*0.02)) = 2.5 + 0.1 = 2.6
self.assertAlmostEqual(algodata[SM2Algorithm.algo_name]["efactor"], 2.6, places=6)
self.assertAlmostEqual(
algodata[SM2Algorithm.algo_name]["efactor"], 2.6, places=6
)
# 测试 efactor 下限为 1.3
algodata[SM2Algorithm.algo_name]["efactor"] = 1.2
SM2Algorithm.revisor(algodata, feedback=5)
@@ -86,18 +120,32 @@ class TestSM2Algorithm(unittest.TestCase):
def test_revisor_interval_calculation(self):
"""测试 interval 计算规则"""
algodata = {SM2Algorithm.algo_name: {"efactor": 2.5, "rept": 0, "interval": 0, "real_rept": 0}}
algodata = {
SM2Algorithm.algo_name: {
"efactor": 2.5,
"rept": 0,
"interval": 0,
"real_rept": 0,
}
}
SM2Algorithm.revisor(algodata, feedback=4)
# rept 从 0 递增到 1因此 interval 应为 6
self.assertEqual(algodata[SM2Algorithm.algo_name]["interval"], 6)
# 现在 rept=1再次调用 revisor 递增到 2
SM2Algorithm.revisor(algodata, feedback=4)
# rept=2interval = round(6 * 2.5) = 15
self.assertEqual(algodata[SM2Algorithm.algo_name]["interval"], 15)
# 单独测试 rept=1 的情况
algodata2 = {SM2Algorithm.algo_name: {"efactor": 2.5, "rept": 1, "interval": 0, "real_rept": 0}}
algodata2 = {
SM2Algorithm.algo_name: {
"efactor": 2.5,
"rept": 1,
"interval": 0,
"real_rept": 0,
}
}
SM2Algorithm.revisor(algodata2, feedback=4)
# rept 递增到 2interval = round(0 * 2.5) = 0
self.assertEqual(algodata2[SM2Algorithm.algo_name]["interval"], 0)
@@ -108,7 +156,10 @@ class TestSM2Algorithm(unittest.TestCase):
self.mock_get_daystamp.return_value = 200
SM2Algorithm.revisor(algodata, feedback=5)
self.assertEqual(algodata[SM2Algorithm.algo_name]["last_date"], 200)
self.assertEqual(algodata[SM2Algorithm.algo_name]["next_date"], 200 + algodata[SM2Algorithm.algo_name]["interval"])
self.assertEqual(
algodata[SM2Algorithm.algo_name]["next_date"],
200 + algodata[SM2Algorithm.algo_name]["interval"],
)
self.assertEqual(algodata[SM2Algorithm.algo_name]["last_modify"], 1000.0)
def test_is_due(self):
@@ -116,7 +167,7 @@ class TestSM2Algorithm(unittest.TestCase):
algodata = {SM2Algorithm.algo_name: {"next_date": 100}}
self.mock_get_daystamp.return_value = 150
self.assertTrue(SM2Algorithm.is_due(algodata))
algodata[SM2Algorithm.algo_name]["next_date"] = 200
self.assertFalse(SM2Algorithm.is_due(algodata))
@@ -131,5 +182,5 @@ class TestSM2Algorithm(unittest.TestCase):
self.assertEqual(SM2Algorithm.nextdate(algodata), 12345)
if __name__ == '__main__':
unittest.main()
if __name__ == "__main__":
unittest.main()

View File

@@ -21,18 +21,20 @@ class TestAtom(unittest.TestCase):
# 创建临时目录用于持久化测试
self.temp_dir = tempfile.TemporaryDirectory()
self.temp_path = pathlib.Path(self.temp_dir.name)
# 创建默认配置
self.config = ConfigFile(pathlib.Path(__file__).parent.parent.parent.parent /
"src/heurams/default/config/config.toml")
self.config = ConfigFile(
pathlib.Path(__file__).parent.parent.parent.parent
/ "src/heurams/default/config/config.toml"
)
# 使用 ConfigContext 设置配置
self.config_ctx = ConfigContext(self.config)
self.config_ctx.__enter__()
# 清空全局注册表
atom_registry.clear()
def tearDown(self):
"""在每个测试之后运行"""
self.config_ctx.__exit__(None, None, None)
@@ -45,7 +47,7 @@ class TestAtom(unittest.TestCase):
self.assertEqual(atom.ident, "test_atom")
self.assertIn("test_atom", atom_registry)
self.assertEqual(atom_registry["test_atom"], atom)
# 检查 registry 默认值
self.assertIsNone(atom.registry["nucleon"])
self.assertIsNone(atom.registry["electron"])
@@ -58,10 +60,10 @@ class TestAtom(unittest.TestCase):
"""测试 link 方法"""
atom = Atom("test_link")
nucleon = Nucleon("test_nucleon", {"content": "test content"})
atom.link("nucleon", nucleon)
self.assertEqual(atom.registry["nucleon"], nucleon)
# 测试链接不支持的键
with self.assertRaises(ValueError):
atom.link("invalid_key", "value")
@@ -70,8 +72,8 @@ class TestAtom(unittest.TestCase):
"""测试 link 后触发 do_eval"""
atom = Atom("test_eval_trigger")
nucleon = Nucleon("test_nucleon", {"content": "eval:1+1"})
with patch.object(atom, 'do_eval') as mock_do_eval:
with patch.object(atom, "do_eval") as mock_do_eval:
atom.link("nucleon", nucleon)
mock_do_eval.assert_called_once()
@@ -80,16 +82,16 @@ class TestAtom(unittest.TestCase):
atom = Atom("test_persist_toml")
nucleon = Nucleon("test_nucleon", {"content": "test"})
atom.link("nucleon", nucleon)
# 设置路径
test_path = self.temp_path / "test.toml"
atom.link("nucleon_path", test_path)
atom.persist("nucleon")
# 验证文件存在且内容正确
self.assertTrue(test_path.exists())
with open(test_path, 'r') as f:
with open(test_path, "r") as f:
data = toml.load(f)
self.assertEqual(data["ident"], "test_nucleon")
self.assertEqual(data["payload"]["content"], "test")
@@ -99,14 +101,14 @@ class TestAtom(unittest.TestCase):
atom = Atom("test_persist_json")
electron = Electron("test_electron", {})
atom.link("electron", electron)
test_path = self.temp_path / "test.json"
atom.link("electron_path", test_path)
atom.persist("electron")
self.assertTrue(test_path.exists())
with open(test_path, 'r') as f:
with open(test_path, "r") as f:
data = json.load(f)
self.assertIn("supermemo2", data)
@@ -117,7 +119,7 @@ class TestAtom(unittest.TestCase):
atom.link("nucleon", nucleon)
atom.link("nucleon_path", self.temp_path / "test.txt")
atom.registry["nucleon_fmt"] = "invalid"
with self.assertRaises(KeyError):
atom.persist("nucleon")
@@ -127,7 +129,7 @@ class TestAtom(unittest.TestCase):
nucleon = Nucleon("test_nucleon", {})
atom.link("nucleon", nucleon)
# 不设置 nucleon_path
with self.assertRaises(TypeError):
atom.persist("nucleon")
@@ -135,26 +137,26 @@ class TestAtom(unittest.TestCase):
"""测试 __getitem__ 和 __setitem__"""
atom = Atom("test_getset")
nucleon = Nucleon("test_nucleon", {})
atom["nucleon"] = nucleon
self.assertEqual(atom["nucleon"], nucleon)
# 测试不支持的键
with self.assertRaises(KeyError):
_ = atom["invalid_key"]
with self.assertRaises(KeyError):
atom["invalid_key"] = "value"
def test_do_eval_with_eval_string(self):
"""测试 do_eval 处理 eval: 字符串"""
atom = Atom("test_do_eval")
nucleon = Nucleon("test_nucleon", {
"content": "eval:'hello' + ' world'",
"number": "eval:2 + 3"
})
nucleon = Nucleon(
"test_nucleon",
{"content": "eval:'hello' + ' world'", "number": "eval:2 + 3"},
)
atom.link("nucleon", nucleon)
# do_eval 应该在链接时自动调用
# 检查 eval 表达式是否被求值
self.assertEqual(nucleon.payload["content"], "hello world")
@@ -163,11 +165,11 @@ class TestAtom(unittest.TestCase):
def test_do_eval_with_config_access(self):
"""测试 do_eval 访问配置"""
atom = Atom("test_eval_config")
nucleon = Nucleon("test_nucleon", {
"max_riddles": "eval:default['mcq']['max_riddles_num']"
})
nucleon = Nucleon(
"test_nucleon", {"max_riddles": "eval:default['mcq']['max_riddles_num']"}
)
atom.link("nucleon", nucleon)
# 配置中 puzzles.mcq.max_riddles_num = 2
self.assertEqual(nucleon.payload["max_riddles"], 2)
@@ -185,15 +187,15 @@ class TestAtom(unittest.TestCase):
# 创建多个 Atom
atom1 = Atom("atom1")
atom2 = Atom("atom2")
self.assertEqual(len(atom_registry), 2)
self.assertEqual(atom_registry["atom1"], atom1)
self.assertEqual(atom_registry["atom2"], atom2)
# 测试 bidict 的反向查找
self.assertEqual(atom_registry.inverse[atom1], "atom1")
self.assertEqual(atom_registry.inverse[atom2], "atom2")
if __name__ == '__main__':
unittest.main()
if __name__ == "__main__":
unittest.main()

View File

@@ -11,10 +11,12 @@ class TestElectron(unittest.TestCase):
def setUp(self):
# 模拟 timer.get_timestamp 返回固定值
self.timestamp_patcher = patch('heurams.kernel.particles.electron.timer.get_timestamp')
self.timestamp_patcher = patch(
"heurams.kernel.particles.electron.timer.get_timestamp"
)
self.mock_get_timestamp = self.timestamp_patcher.start()
self.mock_get_timestamp.return_value = 1234567890.0
def tearDown(self):
self.timestamp_patcher.stop()
@@ -67,9 +69,9 @@ class TestElectron(unittest.TestCase):
electron.modify("interval", 5)
self.assertEqual(electron.algodata[electron.algo]["interval"], 5)
self.assertEqual(electron.algodata[electron.algo]["last_modify"], 1234567890.0)
# 修改不存在的字段应记录警告但不引发异常
with patch('heurams.kernel.particles.electron.logger.warning') as mock_warning:
with patch("heurams.kernel.particles.electron.logger.warning") as mock_warning:
electron.modify("unknown_field", 99)
mock_warning.assert_called_once()
@@ -85,7 +87,7 @@ class TestElectron(unittest.TestCase):
def test_is_due(self):
"""测试 is_due 方法"""
electron = Electron("test_electron")
with patch.object(electron.algo, 'is_due') as mock_is_due:
with patch.object(electron.algo, "is_due") as mock_is_due:
mock_is_due.return_value = 1
result = electron.is_due()
mock_is_due.assert_called_once_with(electron.algodata)
@@ -94,7 +96,7 @@ class TestElectron(unittest.TestCase):
def test_rate(self):
"""测试 rate 方法"""
electron = Electron("test_electron")
with patch.object(electron.algo, 'rate') as mock_rate:
with patch.object(electron.algo, "rate") as mock_rate:
mock_rate.return_value = "good"
result = electron.rate()
mock_rate.assert_called_once_with(electron.algodata)
@@ -103,7 +105,7 @@ class TestElectron(unittest.TestCase):
def test_nextdate(self):
"""测试 nextdate 方法"""
electron = Electron("test_electron")
with patch.object(electron.algo, 'nextdate') as mock_nextdate:
with patch.object(electron.algo, "nextdate") as mock_nextdate:
mock_nextdate.return_value = 1234568000
result = electron.nextdate()
mock_nextdate.assert_called_once_with(electron.algodata)
@@ -112,7 +114,7 @@ class TestElectron(unittest.TestCase):
def test_revisor(self):
"""测试 revisor 方法"""
electron = Electron("test_electron")
with patch.object(electron.algo, 'revisor') as mock_revisor:
with patch.object(electron.algo, "revisor") as mock_revisor:
electron.revisor(quality=3, is_new_activation=True)
mock_revisor.assert_called_once_with(electron.algodata, 3, True)
@@ -144,7 +146,7 @@ class TestElectron(unittest.TestCase):
electron.activate()
self.assertEqual(electron["ident"], "test_electron")
self.assertEqual(electron["is_activated"], 1)
with self.assertRaises(KeyError):
_ = electron["nonexistent_key"]
@@ -154,7 +156,7 @@ class TestElectron(unittest.TestCase):
electron["interval"] = 10
self.assertEqual(electron.algodata[electron.algo]["interval"], 10)
self.assertEqual(electron.algodata[electron.algo]["last_modify"], 1234567890.0)
with self.assertRaises(AttributeError):
electron["ident"] = "new_ident"
@@ -173,5 +175,5 @@ class TestElectron(unittest.TestCase):
self.assertEqual(placeholder.algo, algorithms["supermemo2"])
if __name__ == '__main__':
unittest.main()
if __name__ == "__main__":
unittest.main()

View File

@@ -9,7 +9,9 @@ class TestNucleon(unittest.TestCase):
def test_init(self):
"""测试初始化"""
nucleon = Nucleon("test_id", {"content": "hello", "note": "world"}, {"author": "test"})
nucleon = Nucleon(
"test_id", {"content": "hello", "note": "world"}, {"author": "test"}
)
self.assertEqual(nucleon.ident, "test_id")
self.assertEqual(nucleon.payload, {"content": "hello", "note": "world"})
self.assertEqual(nucleon.metadata, {"author": "test"})
@@ -27,7 +29,7 @@ class TestNucleon(unittest.TestCase):
self.assertEqual(nucleon["ident"], "test_id")
self.assertEqual(nucleon["content"], "hello")
self.assertEqual(nucleon["note"], "world")
with self.assertRaises(KeyError):
_ = nucleon["nonexistent"]
@@ -58,16 +60,23 @@ class TestNucleon(unittest.TestCase):
def test_do_eval_with_metadata_access(self):
"""测试 do_eval 访问元数据"""
nucleon = Nucleon("test_id", {"result": "eval:nucleon.metadata.get('value', 0)"}, {"value": 42})
nucleon = Nucleon(
"test_id",
{"result": "eval:nucleon.metadata.get('value', 0)"},
{"value": 42},
)
nucleon.do_eval()
self.assertEqual(nucleon.payload["result"], "42")
def test_do_eval_nested(self):
"""测试 do_eval 处理嵌套结构"""
nucleon = Nucleon("test_id", {
"list": ["eval:2*3", "normal"],
"dict": {"key": "eval:'hello' + ' world'"}
})
nucleon = Nucleon(
"test_id",
{
"list": ["eval:2*3", "normal"],
"dict": {"key": "eval:'hello' + ' world'"},
},
)
nucleon.do_eval()
self.assertEqual(nucleon.payload["list"][0], "6")
self.assertEqual(nucleon.payload["list"][1], "normal")
@@ -95,5 +104,5 @@ class TestNucleon(unittest.TestCase):
self.assertEqual(placeholder.metadata, {})
if __name__ == '__main__':
unittest.main()
if __name__ == "__main__":
unittest.main()

View File

@@ -19,5 +19,5 @@ class TestBasePuzzle(unittest.TestCase):
self.assertEqual(str(puzzle), "谜题: BasePuzzle")
if __name__ == '__main__':
unittest.main()
if __name__ == "__main__":
unittest.main()

View File

@@ -16,13 +16,13 @@ class TestClozePuzzle(unittest.TestCase):
self.assertEqual(puzzle.wording, "填空题 - 尚未刷新谜题")
self.assertEqual(puzzle.answer, ["填空题 - 尚未刷新谜题"])
@patch('random.sample')
@patch("random.sample")
def test_refresh(self, mock_sample):
"""测试 refresh 方法"""
mock_sample.return_value = [0, 2] # 选择索引 0 和 2
puzzle = ClozePuzzle("hello/world/test", min_denominator=2, delimiter="/")
puzzle.refresh()
# 检查 wording 和 answer
self.assertNotEqual(puzzle.wording, "填空题 - 尚未刷新谜题")
self.assertNotEqual(puzzle.answer, ["填空题 - 尚未刷新谜题"])
@@ -47,5 +47,5 @@ class TestClozePuzzle(unittest.TestCase):
self.assertIn("填空题 - 尚未刷新谜题", str_repr)
if __name__ == '__main__':
unittest.main()
if __name__ == "__main__":
unittest.main()

View File

@@ -38,8 +38,8 @@ class TestMCQPuzzle(unittest.TestCase):
self.assertEqual(len(puzzle.jammer), 4)
self.assertEqual(set(puzzle.jammer), {" "}) # 三个空格?实际上循环填充空格
@patch('random.sample')
@patch('random.shuffle')
@patch("random.sample")
@patch("random.shuffle")
def test_refresh(self, mock_shuffle, mock_sample):
"""测试 refresh 方法生成题目"""
mapping = {"q1": "a1", "q2": "a2", "q3": "a3"}
@@ -51,7 +51,7 @@ class TestMCQPuzzle(unittest.TestCase):
["j1", "j2", "j3"], # 为每个问题选择干扰项(实际调用两次)
]
puzzle.refresh()
# 检查 wording 是列表
self.assertIsInstance(puzzle.wording, list)
self.assertEqual(len(puzzle.wording), 2)
@@ -110,7 +110,7 @@ class TestMCQPuzzle(unittest.TestCase):
puzzle.answer = ["选择题 - 尚未刷新谜题"]
self.assertIn("选择题 - 尚未刷新谜题", str(puzzle))
self.assertIn("正确答案", str(puzzle))
puzzle.wording = ["Q1", "Q2"]
puzzle.answer = ["A1", "A2"]
str_repr = str(puzzle)
@@ -118,5 +118,5 @@ class TestMCQPuzzle(unittest.TestCase):
self.assertIn("A1, A2", str_repr)
if __name__ == '__main__':
unittest.main()
if __name__ == "__main__":
unittest.main()

View File

@@ -15,15 +15,15 @@ class TestPhaser(unittest.TestCase):
self.atom_new = Mock(spec=Atom)
self.atom_new.registry = {"electron": Mock(spec=Electron)}
self.atom_new.registry["electron"].is_activated.return_value = False
self.atom_old = Mock(spec=Atom)
self.atom_old.registry = {"electron": Mock(spec=Electron)}
self.atom_old.registry["electron"].is_activated.return_value = True
# 模拟 Procession 类以避免复杂依赖
self.procession_patcher = patch('heurams.kernel.reactor.phaser.Procession')
self.procession_patcher = patch("heurams.kernel.reactor.phaser.Procession")
self.mock_procession_class = self.procession_patcher.start()
def tearDown(self):
self.procession_patcher.stop()
@@ -31,10 +31,10 @@ class TestPhaser(unittest.TestCase):
"""测试混合新旧原子的初始化"""
atoms = [self.atom_old, self.atom_new, self.atom_old]
phaser = Phaser(atoms)
# 应该创建两个 Procession一个用于旧原子一个用于新原子以及一个总体复习
self.assertEqual(self.mock_procession_class.call_count, 3)
# 检查调用参数
calls = self.mock_procession_class.call_args_list
# 第一个调用应该是旧原子的初始复习
@@ -51,7 +51,7 @@ class TestPhaser(unittest.TestCase):
"""测试只有旧原子"""
atoms = [self.atom_old, self.atom_old]
phaser = Phaser(atoms)
# 应该创建两个 Procession一个初始复习一个总体复习
self.assertEqual(self.mock_procession_class.call_count, 2)
calls = self.mock_procession_class.call_args_list
@@ -64,7 +64,7 @@ class TestPhaser(unittest.TestCase):
"""测试只有新原子"""
atoms = [self.atom_new, self.atom_new]
phaser = Phaser(atoms)
self.assertEqual(self.mock_procession_class.call_count, 2)
calls = self.mock_procession_class.call_args_list
self.assertEqual(calls[0][0][0], atoms)
@@ -80,10 +80,10 @@ class TestPhaser(unittest.TestCase):
mock_proc2 = Mock()
mock_proc2.state = ProcessionState.RUNNING
mock_proc2.phase = PhaserState.QUICK_REVIEW
phaser = Phaser([])
phaser.processions = [mock_proc1, mock_proc2]
result = phaser.current_procession()
self.assertEqual(result, mock_proc2)
self.assertEqual(phaser.state, PhaserState.QUICK_REVIEW)
@@ -92,10 +92,10 @@ class TestPhaser(unittest.TestCase):
"""测试所有 Procession 都完成"""
mock_proc = Mock()
mock_proc.state = ProcessionState.FINISHED
phaser = Phaser([])
phaser.processions = [mock_proc]
result = phaser.current_procession()
self.assertEqual(result, 0)
self.assertEqual(phaser.state, PhaserState.FINISHED)
@@ -104,11 +104,11 @@ class TestPhaser(unittest.TestCase):
"""测试没有 Procession"""
phaser = Phaser([])
phaser.processions = []
result = phaser.current_procession()
self.assertEqual(result, 0)
self.assertEqual(phaser.state, PhaserState.FINISHED)
if __name__ == '__main__':
unittest.main()
if __name__ == "__main__":
unittest.main()