読者です 読者をやめる 読者になる 読者になる

北海道苫小牧市出身の初老PGが書くブログ

永遠のプログラマを夢見る、苫小牧市出身のおじさんのちらしの裏

オフラインリアルタイムどう書くの参加者が事前にやっておくべき1つのこと

満員御礼の第5回 オフラインリアルタイムどう書く。問題が事前公開されていないために参加してからじゃないとコードを書き始められないと思っている参加者がほとんどかもしれない(自分も含めて:p)けど、事前にやっておくべきことが1つだけある。

それは何かというと、単体テストを書いておくこと。このイベントでは、入出力が文字列で、テストパターンがTSV方式で与えられるということがお約束となっている。なので、TSVだけ差し替えればテストできるようなスケルトンを作っておくとよい。まさかテストなしでいきなりロジックを書き始める、なんて人は居ないよね・・・??

というわけで、色々な言語で作ってみた。


Perl

use strict;
use warnings;
use Test::More;
use Answer qw(solve);

open my $in, '<', 'patterns.tsv' or die;
while (<$in>) {
    tr/\r\n//d;
    my ($num, $input, $expected) = split /\t/, $_, 3;
    is +(solve $input), $expected, "TEST $num";
}

done_testing;

__END__
% prove -v test.t
test.t ..
ok 1 - TEST \#1
not ok 2 - TEST \#2
ok 3 - TEST \#3
1..3

#   Failed test 'TEST \#2'
#   at test.t line 10.
#          got: '12345'
#     expected: '13245'
# Looks like you failed 1 test of 3.
Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/3 subtests

Test Summary Report
-------------------
test.t (Wstat: 256 Tests: 3 Failed: 1)
  Failed test:  2
  Non-zero exit status: 1
Files=1, Tests=3,  0 wallclock secs ( 0.02 usr  0.01 sys +  0.01 cusr  0.00 csys =  0.04 CPU)
  Result: FAIL

Python

import unittest
from answer import solve

class TestSequenceFunctions(unittest.TestCase):
    def test(self):
        with open('patterns.tsv') as f:
            for line in f:
                num, inputted, expected = line.rstrip().split("\t")
                self.assertEqual(
                    solve(inputted), expected, "%s failed" % num
                    )

if __name__ == '__main__':
    unittest.main()

# % python2.7 test.py
# F
# ======================================================================
# FAIL: test (__main__.TestSequenceFunctions)
# ----------------------------------------------------------------------
# Traceback (most recent call last):
#   File "test.py", line 10, in test
#     solve(inputted), expected, "%s failed" % num
# AssertionError: #2 failed
# 
# ----------------------------------------------------------------------
# Ran 1 test in 0.000s
# 
# FAILED (failures=1)

Ruby

require "runit/testcase"
require "runit/cui/testrunner"

require "answer.rb"

class AnswerTest < RUNIT::TestCase
  def test
    IO.foreach("patterns.tsv") do |line|
      num, input, expected = line.chomp.split("\t")
      assert_equal (solve input), expected, "#{num} failed."
    end
  end
end

RUNIT::CUI::TestRunner.run(AnswerTest.suite)

__END__
% ruby test.rb
Loaded suite AnswerTest
Started
AnswerTest#test: F

Finished in 0.00554 seconds.

  1) Failure:
AnswerTest#test
    [test.rb:10:in `test'
     test.rb:8:in `foreach'
     test.rb:8:in `test'
     .../ruby/1.8/runit/testcase.rb:42:in `run'
     .../ruby/1.8/runit/testsuite.rb:23:in `run']:
#2 failed.
<"12345"> expected but was
<"13245">.

1 tests, 2 assertions, 1 failures, 0 errors

Java

import static org.junit.Assert.*;
import java.io.*;

public class Test {
    @org.junit.Test
    public void simpleAdd() throws Exception {
        final FileInputStream in = new FileInputStream("patterns.tsv");
        final InputStreamReader reader = new InputStreamReader(in, "US-ASCII");
        final BufferedReader buffered = new BufferedReader(reader);
        String line;
        while ((line = buffered.readLine()) != null) {
            final String[] parts = line.split("\t");
            assertEquals(
                parts[0] + " failed",
                Answer.solve(parts[1]),
                parts[2]
            );
        }
    }
}

/*
% javac -cp .:junit-4.10.jar Answer.java Test.java; java -cp .:junit-4.10.jar org.junit.runner.JUnitCore Test
JUnit version 4.10
.E
Time: 0.007
There was 1 failure:
1) simpleAdd(Test)
org.junit.ComparisonFailure: #2 failed expected:<1[23]45> but was:<1[32]45>
        at org.junit.Assert.assertEquals(Assert.java:125)
        at Test.simpleAdd(Test.java:13)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
        at org.junit.runners.Suite.runChild(Suite.java:128)
        at org.junit.runners.Suite.runChild(Suite.java:24)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:136)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:117)
        at org.junit.runner.JUnitCore.runMain(JUnitCore.java:98)
        at org.junit.runner.JUnitCore.runMainAndExit(JUnitCore.java:53)
        at org.junit.runner.JUnitCore.main(JUnitCore.java:45)

FAILURES!!!
Tests run: 1,  Failures: 1
*/

Scala

import org.scalatest.FunSuite
import scala.io.Source
import Answer.solve
 
class Test extends FunSuite {
  val source = Source.fromFile("patterns.tsv")
  val lines = source.getLines
  lines.foreach(line => {
      val parts = line split '\t'
      test("TEST " + parts(0)) {
        assert(solve(parts(1)) === parts(2))
      }
    }
  )
}

/*
% scalac -cp .:scalatest_2.9.0-1.8.jar Test.scala Answer.scala; scala -cp .:scalatest_2.9.0-1.8.jar org.scalatest.run Test
Run starting. Expected test count is: 3
Test:
- TEST #1
- TEST #2 *** FAILED ***
  "1[23]45" did not equal "1[32]45" (Test.scala:11)
- TEST #3
Run completed in 118 milliseconds.
Total number of tests run: 3
Suites: completed 1, aborted 0
Tests: succeeded 2, failed 1, ignored 0, pending 0
*** 1 TEST FAILED ***
*/

Haskell

module Main where
import Test.HUnit
import Answer

main :: IO ()
main = do
  eachLines <- lines `fmap` readFile "patterns.tsv"
  patterns <- return $ split3 `fmap` eachLines
  tests <- return $ (TestCase . doAssert) `fmap` patterns
  runTestTT $ TestList tests
  return ()
    where split3 str = let (x, xs)   = split str
                           (x', x'') = split xs
                       in (x, x', x'')
          split = (id >< tail) . break (== '\t')
          doAssert (name, input, expected) =
              assertEqual name expected (solve input)
          (><) :: (a -> b) -> (c -> d) -> (a, c) -> (b, d)
          (f >< g) (x, y) = (f x, g y)

-- % runhaskell test.hs
-- ### Failure in: 1
-- #2
-- expected: "13245"
--  but got: "12345"
-- Cases: 3  Tried: 3  Errors: 0  Failures: 1

Perl以外は普段使わない言語なので適当。後、C系統とLISP系統、Groovy、node、PHPなどなどのテストはまだない。pull-req絶賛募集中 :p。

【11/7追記】@emattsan さんがCとC++のスケルトンを実装して下さいました。